feat: initial commit
This commit is contained in:
149
cmd/cli/command/app/package.go
Normal file
149
cmd/cli/command/app/package.go
Normal file
@ -0,0 +1,149 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"forge.cadoles.com/arcad/edge/pkg/app"
|
||||
"forge.cadoles.com/arcad/edge/pkg/bundle"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func PackageCommand() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "package",
|
||||
Usage: "Generate a new app package from given directory",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "directory",
|
||||
Usage: "use source directory `DIR`",
|
||||
Aliases: []string{"d"},
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "output-dir",
|
||||
Aliases: []string{"o"},
|
||||
Usage: "use `DIR` as generated package destination",
|
||||
},
|
||||
},
|
||||
Action: func(ctx *cli.Context) error {
|
||||
appDir := ctx.String("directory")
|
||||
outputDir := ctx.String("output-dir")
|
||||
|
||||
if outputDir == "" {
|
||||
workdir, err := os.Getwd()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not retrieve current working directory")
|
||||
}
|
||||
|
||||
outputDir = workdir
|
||||
}
|
||||
|
||||
bundle := bundle.NewDirectoryBundle(appDir)
|
||||
|
||||
manifest, err := app.LoadAppManifest(bundle)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not load app manifest")
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(outputDir, 0o755); err != nil {
|
||||
return errors.Wrapf(err, "could not create directory ''%s'", outputDir)
|
||||
}
|
||||
|
||||
archiveName := fmt.Sprintf(
|
||||
"%s_%s%s",
|
||||
strings.ToLower(string(manifest.ID)),
|
||||
string(manifest.Version),
|
||||
".zip",
|
||||
)
|
||||
packagePath := filepath.Join(outputDir, archiveName)
|
||||
|
||||
if err := zipDirectory(appDir, packagePath); err != nil {
|
||||
return errors.Wrapf(err, "could not zip directory ''%s'", appDir)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func zipDirectory(baseDir string, outputFile string) error {
|
||||
outFile, err := os.Create(outputFile)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := outFile.Close(); err != nil {
|
||||
panic(errors.WithStack(err))
|
||||
}
|
||||
}()
|
||||
|
||||
w := zip.NewWriter(outFile)
|
||||
|
||||
if err := copyDir(w, baseDir+"/", ""); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
if err := w.Close(); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyDir(writer *zip.Writer, baseDir string, zipBasePath string) error {
|
||||
files, err := ioutil.ReadDir(baseDir)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
if !file.IsDir() {
|
||||
srcPath := baseDir + file.Name()
|
||||
zipPath := zipBasePath + file.Name()
|
||||
|
||||
if err := copyFile(writer, srcPath, zipPath); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
} else if file.IsDir() {
|
||||
newBase := baseDir + file.Name() + "/"
|
||||
|
||||
if err := copyDir(writer, newBase, zipBasePath+file.Name()+"/"); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyFile(writer *zip.Writer, srcPath string, zipPath string) error {
|
||||
r, err := os.Open(srcPath)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := r.Close(); err != nil {
|
||||
panic(errors.WithStack(err))
|
||||
}
|
||||
}()
|
||||
|
||||
f, err := writer.Create(zipPath)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
if _, err = io.Copy(f, r); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
16
cmd/cli/command/app/root.go
Normal file
16
cmd/cli/command/app/root.go
Normal file
@ -0,0 +1,16 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func Root() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "app",
|
||||
Usage: "App related commands",
|
||||
Subcommands: []*cli.Command{
|
||||
RunCommand(),
|
||||
PackageCommand(),
|
||||
},
|
||||
}
|
||||
}
|
121
cmd/cli/command/app/run.go
Normal file
121
cmd/cli/command/app/run.go
Normal file
@ -0,0 +1,121 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
|
||||
"forge.cadoles.com/arcad/edge/pkg/bus/memory"
|
||||
appHTTP "forge.cadoles.com/arcad/edge/pkg/http"
|
||||
"forge.cadoles.com/arcad/edge/pkg/module"
|
||||
"forge.cadoles.com/arcad/edge/pkg/storage/sqlite"
|
||||
"gitlab.com/wpetit/goweb/logger"
|
||||
|
||||
"forge.cadoles.com/arcad/edge/pkg/bundle"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
_ "modernc.org/sqlite"
|
||||
)
|
||||
|
||||
func RunCommand() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "run",
|
||||
Usage: "Run the specified app bundle",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "path",
|
||||
Usage: "use `PATH` as app bundle (zipped bundle or directory)",
|
||||
Aliases: []string{"p"},
|
||||
Value: ".",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "address",
|
||||
Usage: "use `ADDRESS` as http server listening address",
|
||||
Aliases: []string{"a"},
|
||||
Value: ":8080",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "log-format",
|
||||
Usage: "use `LOG-FORMAT` ('json' or 'human')",
|
||||
Value: "human",
|
||||
},
|
||||
&cli.IntFlag{
|
||||
Name: "log-level",
|
||||
Usage: "use `LOG-LEVEL` (0: debug -> 5: fatal)",
|
||||
Value: 0,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "storage-file",
|
||||
Usage: "use `FILE` for SQLite storage database",
|
||||
Value: "data.sqlite",
|
||||
},
|
||||
},
|
||||
Action: func(ctx *cli.Context) error {
|
||||
address := ctx.String("address")
|
||||
path := ctx.String("path")
|
||||
logFormat := ctx.String("log-format")
|
||||
logLevel := ctx.Int("log-level")
|
||||
storageFile := ctx.String("storage-file")
|
||||
|
||||
logger.SetFormat(logger.Format(logFormat))
|
||||
logger.SetLevel(logger.Level(logLevel))
|
||||
|
||||
cmdCtx := ctx.Context
|
||||
|
||||
absPath, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not resolve path '%s'", path)
|
||||
}
|
||||
|
||||
logger.Info(cmdCtx, "opening app bundle", logger.F("path", absPath))
|
||||
|
||||
bundle, err := bundle.FromPath(path)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not open path '%s' as an app bundle", path)
|
||||
}
|
||||
|
||||
mux := chi.NewMux()
|
||||
|
||||
mux.Use(middleware.Logger)
|
||||
|
||||
bus := memory.NewBus()
|
||||
|
||||
db, err := sql.Open("sqlite", storageFile)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not open database with path '%s'", storageFile)
|
||||
}
|
||||
|
||||
documentStore := sqlite.NewDocumentStoreWithDB(db)
|
||||
blobStore := sqlite.NewBlobStoreWithDB(db)
|
||||
|
||||
handler := appHTTP.NewHandler(
|
||||
appHTTP.WithBus(bus),
|
||||
appHTTP.WithServerModules(
|
||||
module.ContextModuleFactory(),
|
||||
module.ConsoleModuleFactory(),
|
||||
module.LifecycleModuleFactory(bus),
|
||||
module.NetModuleFactory(bus),
|
||||
module.RPCModuleFactory(bus),
|
||||
module.StoreModuleFactory(documentStore),
|
||||
module.BlobModuleFactory(bus, blobStore),
|
||||
),
|
||||
)
|
||||
if err := handler.Load(bundle); err != nil {
|
||||
return errors.Wrap(err, "could not load app bundle")
|
||||
}
|
||||
|
||||
mux.Handle("/*", handler)
|
||||
|
||||
logger.Info(cmdCtx, "listening", logger.F("address", address))
|
||||
|
||||
if err := http.ListenAndServe(address, mux); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
48
cmd/cli/command/main.go
Normal file
48
cmd/cli/command/main.go
Normal file
@ -0,0 +1,48 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func Main(commands ...*cli.Command) {
|
||||
ctx := context.Background()
|
||||
|
||||
app := &cli.App{
|
||||
Name: "edge-cli",
|
||||
Usage: "Arcad edge cli",
|
||||
Commands: commands,
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "debug",
|
||||
EnvVars: []string{"DEBUG"},
|
||||
Value: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
app.ExitErrHandler = func(ctx *cli.Context, err error) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
debug := ctx.Bool("debug")
|
||||
|
||||
if !debug {
|
||||
fmt.Printf("[ERROR] %v\n", err)
|
||||
} else {
|
||||
fmt.Printf("%+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
sort.Sort(cli.FlagsByName(app.Flags))
|
||||
sort.Sort(cli.CommandsByName(app.Commands))
|
||||
|
||||
if err := app.RunContext(ctx, os.Args); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
10
cmd/cli/main.go
Normal file
10
cmd/cli/main.go
Normal file
@ -0,0 +1,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"forge.cadoles.com/arcad/edge/cmd/cli/command"
|
||||
"forge.cadoles.com/arcad/edge/cmd/cli/command/app"
|
||||
)
|
||||
|
||||
func main() {
|
||||
command.Main(app.Root())
|
||||
}
|
Reference in New Issue
Block a user