From 990b47f770ce8fcc422c8bc7d5f833f8840e748e Mon Sep 17 00:00:00 2001 From: Max Date: Fri, 28 Oct 2022 22:42:02 +0800 Subject: [PATCH] [add] yao get command --- .gitignore | 3 +- cmd/get.go | 62 ++++++++++ cmd/get/get.go | 276 ++++++++++++++++++++++++++++++++++++++++++++ cmd/get/get_test.go | 32 +++++ cmd/root.go | 5 +- 5 files changed, 375 insertions(+), 3 deletions(-) create mode 100644 cmd/get.go create mode 100644 cmd/get/get.go create mode 100644 cmd/get/get_test.go diff --git a/.gitignore b/.gitignore index 24ef94a5..eea5ce1c 100644 --- a/.gitignore +++ b/.gitignore @@ -33,4 +33,5 @@ xgen/v1.0/* !xgen/v0.9/index.html !xgen/v1.0/index.html !xgen/v1.0/umi.js -!xgen/v1.0/layouts__index.async.js \ No newline at end of file +!xgen/v1.0/layouts__index.async.js +*-unit-test \ No newline at end of file diff --git a/cmd/get.go b/cmd/get.go new file mode 100644 index 00000000..f0298043 --- /dev/null +++ b/cmd/get.go @@ -0,0 +1,62 @@ +package cmd + +import ( + "fmt" + "os" + + "github.com/fatih/color" + "github.com/spf13/cobra" + "github.com/yaoapp/yao/cmd/get" + "github.com/yaoapp/yao/share" +) + +var getCmd = &cobra.Command{ + Use: "get", + Short: L("Get an application"), + Long: L("Get an application"), + Run: func(cmd *cobra.Command, args []string) { + if len(args) < 1 { + fmt.Println(color.RedString(L("Not enough arguments"))) + fmt.Println(color.WhiteString(share.BUILDNAME + " help")) + return + } + + repo := args[0] + pkg, err := get.New(repo) + if err != nil { + fmt.Println(color.RedString(err.Error())) + os.Exit(1) + } + + fmt.Println(color.WhiteString("From Infra: %s", pkg.Remote)) + fmt.Println(color.WhiteString("Visit: https://LetsInfra.com")) + err = pkg.Download() + if err != nil { + fmt.Println(color.RedString(err.Error())) + os.Exit(1) + } + + dest, err := os.Getwd() + if err != nil { + fmt.Println(color.RedString(err.Error())) + os.Exit(1) + } + + // dest, err = os.MkdirTemp(dest, "*-unit-test") + // if err != nil { + // fmt.Println(color.RedString(err.Error())) + // os.Exit(1) + // } + // os.MkdirAll(dest, os.ModePerm) + + app, err := pkg.Unpack(dest) + if err != nil { + fmt.Println(color.RedString(err.Error())) + os.Exit(1) + } + + fmt.Println(color.GreenString(app.Name), color.WhiteString(app.Version)) + fmt.Println(color.GreenString(L("✨DONE✨"))) + + }, +} diff --git a/cmd/get/get.go b/cmd/get/get.go new file mode 100644 index 00000000..35215af7 --- /dev/null +++ b/cmd/get/get.go @@ -0,0 +1,276 @@ +package get + +import ( + "archive/zip" + "fmt" + "io" + "io/ioutil" + "net/http" + "os" + "path/filepath" + "strings" + "time" + + jsoniter "github.com/json-iterator/go" + "github.com/yaoapp/gou/fs/system" + "github.com/yaoapp/kun/log" + "github.com/yaoapp/yao/widgets/app" +) + +const ( + + // Application application + Application uint = iota + + // Widgets ? model & table & flow + Widgets + + // Table table widget + Table + + // Form form widget + Form + + // Model model model + Model + + // Flow data flow + Flow +) + +// Package package +type Package struct { + Name string + Team string + Type uint + Remote string + Origin string + Temp string + Tag string + From string +} + +// New create a package via name +func New(repo string) (*Package, error) { + + team, name, tag, err := parse(repo) + if err != nil { + return nil, err + } + + pkg := &Package{ + Origin: repo, + Team: team, + Name: name, + Tag: tag, + Type: Application, + } + + url := pkg.InfraURL() + if urlExists(url) { + pkg.Remote = url + pkg.From = "LetsInfra.com" + return pkg, nil + } + + // @Todo: Download from Github + + return nil, fmt.Errorf("%s not found", repo) +} + +// InfraURL infra package url +func parse(repo string) (string, string, string, error) { + + tag := "latest" + repo = strings.TrimSpace(repo) + if !strings.Contains(repo, "/") { + repo = fmt.Sprintf("yaoapp/%s", repo) + } + + if strings.Contains(repo, "@") { + arr := strings.Split(repo, "@") + repo = arr[0] + tag = arr[1] + } + + arr := strings.Split(repo, "/") + if len(arr) != 2 { + return "", "", "", fmt.Errorf("REPO: %s format error", repo) + } + + team := arr[0] + name := arr[1] + return team, name, tag, nil +} + +// InfraURL infra package url +func (pkg *Package) InfraURL() string { + return fmt.Sprintf("https://mirrors.yao.run/apps/%s/%s/%s.zip", pkg.Team, pkg.Name, pkg.Tag) +} + +// GithubURL github package url +func (pkg *Package) GithubURL() string { + return fmt.Sprintf("mirrors.letsinfra.com/apps/%s/%s/%s", pkg.Team, pkg.Name, pkg.Tag) +} + +// urlExists check the http url is exists +func urlExists(url string) bool { + resp, err := http.Get(url) + if err != nil { + return false + } + + if resp.Body != nil { + defer resp.Body.Close() + } + + return resp.StatusCode == 200 +} + +// Download a package from remote +func (pkg *Package) Download() error { + + if pkg.Remote == "" { + return fmt.Errorf("remote url is requried") + } + + root, err := os.MkdirTemp("", "*-yao-zip") + if err != nil { + return fmt.Errorf("Can't Create temp dir %s", err.Error()) + } + + name := fmt.Sprintf("%s-%s-%d.zip", pkg.Team, pkg.Name, time.Now().UnixMicro()) + file := filepath.Join(root, name) + out, err := os.Create(file) + defer out.Close() + if err != nil { + return fmt.Errorf("Can't Create file: %s", err.Error()) + } + + resp, err := http.Get(pkg.Remote) + if err != nil { + return fmt.Errorf("Download Error: %s", err.Error()) + } + defer resp.Body.Close() + + _, err = io.Copy(out, resp.Body) + if err != nil { + return fmt.Errorf("Copy Error: %s", err.Error()) + } + + pkg.Temp = file + return nil +} + +// Validate a package files +func (pkg *Package) Validate() error { + if pkg.Temp == "" { + return fmt.Errorf("temp file not found") + } + return nil +} + +// Unpack a package to current dir +func (pkg *Package) Unpack(dest string) (*app.DSL, error) { + + dest, err := filepath.Abs(dest) + if err != nil { + return nil, err + } + + files, err := ioutil.ReadDir(dest) + if err != nil { + return nil, err + } + + if len(files) > 0 { + return nil, fmt.Errorf("current folder shoud be empty") + } + + temp, err := os.MkdirTemp("", "*-yao-unzip") + if err != nil { + return nil, err + } + defer os.RemoveAll(temp) + + // Read zip file + r, err := zip.OpenReader(pkg.Temp) + if err != nil { + return nil, err + } + + defer r.Close() + defer os.Remove(pkg.Temp) + + path := "" + for i, f := range r.File { + if i == 0 { + path = filepath.Join(temp, strings.TrimRight(f.Name, "/")) + } + err := extractFile(f, temp) + if err != nil { + return nil, err + } + } + + data, err := os.ReadFile(filepath.Join(path, "app.json")) + if err != nil { + return nil, err + } + + var setting app.DSL + err = jsoniter.Unmarshal(data, &setting) + if err != nil { + return nil, err + } + + fs := system.New("/") + err = fs.Copy(path, dest) + if err != nil { + return nil, err + } + + // Remove env + err = fs.Remove(filepath.Join(dest, ".env")) + if err != nil { + return nil, err + } + + return &setting, nil +} + +// extractFile extract and save file to the dest path +func extractFile(f *zip.File, dest string) error { + rc, err := f.Open() + if err != nil { + return err + } + defer rc.Close() + + path := filepath.Join(dest, f.Name) + + // Check for ZipSlip (Directory traversal) + if !strings.HasPrefix(path, filepath.Clean(dest)+string(os.PathSeparator)) { + return fmt.Errorf("illegal file path: %s", path) + } + + if f.FileInfo().IsDir() { + os.MkdirAll(path, f.Mode()) + } else { + os.MkdirAll(filepath.Dir(path), f.Mode()) + f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) + if err != nil { + return err + } + defer func() { + if err := f.Close(); err != nil { + log.Error("repo unzip extractFile: %s", err.Error()) + } + }() + _, err = io.Copy(f, rc) + if err != nil { + return err + } + } + return nil +} diff --git a/cmd/get/get_test.go b/cmd/get/get_test.go new file mode 100644 index 00000000..4e07b5a3 --- /dev/null +++ b/cmd/get/get_test.go @@ -0,0 +1,32 @@ +package get + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestUnpack(t *testing.T) { + pkg, err := New("yaoapp/demo-app") + if err != nil { + t.Fatal(err) + } + + if err := pkg.Download(); err != nil { + t.Fatal(err) + } + + dest, err := os.MkdirTemp("", "*-unit-test") + if err != nil { + t.Fatal(err) + } + + defer os.RemoveAll(dest) + app, err := pkg.Unpack(dest) + if err != nil { + t.Fatal(err) + } + + assert.NotNil(t, app.Name) +} diff --git a/cmd/root.go b/cmd/root.go index ca3009a6..74247736 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -17,7 +17,8 @@ var envFile string var lang = os.Getenv("YAO_LANG") var langs = map[string]string{ - "Start Engine": "启动象传应用引擎", + "Start Engine": "启动 YAO 应用引擎", + "Get an application": "下载应用源码", "One or more arguments are not correct": "参数错误", "Application directory": "指定应用路径", "Environment file": "指定环境变量文件", @@ -108,7 +109,7 @@ func init() { inspectCmd, startCmd, runCmd, - initCmd, + getCmd, dumpCmd, restoreCmd, socketCmd,