Skip to content

Commit

Permalink
refactor: cli (#802)
Browse files Browse the repository at this point in the history
# Description

Refactor CLI, compile GO binaries by default, extend support for wasi.

---------

Co-authored-by: woorui <rui1009479218@gmail.com>
  • Loading branch information
venjiang and woorui committed May 6, 2024
1 parent db3bd5e commit 4caef39
Show file tree
Hide file tree
Showing 41 changed files with 416 additions and 743 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,4 @@ coverage.txt
*.o
build/
.env
example/10-ai/*.yaml
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ func InputSchema() any {
Create a Stateful Serverless Function to get the IP and Latency of a domain:

```golang
func handler(ctx serverless.Context) {
func Handler(ctx serverless.Context) {
fc, _ := ai.ParseFunctionCallContext(ctx)

var msg Parameter
Expand All @@ -128,7 +128,7 @@ func handler(ctx serverless.Context) {
Finally, let's run it

```bash
$ go run main.go
$ yomo run app.go

time=2024-03-19T21:43:30.583+08:00 level=INFO msg="connected to zipper" component=StreamFunction sfn_id=B0ttNSEKLSgMjXidB11K1 sfn_name=fn-get-ip-from-domain zipper_addr=localhost:9000
time=2024-03-19T21:43:30.584+08:00 level=INFO msg="register ai function success" component=StreamFunction sfn_id=B0ttNSEKLSgMjXidB11K1 sfn_name=fn-get-ip-from-domain zipper_addr=localhost:9000 name=fn-get-ip-from-domain tag=16
Expand Down
4 changes: 2 additions & 2 deletions cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ See [../example/9-cli/sfn/app.go](../example/9-cli/sfn/app.go)

#### Build

Build the app.go to a WebAssembly file.
Build the app.go, defaults to sfn.yomo binary file, with `-w` flag to generate a WebAssembly file.

```sh
cd ../example/9-cli/sfn
Expand All @@ -69,5 +69,5 @@ yomo build
#### Run

```sh
yomo run sfn.wasm
yomo run sfn.yomo
```
15 changes: 7 additions & 8 deletions cli/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,35 +19,33 @@ import (
"os"

"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/yomorun/yomo/cli/serverless"
"github.com/yomorun/yomo/cli/viper"
"github.com/yomorun/yomo/pkg/log"
)

var buildViper *viper.Viper

// buildCmd represents the build command
var buildCmd = &cobra.Command{
Use: "build [flags] app.go",
Short: "Build the YoMo Stream Function",
Long: "Build the YoMo Stream Function as WebAssembly",
Long: "Build the YoMo Stream Function",
Run: func(cmd *cobra.Command, args []string) {
if err := parseFileArg(args, &opts, defaultSFNSourceFile); err != nil {
log.FailureStatusEvent(os.Stdout, err.Error())
os.Exit(127)
// return
}
loadViperValue(cmd, buildViper, &opts.ModFile, "modfile")
// use environment variable to override flags
opts.UseEnv = true
loadOptionsFromViper(viper.BuildViper, &opts)

log.InfoStatusEvent(os.Stdout, "YoMo Stream Function file: %v", opts.Filename)
log.InfoStatusEvent(os.Stdout, "YoMo Stream Function parsing...")
s, err := serverless.Create(&opts)
if err != nil {
log.FailureStatusEvent(os.Stdout, err.Error())
os.Exit(127)
// return
}
log.InfoStatusEvent(os.Stdout, "YoMo Stream Function parse done.")
// build
log.PendingStatusEvent(os.Stdout, "YoMo Stream Function building...")
if err := s.Build(true); err != nil {
Expand All @@ -63,6 +61,7 @@ func init() {
rootCmd.AddCommand(buildCmd)

buildCmd.Flags().StringVarP(&opts.ModFile, "modfile", "m", "", "custom go.mod")
buildCmd.Flags().BoolVarP(&opts.WASI, "wasi", "w", false, "build with WASI target")

buildViper = bindViper(buildCmd)
viper.BindPFlags(viper.BuildViper, buildCmd.Flags())
}
67 changes: 34 additions & 33 deletions cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,11 @@ package cli
import (
"fmt"
"path"
"path/filepath"
"runtime"
"strconv"
"strings"

"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/spf13/viper"
"github.com/yomorun/yomo/cli/serverless"
"github.com/yomorun/yomo/pkg/file"
Expand All @@ -20,7 +19,8 @@ import (
const (
defaultSFNSourceFile = "app.go"
defaultSFNTestSourceFile = "app_test.go"
defaultSFNCompliedFile = "sfn.wasm"
defaultSFNCompliedFile = "sfn.yomo"
defaultSFNWASIFile = "sfn.wasm"
)

// GetRootPath get root path
Expand All @@ -32,10 +32,11 @@ func GetRootPath() string {
return ""
}

func parseURL(url string, opts *serverless.Options) error {
url = strings.TrimSpace(url)
func parseZipperAddr(opts *serverless.Options) error {
url := opts.ZipperAddr
if url == "" {
url = "localhost:9000"
opts.ZipperAddr = "localhost:9000"
return nil
}

splits := strings.Split(url, ":")
Expand All @@ -53,39 +54,39 @@ func parseURL(url string, opts *serverless.Options) error {
return nil
}

func getViperName(name string) string {
return "yomo_sfn_" + strings.ReplaceAll(name, "-", "_")
// loadOptionsFromViper load options from viper, supports flags and environment variables
func loadOptionsFromViper(v *viper.Viper, opts *serverless.Options) {
opts.Name = v.GetString("name")
opts.ZipperAddr = v.GetString("zipper")
opts.Credential = v.GetString("credential")
opts.ModFile = v.GetString("modfile")
opts.Runtime = v.GetString("runtime")
opts.WASI = v.GetBool("wasi")
}

func bindViper(cmd *cobra.Command) *viper.Viper {
v := viper.New()

// bind environment variables
v.AllowEmptyEnv(true)
cmd.Flags().VisitAll(func(f *pflag.Flag) {
name := getViperName(f.Name)
v.BindEnv(name)
v.SetDefault(name, f.DefValue)
})

return v
}

func loadViperValue(cmd *cobra.Command, v *viper.Viper, p *string, name string) {
f := cmd.Flag(name)
if !f.Changed {
*p = v.GetString(getViperName(name))
func parseFileArg(args []string, opts *serverless.Options, defaultFiles ...string) error {
if len(args) >= 1 && args[0] != "" {
opts.Filename = args[0]
return checkOptions(opts)
}
for _, f := range defaultFiles {
opts.Filename = f
err := checkOptions(opts)
if err == nil {
break
}
}
return nil
}

func parseFileArg(args []string, opts *serverless.Options, defaultFile string) error {
if len(args) >= 1 && args[0] != "" {
opts.Filename = args[0]
} else {
opts.Filename = defaultFile
func checkOptions(opts *serverless.Options) error {
f, err := filepath.Abs(opts.Filename)
if err != nil {
return err
}
if !file.Exists(opts.Filename) {
return fmt.Errorf("file %s not found", opts.Filename)
if !file.Exists(f) {
return fmt.Errorf("file %s not found", f)
}
opts.Filename = f
return nil
}
36 changes: 24 additions & 12 deletions cli/dev.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,35 +17,38 @@ package cli

import (
"os"
"path/filepath"

"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/yomorun/yomo/cli/serverless"
"github.com/yomorun/yomo/cli/viper"
"github.com/yomorun/yomo/pkg/log"
)

var devViper *viper.Viper

// devCmd represents the dev command
var devCmd = &cobra.Command{
Use: "dev [flags] sfn.wasm",
Use: "dev [flags]",
Short: "Test a YoMo Stream Function",
Long: "Test a YoMo Stream Function with public zipper and mocking data",
FParseErrWhitelist: cobra.FParseErrWhitelist{UnknownFlags: true},
Run: func(cmd *cobra.Command, args []string) {
if err := parseFileArg(args, &opts, defaultSFNCompliedFile); err != nil {
if err := parseFileArg(args, &opts, defaultSFNCompliedFile, defaultSFNWASIFile, defaultSFNSourceFile); err != nil {
log.FailureStatusEvent(os.Stdout, err.Error())
return
}
loadViperValue(cmd, devViper, &opts.ModFile, "modfile")
loadOptionsFromViper(viper.RunViper, &opts)
// Serverless
log.InfoStatusEvent(os.Stdout, "YoMo Stream Function file: %v", opts.Filename)
// resolve serverless
log.PendingStatusEvent(os.Stdout, "Create YoMo Stream Function instance...")

// Connect the serverless to YoMo dev-server, it will automatically emit the mock data.
opts.ZipperAddr = "tap.yomo.dev:9140"
opts.Name = "yomo-app-demo"
opts.ZipperAddr = "tap.yomo.dev:9140"

// Set the environment variables for the YoMo Stream Function
os.Setenv("YOMO_SFN_NAME", opts.Name)
os.Setenv("YOMO_SFN_ZIPPER", opts.ZipperAddr)

s, err := serverless.Create(&opts)
if err != nil {
Expand All @@ -54,20 +57,29 @@ var devCmd = &cobra.Command{
}
if !s.Executable() {
log.FailureStatusEvent(os.Stdout,
"You cannot run `%s` directly. build first with the `yomo build %s` command and then run with the 'yomo run sfn.wasm' command.",
"You cannot run `%s` directly. build first with the `yomo build %s` command and then run with the 'yomo run %s' command.",
opts.Filename,
opts.Filename,
opts.Filename,
)
return
}
// build if it's go file
if ext := filepath.Ext(opts.Filename); ext == ".go" {
log.PendingStatusEvent(os.Stdout, "YoMo Stream Function building...")
if err := s.Build(true); err != nil {
log.FailureStatusEvent(os.Stdout, err.Error())
os.Exit(127)
}
log.SuccessStatusEvent(os.Stdout, "Success! YoMo Stream Function build.")
}
// run
log.InfoStatusEvent(
os.Stdout,
"Starting YoMo Stream Function instance with executable file: %s. Zipper: %v.",
opts.Filename,
"Starting YoMo Stream Function instance with zipper: %v",
opts.ZipperAddr,
)
log.InfoStatusEvent(os.Stdout, "YoMo Stream Function is running...")
log.InfoStatusEvent(os.Stdout, "Stream Function is running...")
if err := s.Run(verbose); err != nil {
log.FailureStatusEvent(os.Stdout, err.Error())
return
Expand All @@ -80,5 +92,5 @@ func init() {

devCmd.Flags().StringVarP(&opts.ModFile, "modfile", "m", "", "custom go.mod")

devViper = bindViper(devCmd)
viper.BindPFlags(viper.DevViper, devCmd.Flags())
}
4 changes: 2 additions & 2 deletions cli/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,15 +69,15 @@ var initCmd = &cobra.Command{

// create .env
fname = filepath.Join(name, ".env")
if err := file.PutContents(fname, []byte(fmt.Sprintf("YOMO_SFN_NAME=%s\n", name))); err != nil {
if err := file.PutContents(fname, []byte(fmt.Sprintf("YOMO_SFN_NAME=%s\nYOMO_SFN_ZIPPER=localhost:9000\n", name))); err != nil {
log.FailureStatusEvent(os.Stdout, "Write stream function .env file failure with the error: %v", err)
return
}

log.SuccessStatusEvent(os.Stdout, "Congratulations! You have initialized the stream function successfully.")
log.InfoStatusEvent(os.Stdout, "You can enjoy the YoMo Stream Function via the command: ")
log.InfoStatusEvent(os.Stdout, "\tStep 1: cd %s && yomo build", name)
log.InfoStatusEvent(os.Stdout, "\tStep 2: yomo run sfn.wasm")
log.InfoStatusEvent(os.Stdout, "\tStep 2: yomo run sfn.yomo")
},
}

Expand Down
1 change: 0 additions & 1 deletion cli/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import (

var (
config string
url string
opts serverless.Options
verbose bool
)
Expand Down

0 comments on commit 4caef39

Please sign in to comment.