Skip to content

Commit

Permalink
feat: refactor config handling + introduce services for start command
Browse files Browse the repository at this point in the history
  • Loading branch information
AzraelSec committed Sep 12, 2023
1 parent fff6b58 commit 55db0d2
Show file tree
Hide file tree
Showing 11 changed files with 305 additions and 141 deletions.
2 changes: 1 addition & 1 deletion cmd/cli/commands/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ func localInitFactory(cm *config.ConfigManager, g git.Git) *cobra.Command {
Use: "local",
Short: "Use a local configuration to clones the configured repos",
Run: func(cmd *cobra.Command, args []string) {
repos := cm.GetRepos()
repos := cm.Repos

initFn := func(cloneOps git.CloneOps) (struct{}, error) {
return struct{}{}, g.Clone(cloneOps)
Expand Down
2 changes: 1 addition & 1 deletion cmd/cli/commands/reset.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func resetFactory(cm *config.ConfigManager, g git.Git) *cobra.Command {
Use: "reset",
Short: "Reset the branch to its original base branch and pull changes from remote",
Run: func(cmd *cobra.Command, args []string) {
repos := cm.GetRepos()
repos := cm.Repos

if !routine.AllClean(repos, g) {
color.Red("Some of the repositories are not clean - it's not safe to switch")
Expand Down
32 changes: 20 additions & 12 deletions cmd/cli/commands/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,18 @@ var rootCmd = &cobra.Command{
}

func ExecuteRoot() {
cfPath, err := getConfigFilePath()
if err != nil {
color.Red("Impossible to identify a valid %s config file", CONFIG_FILE_NAME)
return
}

// TODO: Make this lazy so that it's possible to create commands
// that don't require the configuration file (remote init)
gm := external_git.NewExternalGit()
cm, err := loadConfigManager()

cm, err := loadConfigManager(cfPath)
if err != nil {
color.Red("Impossible to find a valid %s nearby configuration file", CONFIG_FILE_NAME)
color.Red("Impossible to find a valid %s nearby configuration file.\nDetails: %v", CONFIG_FILE_NAME, err)
return
}

Expand All @@ -47,21 +52,24 @@ func ExecuteRoot() {
rootCmd.Execute()
}

func loadConfigManager() (*config.ConfigManager, error) {
configPath := ""
wd, err := os.Getwd()
if err == nil {
configPath, _ = findNearestConfigFile(wd, MAX_CONFIG_FILE_DEPTH)
func getConfigFilePath() (string, error) {
if v, exists := os.LookupEnv(CONFIG_PATH_ENV); exists {
return v, nil
}

if v, exists := os.LookupEnv(CONFIG_PATH_ENV); exists {
configPath = v
wd, err := os.Getwd()
if err != nil {
return "", errors.New("impossible to find the current directory")
}

if configPath == "" {
return nil, errors.New("no configuration file found")
configPath, err := findNearestConfigFile(wd, MAX_CONFIG_FILE_DEPTH)
if err != nil {
return "", errors.New("no configuration file found")
}
return configPath, nil
}

func loadConfigManager(configPath string) (*config.ConfigManager, error) {
rawConfig, err := os.ReadFile(configPath)
if err != nil {
return nil, err
Expand Down
192 changes: 155 additions & 37 deletions cmd/cli/commands/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,65 @@ package commands

import (
"context"
"fmt"
"os"
"os/signal"
"path"
"slices"
"sync"
"time"

"github.com/AzraelSec/glock/internal/config"
"github.com/AzraelSec/glock/internal/log"
"github.com/AzraelSec/glock/internal/runner"
"github.com/AzraelSec/glock/pkg/dir"
"github.com/AzraelSec/glock/pkg/git"
"github.com/AzraelSec/glock/pkg/shell"
"github.com/AzraelSec/godotenv"
"github.com/fatih/color"
"github.com/spf13/cobra"
)

type startServicePayload struct {
tag string
cmd string
}

func serviceRun(ctx context.Context, g git.Git, configPath string, envFilenames []string, payload startServicePayload) (startOutputPayload, error) {
res := startOutputPayload{
Pid: -1,
RetCode: -1,
}

startProcess := shell.NewSyncShell(shell.ShellOps{
ExecPath: configPath,
Cmd: payload.cmd,
Ctx: ctx,
})

tsw := log.NewTagStreamWriter(payload.tag, os.Stdout)
rc, err := startProcess.Start(tsw)
if err != nil {
return res, err
}

res.RetCode = rc
res.Pid = startProcess.Pid

return res, nil
}

type startInputPayload struct {
gitPath string
startCmd string
stopCmd string
gitPath string
cmd string
name string
}

type startOutputPayload struct {
Pid int
RetCode int
}

func startRepo(ctx context.Context, g git.Git, payload startInputPayload) (startOutputPayload, error) {
func repoRun(ctx context.Context, g git.Git, envFilenames []string, payload startInputPayload) (startOutputPayload, error) {
res := startOutputPayload{
Pid: -1,
RetCode: -1,
Expand All @@ -36,65 +70,149 @@ func startRepo(ctx context.Context, g git.Git, payload startInputPayload) (start
return res, config.RepoNotFoundErr
}

denv, _ := godotenv.ReadFrom(payload.gitPath, false, envFilenames...)
startProcess := shell.NewSyncShell(shell.ShellOps{
ExecPath: payload.gitPath,
Cmd: payload.startCmd,
Cmd: payload.cmd,
Ctx: ctx,
Env: denv,
})
if rc, err := startProcess.Start(os.Stdout); shell.IgnoreInterrupt(err) != nil {
return res, fmt.Errorf("impossible to start => %v", err)
} else {
res.RetCode = rc
res.Pid = startProcess.Pid
}

if payload.stopCmd == "" {
return res, nil
tsw := log.NewTagStreamWriter(payload.name, os.Stdout)
rc, err := startProcess.Start(tsw)
if err != nil {
return res, err
}

stopProcess := shell.NewSyncShell(shell.ShellOps{
ExecPath: payload.gitPath,
Cmd: payload.stopCmd,
})
if _, err := stopProcess.Start(os.Stdout); err != nil {
return res, fmt.Errorf("impossible to end => %v", err)
}
res.RetCode = rc
res.Pid = startProcess.Pid

return res, nil
}

func startFactory(cm *config.ConfigManager, g git.Git) *cobra.Command {
return &cobra.Command{
var filteredRepos *[]string

cmd := &cobra.Command{
Use: "start",
Short: "Start the development stack 🚀",
Run: func(cmd *cobra.Command, args []string) {
repos := cm.GetRepos()
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill)
defer stop()
repos := []config.LiveRepo{}
if filteredRepos == nil || len(*filteredRepos) == 0 {
repos = cm.Repos
} else {
for _, repo := range cm.Repos {
if slices.Contains(*filteredRepos, repo.Name) {
repos = append(repos, repo)
}
}
}

if len(repos) == 0 {
color.Red("You cannot start your stack with no repos selected")
return
}

filteredRepos := []config.LiveRepo{}
executableRepo := []config.LiveRepo{}
disposableRepo := []config.LiveRepo{}
for _, repo := range repos {
if repo.Config.StartCmd == "" {
continue
if len(repo.Config.OnStart) != 0 {
executableRepo = append(executableRepo, repo)
}
if repo.Config.OnStop != "" {
disposableRepo = append(disposableRepo, repo)
}
}

executableService := []config.Services{}
disposableService := []config.Services{}

for _, srv := range cm.Services {
if srv.Cmd != "" {
executableService = append(executableService, srv)
}
if srv.Dispose != "" {
disposableService = append(disposableService, srv)
}
filteredRepos = append(filteredRepos, repo)
}

startArgs := make([]startInputPayload, 0, len(filteredRepos))
onStartCtx, stop := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill)
defer stop()
startServiceArgs := make([]startServicePayload, 0, len(executableService))
startServiceFn := func(payload startServicePayload) (startOutputPayload, error) {
return serviceRun(onStartCtx, g, path.Dir(cm.ConfigPath), cm.EnvFilenames, payload)
}
for _, srv := range executableService {
startServiceArgs = append(startServiceArgs, startServicePayload{tag: srv.Tag, cmd: srv.Cmd})
}

startArgs := make([]startInputPayload, 0, len(executableRepo))
startFn := func(args startInputPayload) (startOutputPayload, error) {
return startRepo(ctx, g, args)
return repoRun(onStartCtx, g, cm.EnvFilenames, args)
}
for _, repo := range executableRepo {
for _, startCmd := range repo.Config.OnStart {
startArgs = append(startArgs, startInputPayload{
cmd: startCmd,
gitPath: repo.GitConfig.Path,
name: repo.Name,
})
}
}

for _, repo := range filteredRepos {
startArgs = append(startArgs, startInputPayload{
startCmd: repo.Config.StartCmd,
stopCmd: repo.Config.StopCmd,
gitPath: repo.GitConfig.Path,
onServiceStopCtx, cancel := context.WithTimeout(context.Background(), time.Minute*10)
defer cancel()
stopServiceArgs := make([]startServicePayload, 0, len(disposableService))
stopServiceFn := func(payload startServicePayload) (startOutputPayload, error) {
return serviceRun(onServiceStopCtx, g, path.Dir(cm.ConfigPath), cm.EnvFilenames, payload)
}
for _, srv := range disposableService {
stopServiceArgs = append(stopServiceArgs, startServicePayload{tag: srv.Tag, cmd: srv.Dispose})
}
onStopCtx, cancel := context.WithTimeout(context.Background(), time.Minute*10)
defer cancel()

stopArgs := make([]startInputPayload, 0, len(disposableRepo))
stopFn := func(args startInputPayload) (startOutputPayload, error) {
return repoRun(onStopCtx, g, cm.EnvFilenames, args)
}
for _, repo := range executableRepo {
stopArgs = append(stopArgs, startInputPayload{
cmd: repo.Config.OnStop,
gitPath: repo.GitConfig.Path,
})
}

runner.Run(startFn, startArgs)
wg := sync.WaitGroup{}
wg.Add(2)
go func() {
runner.Run(startServiceFn, startServiceArgs)
wg.Done()
}()
// note: is there a way to get rid of this?
time.Sleep(5 * time.Second)
go func() {
runner.Run(startFn, startArgs)
wg.Done()
}()
wg.Wait()

wg.Add(2)
go func() {
runner.Run(stopFn, stopArgs)
wg.Done()
}()
go func() {
runner.Run(stopServiceFn, stopServiceArgs)
wg.Done()
}()
wg.Wait()

color.Green("Execution completed")
},
}

filteredRepos = cmd.Flags().StringArrayP("repos", "r", nil, "list of repository you want to start")

return cmd
}
2 changes: 1 addition & 1 deletion cmd/cli/commands/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ func statusFactory(cm *config.ConfigManager, g git.Git) *cobra.Command {
Use: "status",
Short: "Retrieves the current branch on each repo",
Run: func(cmd *cobra.Command, args []string) {
repos := cm.GetRepos()
repos := cm.Repos
lrn := 1

statusArgs := make([]git.Repo, 0, len(repos))
Expand Down
2 changes: 1 addition & 1 deletion cmd/cli/commands/switch.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func switchFactory(cm *config.ConfigManager, g git.Git) *cobra.Command {
Short: "Changes the branch to the given one - for all the repos that has it",
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
repos := cm.GetRepos()
repos := cm.Repos

if !*force && !routine.AllClean(repos, g) {
color.Red("Some of the repositories are not clean - it's not safe to switch")
Expand Down
2 changes: 1 addition & 1 deletion cmd/cli/commands/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ func updateFactory(cm *config.ConfigManager, g git.Git) *cobra.Command {
Use: "update",
Short: "Updates your repositories",
Run: func(cmd *cobra.Command, args []string) {
repos := cm.GetRepos()
repos := cm.Repos
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill)
defer stop()

Expand Down
Loading

0 comments on commit 55db0d2

Please # to comment.