any-sync/util/cmd/deploy/deploy.go
2022-12-20 23:49:36 +01:00

357 lines
9.9 KiB
Go

package main
import (
"context"
"fmt"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/app/logger"
"github.com/spf13/cobra"
"go.uber.org/zap"
"os"
"os/exec"
"path"
"path/filepath"
"sync"
)
var log = logger.NewNamed("cmd.deploy")
type absolutePaths struct {
configPath string
nodePkgPath string
nodeBinaryPath string
clientPkgPath string
clientBinaryPath string
dbPath string
initialPath string
nodePkgName string
clientPkgName string
}
type appPath struct {
wdPath string
binaryPath string
configPath string
logPath string
}
const (
anytypeClientBinaryName = "anytype-client"
anytypeNodeBinaryName = "anytype-node"
)
var rootCmd = &cobra.Command{
Use: "deploy",
PersistentPreRun: func(cmd *cobra.Command, args []string) {
absolute := absolutePaths{}
// checking package names
nodePkgName, err := cmd.Flags().GetString("node-pkg")
if err != nil {
log.With(zap.Error(err)).Fatal("node package is not specified")
}
absolute.nodePkgName = nodePkgName
clientPkgName, err := cmd.Flags().GetString("client-pkg")
if err != nil {
log.With(zap.Error(err)).Fatal("client package is not specified")
}
absolute.clientPkgName = clientPkgName
// checking configs
cfgPath, err := cmd.Flags().GetString("config-dir")
if err != nil {
log.With(zap.Error(err)).Fatal("no config-dir flag is registered")
}
if _, err := os.Stat(cfgPath); os.IsNotExist(err) {
log.With(zap.Error(err)).Fatal("the config directory doesn't exist")
}
absolute.configPath, _ = filepath.Abs(cfgPath)
// checking node package
nodePath, err := cmd.Flags().GetString("node-path")
if err != nil {
log.With(zap.Error(err)).Fatal("no node-path flag is registered")
}
if _, err := os.Stat(path.Join(nodePath, "go.mod")); os.IsNotExist(err) {
log.With(zap.Error(err)).Fatal("the path to node does not contain a go module")
}
absolute.nodePkgPath, _ = filepath.Abs(nodePath)
// checking client package
clientPath, err := cmd.Flags().GetString("client-path")
if err != nil {
log.With(zap.Error(err)).Fatal("no client-path flag is registered")
}
if _, err := os.Stat(path.Join(clientPath, "go.mod")); os.IsNotExist(err) {
log.With(zap.Error(err)).Fatal("the path to client does not contain a go module")
}
absolute.clientPkgPath, _ = filepath.Abs(clientPath)
// checking binary path
binaryPath, err := cmd.Flags().GetString("bin")
if err != nil {
log.With(zap.Error(err)).Fatal("no bin flag is registered")
}
err = createDirectoryIfNotExists(binaryPath)
if err != nil {
log.With(zap.Error(err)).Fatal("failed to create directory")
}
absoluteBinPath, _ := filepath.Abs(binaryPath)
absolute.clientBinaryPath = path.Join(absoluteBinPath, anytypeClientBinaryName)
absolute.nodeBinaryPath = path.Join(absoluteBinPath, anytypeNodeBinaryName)
// checking db path
dbPath, err := cmd.Flags().GetString("db-path")
if err != nil {
log.With(zap.Error(err)).Fatal("no db-path flag is registered")
}
err = createDirectoryIfNotExists(dbPath)
if err != nil {
log.With(zap.Error(err)).Fatal("failed to create directory")
}
absolute.dbPath, _ = filepath.Abs(dbPath)
ctx := context.WithValue(context.Background(), "paths", absolute)
cmd.SetContext(ctx)
},
}
var buildRunAllCmd = &cobra.Command{
Use: "build-and-run",
Long: "build and then run all clients and nodes",
Run: func(cmd *cobra.Command, args []string) {
paths, ok := cmd.Context().Value("paths").(absolutePaths)
if !ok {
log.Fatal("did not get context")
}
// checking number of nodes to deploy
numNodes, err := cmd.Flags().GetUint("nodes")
if err != nil {
log.With(zap.Error(err)).Fatal("number of nodes is not specified")
}
// checking number of clients to deploy
numClients, err := cmd.Flags().GetUint("clients")
if err != nil {
log.With(zap.Error(err)).Fatal("number of clients is not specified")
}
// running the script
err = buildRunAll(paths, numClients, numNodes)
if err != nil {
log.With(zap.Error(err)).Fatal("failed to run the command")
}
},
}
var buildAllCmd = &cobra.Command{
Use: "build-all",
Long: "builds both the clients and nodes",
Run: func(cmd *cobra.Command, args []string) {
paths, ok := cmd.Context().Value("paths").(absolutePaths)
if !ok {
log.Fatal("did not get context")
}
err := buildAll(paths)
if err != nil {
log.With(zap.Error(err)).Fatal("failed to run the command")
return
}
},
}
var runAllCmd = &cobra.Command{
Use: "run-all",
Long: "runs all clients and nodes",
Run: func(cmd *cobra.Command, args []string) {
paths, ok := cmd.Context().Value("paths").(absolutePaths)
if !ok {
log.Fatal("did not get context")
}
// checking number of nodes to deploy
numNodes, err := cmd.Flags().GetUint("nodes")
if err != nil {
log.With(zap.Error(err)).Fatal("number of nodes is not specified")
}
// checking number of clients to deploy
numClients, err := cmd.Flags().GetUint("clients")
if err != nil {
log.With(zap.Error(err)).Fatal("number of clients is not specified")
}
err = runAll(paths, numClients, numNodes)
if err != nil {
log.With(zap.Error(err)).Fatal("failed to run the command")
return
}
},
}
func init() {
rootCmd.PersistentFlags().String("config-dir", "etc/configs", "generated configs")
rootCmd.PersistentFlags().String("node-pkg", "github.com/anytypeio/go-anytype-infrastructure-experiments/node/cmd", "node package")
rootCmd.PersistentFlags().String("client-pkg", "github.com/anytypeio/go-anytype-infrastructure-experiments/client/cmd", "client package")
rootCmd.PersistentFlags().String("node-path", "node", "path to node go.mod")
rootCmd.PersistentFlags().String("client-path", "client", "path to client go.mod")
rootCmd.PersistentFlags().String("bin", "bin", "path to folder where all the binaries are")
rootCmd.PersistentFlags().String("db-path", "db", "path to folder where the working directories should be placed")
buildRunAllCmd.Flags().UintP("nodes", "n", 3, "number of nodes to be generated")
buildRunAllCmd.Flags().UintP("clients", "c", 2, "number of clients to be generated")
runAllCmd.Flags().UintP("nodes", "n", 3, "number of nodes to be generated")
runAllCmd.Flags().UintP("clients", "c", 2, "number of clients to be generated")
rootCmd.AddCommand(buildRunAllCmd)
rootCmd.AddCommand(buildAllCmd)
rootCmd.AddCommand(runAllCmd)
}
func main() {
err := rootCmd.Execute()
if err != nil {
log.With(zap.Error(err)).Fatal("failed to execute the command")
}
}
func createDirectoryIfNotExists(dirPath string) (err error) {
if _, err = os.Stat(dirPath); !os.IsNotExist(err) {
return
}
return os.Mkdir(dirPath, os.ModePerm)
}
func createAppPaths(paths absolutePaths, binaryPath, appName string, num int) (appPaths []appPath, err error) {
appTypePath := path.Join(paths.dbPath, appName)
err = createDirectoryIfNotExists(appTypePath)
if err != nil {
return
}
for i := 0; i < num; i++ {
// checking if relevant config exists
cfgPath := path.Join(paths.configPath, fmt.Sprintf("%s%d.yml", appName, i+1))
if _, err = os.Stat(cfgPath); os.IsNotExist(err) {
err = fmt.Errorf("not enough %s configs are generated: %w", appName, err)
return
}
// creating directory for each app
resPath := path.Join(appTypePath, fmt.Sprintf("%d", i+1))
err = createDirectoryIfNotExists(resPath)
if err != nil {
return
}
appPaths = append(appPaths, appPath{
wdPath: resPath,
binaryPath: binaryPath,
configPath: cfgPath,
logPath: path.Join(resPath, "app.log"),
})
}
return
}
func buildRunAll(paths absolutePaths, numClients, numNodes uint) (err error) {
err = buildAll(paths)
if err != nil {
err = fmt.Errorf("failed to build all: %w", err)
return
}
return runAll(paths, numClients, numNodes)
}
func runAll(paths absolutePaths, numClients uint, numNodes uint) (err error) {
nodePaths, err := createAppPaths(paths, paths.nodeBinaryPath, "node", int(numNodes))
if err != nil {
err = fmt.Errorf("failed to create working directories for nodes: %w", err)
return
}
clientPaths, err := createAppPaths(paths, paths.clientBinaryPath, "client", int(numClients))
if err != nil {
err = fmt.Errorf("failed to create working directories for clients: %w", err)
return
}
wg := sync.WaitGroup{}
for _, nodePath := range nodePaths {
wg.Add(1)
go func(path appPath) {
err = runApp(path, &wg)
if err != nil {
log.With(zap.Error(err)).Error("running node failed with error")
}
}(nodePath)
}
for _, clientPath := range clientPaths {
wg.Add(1)
go func(path appPath) {
err = runApp(path, &wg)
if err != nil {
log.With(zap.Error(err)).Error("running node failed with error")
}
}(clientPath)
}
wg.Wait()
return
}
func buildAll(paths absolutePaths) (err error) {
err = build(paths.nodePkgPath, paths.nodeBinaryPath, paths.nodePkgName)
if err != nil {
err = fmt.Errorf("failed to build node: %w", err)
return
}
err = build(paths.clientPkgPath, paths.clientBinaryPath, paths.clientPkgName)
if err != nil {
err = fmt.Errorf("failed to build client: %w", err)
return
}
return
}
func build(dirPath, binaryPath, packageName string) (err error) {
cmd := exec.Command("go", "build", "-v", "-o", binaryPath, packageName)
cmd.Dir = dirPath
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Start()
if err != nil {
return
}
log.With(zap.String("binary path", binaryPath), zap.String("package name", packageName)).Info("building the app")
return cmd.Wait()
}
func runApp(app appPath, wg *sync.WaitGroup) (err error) {
cmd := exec.Command(app.binaryPath, "-c", app.configPath)
cmd.Dir = app.wdPath
file, err := os.OpenFile(app.logPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, os.ModePerm)
if err != nil {
return
}
defer file.Close()
cmd.Stdout = file
cmd.Stderr = file
err = cmd.Start()
log.With(zap.String("working directory", app.wdPath), zap.String("log path", app.logPath)).Info("running the app")
if err != nil {
return
}
err = cmd.Wait()
wg.Done()
return
}