diff --git a/.gitignore b/.gitignore index 95a793d21..c5f8e4c4a 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ coverage.txt .terraform .terraform.lock.hcl /cmd/terramate/terramate + +.vscode/ diff --git a/cmd/terramate/cli/cli.go b/cmd/terramate/cli/cli.go index 407e3e677..574f7cf37 100644 --- a/cmd/terramate/cli/cli.go +++ b/cmd/terramate/cli/cli.go @@ -22,6 +22,7 @@ import ( "os/exec" "path/filepath" "strings" + "time" "github.com/mineiros-io/terramate/dag" "github.com/mineiros-io/terramate/generate" @@ -36,6 +37,8 @@ import ( "github.com/mineiros-io/terramate/hcl" "github.com/mineiros-io/terramate/stack" "github.com/posener/complete" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" "github.com/willabides/kongplete" ) @@ -52,11 +55,18 @@ const ( defaultBranchBaseRef = "HEAD^" ) +const ( + defaultLogLevel = "none" + defaultLogFmt = "json" +) + type cliSpec struct { Version struct{} `cmd:"" help:"Terramate version."` Chdir string `short:"C" optional:"true" help:"sets working directory."` GitChangeBase string `short:"B" optional:"true" help:"git base ref for computing changes."` Changed bool `short:"c" optional:"true" help:"filter by changed infrastructure"` + LogLevel string `optional:"true" help:"sets the log level. Possible values are: 'trace', 'debug', 'none'. The default is 'none'."` + LogFmt string `optional:"true" help:"sets the log format. Possible values are: 'json', 'text'. The default is 'text'."` Run struct { Quiet bool `short:"q" help:"Don't print any information other than the command output."` @@ -192,6 +202,16 @@ func newCLI( return nil, fmt.Errorf("failed to parse cli args %v: %v", args, err) } + logLevel := parsedArgs.LogLevel + logFmt := parsedArgs.LogFmt + + if err := configureLogging(logLevel, logFmt, stderr); err != nil { + return nil, err + } + + log.Trace(). + Str("action", "newCli()"). + Msg("Get working directory.") wd := parsedArgs.Chdir if wd == "" { wd, err = os.Getwd() @@ -200,21 +220,34 @@ func newCLI( } } + logger := log.With(). + Str("action", "newCli()"). + Str("stack", wd). + Logger() + + logger.Trace(). + Msgf("Evaluate symbolic links for %q.", wd) wd, err = filepath.EvalSymlinks(wd) if err != nil { return nil, fmt.Errorf("failed evaluating symlinks for %q: %w", wd, err) } + logger.Trace(). + Msgf("Get absolute file path of %q.", wd) wd, err = filepath.Abs(wd) if err != nil { return nil, fmt.Errorf("getting absolute path of %q: %w", wd, err) } + logger.Trace(). + Msgf("Change working directory to %q.", wd) err = os.Chdir(wd) if err != nil { return nil, fmt.Errorf("failed to change working directory to %q: %w", wd, err) } + logger.Trace(). + Msgf("Look up project in %q.", wd) prj, foundRoot, err := lookupProject(wd) if err != nil { return nil, fmt.Errorf("failed to lookup project root from %q: %w", wd, err) @@ -224,6 +257,8 @@ func newCLI( return nil, fmt.Errorf("project root not found") } + logger.Trace(). + Msg("Set defaults from parsed command line arguments.") err = prj.setDefaults(&parsedArgs) if err != nil { return nil, fmt.Errorf("setting configuration: %w", err) @@ -250,46 +285,100 @@ func (c *cli) run() error { return nil } + logger := log.With(). + Str("action", "run()"). + Str("stack", c.wd()). + Logger() + if c.parsedArgs.Changed { + logger.Trace(). + Msg("`Changed` flag was set.") + + logger.Trace(). + Msg("Create new git wrapper.") git, err := newGit(c.root(), c.inheritEnv, true) if err != nil { return err } + + logger.Trace(). + Msg("Check git default remote.") if err := c.checkDefaultRemote(git); err != nil { return err } + + logger.Trace(). + Msg("Check git default branch was updated.") if err := c.checkLocalDefaultIsUpdated(git); err != nil { return err } } + logger.Debug(). + Msg("Handle input command.") switch c.ctx.Command() { case "version": + logger.Debug(). + Msg("Get terramate version.") c.log(terramate.Version()) case "plan graph": + log.Trace(). + Str("actionContext", "cli()"). + Str("stack", c.wd()). + Msg("Handle `plan graph`.") return c.generateGraph() case "plan run-order": + log.Trace(). + Str("actionContext", "cli()"). + Str("stack", c.wd()). + Msg("Print run-order.") return c.printRunOrder() case "stacks init": + log.Trace(). + Str("actionContext", "cli()"). + Str("stack", c.wd()). + Msg("Handle stacks init command.") return c.initStack([]string{c.wd()}) case "stacks list": + log.Trace(). + Str("actionContext", "cli()"). + Str("stack", c.wd()). + Msg("Print list of stacks.") return c.printStacks() case "stacks init ": + log.Trace(). + Str("actionContext", "cli()"). + Str("stack", c.wd()). + Msg("Handle stacks init command.") return c.initStack(c.parsedArgs.Stacks.Init.StackDirs) case "stacks globals": + log.Trace(). + Str("actionContext", "cli()"). + Str("stack", c.wd()). + Msg("Handle stacks global command.") return c.printStacksGlobals() case "run": + logger.Debug(). + Msg("Handle `run` command.") if len(c.parsedArgs.Run.Command) == 0 { return errors.New("no command specified") } fallthrough case "run ": + logger.Debug(). + Msg("Handle `run ` command.") return c.runOnStacks() case "generate": + logger.Debug(). + Msg("Handle `generate` command.") return generate.Do(c.root()) case "metadata": + logger.Debug(). + Msg("Handle `metadata` command.") return c.printMetadata() case "install-completions": + logger.Debug(). + Msg("Handle `install-completions` command.") return c.parsedArgs.InstallCompletions.Run(c.ctx) default: return fmt.Errorf("unexpected command sequence: %s", c.ctx.Command()) @@ -300,11 +389,25 @@ func (c *cli) run() error { func (c *cli) initStack(dirs []string) error { var errmsgs []string + + logger := log.With(). + Str("action", "initStack()"). + Logger() + + logger.Debug(). + Msg("Init stacks.") for _, d := range dirs { if !filepath.IsAbs(d) { + log.Trace(). + Str("stack", fmt.Sprintf("%s%s", c.wd(), strings.Trim(d, "."))). + Msg("Make file path absolute.") d = filepath.Join(c.wd(), d) } + log.Debug(). + Str("stack", fmt.Sprintf("%s%s", c.wd(), strings.Trim(d, "."))). + Msg("Init stack.") + err := terramate.Init(c.root(), d, c.parsedArgs.Stacks.Init.Force) if err != nil { c.logerr("warn: failed to initialize stack: %v", err) @@ -321,19 +424,36 @@ func (c *cli) initStack(dirs []string) error { func (c *cli) listStacks(mgr *terramate.Manager, isChanged bool) ([]terramate.Entry, error) { if isChanged { + log.Trace(). + Str("action", "listStacks()"). + Str("stack", c.wd()). + Msg("`Changed` flag was set. List changed stacks.") return mgr.ListChanged() } - return mgr.List() } func (c *cli) printStacks() error { + logger := log.With(). + Str("action", "printStacks()"). + Logger() + + logger.Trace(). + Str("stack", c.wd()). + Msg("Create a new stack manager.") mgr := terramate.NewManager(c.root(), c.prj.baseRef) + + logger.Trace(). + Str("stack", c.wd()). + Msg("Get stack list.") entries, err := c.listStacks(mgr, c.parsedArgs.Changed) if err != nil { return err } + logger.Trace(). + Str("stack", c.wd()). + Msg("Print stacks.") for _, entry := range entries { stack := entry.Stack stackRepr, ok := c.friendlyFmtDir(stack.Dir) @@ -341,6 +461,10 @@ func (c *cli) printStacks() error { continue } + logger.Debug(). + Str("stack", c.wd()+stack.Dir). + Msg("Print stack.") + if c.parsedArgs.Stacks.List.Why { c.log("%s - %s", stackRepr, entry.Reason) } else { @@ -353,10 +477,21 @@ func (c *cli) printStacks() error { func (c *cli) generateGraph() error { var getLabel func(s stack.S) string + logger := log.With(). + Str("action", "generateGraph()"). + Str("stack", c.wd()). + Logger() + + logger.Trace(). + Msg("Handle graph label command line argument.") switch c.parsedArgs.Plan.Graph.Label { case "stack.name": + logger.Debug(). + Msg("Set label to stack name.") getLabel = func(s stack.S) string { return s.Name() } case "stack.dir": + logger.Debug(). + Msg("Set label stack directory.") getLabel = func(s stack.S) string { return s.Dir } default: return fmt.Errorf("-label expects the values \"stack.name\" or \"stack.dir\"") @@ -366,6 +501,8 @@ func (c *cli) generateGraph() error { return err } + logger.Debug(). + Msg("Create new graph.") loader := stack.NewLoader(c.root()) dotGraph := dot.NewGraph(dot.Directed) graph := dag.New() @@ -394,11 +531,17 @@ func (c *cli) generateGraph() error { } } + logger.Debug(). + Msg("Set output of graph.") outFile := c.parsedArgs.Plan.Graph.Outfile var out io.Writer if outFile == "" { + logger.Trace(). + Msg("Set output to stdout.") out = c.stdout } else { + logger.Trace(). + Msg("Set output to file.") f, err := os.Create(outFile) if err != nil { return fmt.Errorf("opening file %q: %w", outFile, err) @@ -409,6 +552,8 @@ func (c *cli) generateGraph() error { out = f } + logger.Debug(). + Msg("Write graph to output.") _, err = out.Write([]byte(dotGraph.String())) if err != nil { return fmt.Errorf("writing output to %q: %w", outFile, err) @@ -455,18 +600,35 @@ func generateDot( } func (c *cli) printRunOrder() error { + logger := log.With(). + Str("action", "printRunOrder()"). + Str("stack", c.wd()). + Logger() + + logger.Trace(). + Msg("Create new terramate manager.") mgr := terramate.NewManager(c.root(), c.prj.baseRef) + + logger.Trace(). + Msg("Get list of stacks.") entries, err := c.listStacks(mgr, c.parsedArgs.Changed) if err != nil { return err } + logger.Trace(). + Msg("Filter stacks by working directory.") entries = c.filterStacksByWorkingDir(entries) + + logger.Trace(). + Msg("Create stack array.") stacks := make([]stack.S, len(entries)) for i, e := range entries { stacks[i] = e.Stack } + logger.Debug(). + Msg("Get run order.") order, reason, err := terramate.RunOrder(c.root(), stacks, c.parsedArgs.Changed) if err != nil { if errors.Is(err, dag.ErrCycleDetected) { @@ -513,14 +675,27 @@ func (c *cli) printStacksGlobals() error { } func (c *cli) printMetadata() error { + logger := log.With(). + Str("action", "printMetadata()"). + Logger() + + logger.Trace(). + Str("stack", c.wd()). + Msg("Load metadata.") metadata, err := terramate.LoadMetadata(c.root()) if err != nil { return err } + logger.Trace(). + Str("stack", c.wd()). + Msg("Log metadata.") c.log("Available metadata:") for _, stack := range metadata.Stacks { + logger.Debug(). + Str("stack", c.wd()+stack.Path). + Msg("Print metadata for individual stack.") c.log("\nstack %q:", stack.Path) c.log("\tterramate.name=%q", stack.Name) c.log("\tterramate.path=%q", stack.Path) @@ -530,7 +705,17 @@ func (c *cli) printMetadata() error { } func (c *cli) runOnStacks() error { + logger := log.With(). + Str("action", "runOnStacks()"). + Str("stack", c.wd()). + Logger() + + logger.Trace(). + Msg("Create new terramate manager.") mgr := terramate.NewManager(c.root(), c.prj.baseRef) + + logger.Trace(). + Msg("Get list of stacks.") entries, err := c.listStacks(mgr, c.parsedArgs.Changed) if err != nil { return err @@ -542,12 +727,19 @@ func (c *cli) runOnStacks() error { c.log("Running on all stacks:") } + logger.Trace(). + Msg("Filter stacks by working directory.") entries = c.filterStacksByWorkingDir(entries) + + logger.Trace(). + Msg("Create array of stacks.") stacks := make([]stack.S, len(entries)) for i, e := range entries { stacks[i] = e.Stack } + logger.Trace(). + Msg("Get command to run.") cmdName := c.parsedArgs.Run.Command[0] args := c.parsedArgs.Run.Command[1:] cmd := exec.Command(cmdName, args...) @@ -555,6 +747,8 @@ func (c *cli) runOnStacks() error { cmd.Stdout = c.stdout cmd.Stderr = c.stderr + logger.Trace(). + Msg("Get order of stacks to run command on.") order, reason, err := terramate.RunOrder(c.root(), stacks, c.parsedArgs.Changed) if err != nil { if errors.Is(err, dag.ErrCycleDetected) { @@ -565,6 +759,8 @@ func (c *cli) runOnStacks() error { } if c.parsedArgs.Run.DryRun { + logger.Trace(). + Msg("Do a dry run - get order without actually running command.") if len(order) > 0 { c.log("The stacks will be executed using order below:") @@ -579,6 +775,8 @@ func (c *cli) runOnStacks() error { return nil } + logger.Debug(). + Msg("Run command.") err = terramate.Run(c.root(), order, cmd) if err != nil { c.logerr("warn: failed to execute command: %v", err) @@ -599,6 +797,13 @@ func (c *cli) logerr(format string, args ...interface{}) { } func (c *cli) checkDefaultRemote(g *git.Git) error { + logger := log.With(). + Str("action", "checkDefaultRemote()"). + Str("stack", c.wd()). + Logger() + + logger.Trace(). + Msg("Get list of configured git remotes.") remotes, err := g.Remotes() if err != nil { return fmt.Errorf("checking if remote %q exists: %v", defaultRemote, err) @@ -606,6 +811,8 @@ func (c *cli) checkDefaultRemote(g *git.Git) error { var defRemote *git.Remote + logger.Trace(). + Msg("Find default git remote.") for _, remote := range remotes { if remote.Name == defaultRemote { defRemote = &remote @@ -621,6 +828,8 @@ func (c *cli) checkDefaultRemote(g *git.Git) error { ) } + logger.Trace(). + Msg("Find default git branch.") for _, branch := range defRemote.Branches { if branch == defaultBranch { return nil @@ -637,6 +846,13 @@ func (c *cli) checkDefaultRemote(g *git.Git) error { } func (c *cli) checkLocalDefaultIsUpdated(g *git.Git) error { + logger := log.With(). + Str("action", "checkLocalDefaultIsUpdated()"). + Str("stack", c.wd()). + Logger() + + logger.Trace(). + Msg("Get current git branch.") branch, err := g.CurrentBranch() if err != nil { return fmt.Errorf("checking local branch is updated: %v", err) @@ -649,12 +865,16 @@ func (c *cli) checkLocalDefaultIsUpdated(g *git.Git) error { c.logerr("current branch %q is the default branch, checking if it is updated.", branch) c.logerr("retrieving info from remote branch: %s/%s ...", defaultRemote, defaultBranch) + logger.Trace(). + Msg("Fetch remote reference.") remoteRef, err := g.FetchRemoteRev(defaultRemote, defaultBranch) if err != nil { return fmt.Errorf("checking local branch %q is update: %v", branch, err) } c.logerr("retrieved info from remote branch: %s/%s.", defaultRemote, defaultBranch) + logger.Trace(). + Msg("Get local commit ID.") localCommitID, err := g.RevParse(branch) if err != nil { return fmt.Errorf("checking local branch %q is update: %v", branch, err) @@ -683,8 +903,17 @@ func (c *cli) friendlyFmtDir(dir string) (string, bool) { } func (c *cli) filterStacksByWorkingDir(stacks []terramate.Entry) []terramate.Entry { + logger := log.With(). + Str("action", "filterStacksByWorkingDir()"). + Str("stack", c.wd()). + Logger() + + logger.Trace(). + Msg("Get relative working directory.") relwd := prj.RelPath(c.root(), c.wd()) + logger.Trace(). + Msg("Get filtered stacks.") filtered := []terramate.Entry{} for _, e := range stacks { if strings.HasPrefix(e.Stack.Dir, relwd) { @@ -696,6 +925,9 @@ func (c *cli) filterStacksByWorkingDir(stacks []terramate.Entry) []terramate.Ent } func newGit(basedir string, inheritEnv bool, checkrepo bool) (*git.Git, error) { + log.Debug(). + Str("action", "newGit()"). + Msg("Create new git wrapper providing config.") g, err := git.WithConfig(git.Config{ WorkingDir: basedir, InheritEnv: inheritEnv, @@ -716,15 +948,29 @@ func lookupProject(wd string) (prj project, found bool, err error) { prj = project{ wd: wd, } + + logger := log.With(). + Str("action", "lookupProject()"). + Str("stack", wd). + Logger() + + logger.Trace(). + Msg("Create new git wrapper.") gw, err := newGit(wd, false, false) if err == nil { + logger.Trace(). + Msg("Get root of git repo.") gitdir, err := gw.Root() if err == nil { + logger.Trace(). + Msg("Get absolute path of git directory.") gitabs, err := filepath.Abs(gitdir) if err != nil { return project{}, false, fmt.Errorf("getting absolute path of %q: %w", gitdir, err) } + logger.Trace(). + Msg("Evaluate symbolic links.") gitabs, err = filepath.EvalSymlinks(gitabs) if err != nil { return project{}, false, fmt.Errorf("failed evaluating symlinks of %q: %w", @@ -732,6 +978,9 @@ func lookupProject(wd string) (prj project, found bool, err error) { } root := filepath.Dir(gitabs) + + logger.Trace(). + Msg("Load root config.") cfg, _, err := config.TryLoadRootConfig(root) if err != nil { return project{}, false, err @@ -748,6 +997,8 @@ func lookupProject(wd string) (prj project, found bool, err error) { dir := wd for { + logger.Trace(). + Msg("Load root config.") cfg, ok, err := config.TryLoadRootConfig(dir) if err != nil { return project{}, false, err @@ -771,17 +1022,27 @@ func lookupProject(wd string) (prj project, found bool, err error) { } func (p *project) setDefaults(parsedArgs *cliSpec) error { + logger := log.With(). + Str("action", "setDefaults()"). + Str("stack", p.wd). + Logger() + if p.rootcfg.Terramate == nil { // if config has no terramate block we create one with default // configurations. + logger.Trace(). + Str("configFile", p.root+"/terramate.tm.hcl"). + Msg("Create terramate block.") p.rootcfg.Terramate = &hcl.Terramate{} } + logger.Debug(). + Str("configFile", p.root+"/terramate.tm.hcl"). + Msg("Set defaults.") cfg := &p.rootcfg if cfg.Terramate.RootConfig == nil { p.rootcfg.Terramate.RootConfig = &hcl.RootConfig{} } - gitOpt := &cfg.Terramate.RootConfig.Git if gitOpt.BaseRef == "" { @@ -804,11 +1065,17 @@ func (p *project) setDefaults(parsedArgs *cliSpec) error { if baseRef == "" { baseRef = gitOpt.BaseRef if p.isRepo { + logger.Trace(). + Str("configFile", p.root+"/terramate.tm.hcl"). + Msg("Create new git wrapper.") gw, err := newGit(p.wd, false, false) if err != nil { return err } + logger.Trace(). + Str("configFile", p.root+"/terramate.tm.hcl"). + Msg("Get current branch.") branch, err := gw.CurrentBranch() if err != nil { return fmt.Errorf("failed to get current git branch: %v", err) @@ -824,3 +1091,68 @@ func (p *project) setDefaults(parsedArgs *cliSpec) error { return nil } + +func configureLogging(logLevel string, logFmt string, output io.Writer) error { + + if logLevel == "" { + logLevel = defaultLogLevel + } + + if logFmt == "" { + logFmt = defaultLogFmt + } + + zloglevel, err := getzlogLevel(logLevel) + if err != nil { + return err + } + + logwriter, err := getzlogWriter(logFmt, output) + if err != nil { + return err + } + + zerolog.SetGlobalLevel(zloglevel) + log.Logger = zerolog.New(logwriter).With().Timestamp().Caller().Logger() + return nil +} + +func lookupEnv(name, def string) string { + if v, ok := os.LookupEnv(name); ok { + return v + } + return def +} + +func getzlogLevel(level string) (zerolog.Level, error) { + switch level { + case "trace": + return zerolog.TraceLevel, nil + case "debug": + return zerolog.DebugLevel, nil + case "info": + return zerolog.InfoLevel, nil + case "warn": + return zerolog.WarnLevel, nil + case "error": + return zerolog.ErrorLevel, nil + case "none": + return zerolog.Disabled, nil + default: + return zerolog.NoLevel, fmt.Errorf("unknown log level %q", level) + } +} + +func getzlogWriter(format string, output io.Writer) (io.Writer, error) { + switch format { + case "text": + color := lookupEnv("TM_LOG_COLOR", "ON") + return zerolog.ConsoleWriter{Out: output, NoColor: color != "ON", TimeFormat: time.RFC3339}, nil + case "json": + // Default is JSON on zlog + zerolog.TimeFieldFormat = zerolog.TimeFormatUnix + return output, nil + default: + return nil, fmt.Errorf("unknown log format %q", format) + } +} diff --git a/config/config.go b/config/config.go index 75626707c..49d5bef6f 100644 --- a/config/config.go +++ b/config/config.go @@ -19,6 +19,7 @@ import ( "path/filepath" "github.com/mineiros-io/terramate/hcl" + "github.com/rs/zerolog/log" ) const ( @@ -31,14 +32,26 @@ const ( // Exists tells if path has a terramate config file. func Exists(path string) bool { + logger := log.With(). + Str("action", "Exists()"). + Str("stack", path). + Logger() + + logger.Trace(). + Msg("Get path info.") st, err := os.Stat(path) if err != nil { return false } + + logger.Trace(). + Msg("Check if path is directory.") if !st.IsDir() { return false } + logger.Trace(). + Msg("Look for config file within directory.") fname := filepath.Join(path, Filename) info, err := os.Stat(fname) if err != nil { @@ -49,6 +62,14 @@ func Exists(path string) bool { } func TryLoadRootConfig(dir string) (cfg hcl.Config, found bool, err error) { + logger := log.With(). + Str("action", "TryLoadRootConfig()"). + Str("stack", dir). + Str("configFile", dir+Filename). + Logger() + + logger.Trace(). + Msg("Check if file exists.") path := filepath.Join(dir, Filename) _, err = os.Stat(path) if err != nil { @@ -59,6 +80,8 @@ func TryLoadRootConfig(dir string) (cfg hcl.Config, found bool, err error) { return hcl.Config{}, false, err } + logger.Trace(). + Msg("Parse file.") cfg, err = hcl.ParseFile(path) if err != nil { return hcl.Config{}, false, err diff --git a/git/git.go b/git/git.go index 569659a58..8195490e0 100644 --- a/git/git.go +++ b/git/git.go @@ -21,6 +21,8 @@ import ( "os/exec" "sort" "strings" + + "github.com/rs/zerolog/log" ) type ( @@ -112,6 +114,9 @@ func NewConfig(username, email string) Config { // NewConfigWithPath calls NewConfig but also sets the git program path. func NewConfigWithPath(username, email, programPath string) Config { + log.Debug(). + Str("action", "NewConfigWithPath()"). + Msg("Make new config.") config := NewConfig(username, email) config.ProgramPath = programPath return config @@ -124,28 +129,51 @@ func NewWrapper(user, email string) (*Git, error) { // WithConfig creates a new git wrapper by providing the config. func WithConfig(cfg Config) (*Git, error) { + logger := log.With(). + Str("action", "WithConfig()"). + Str("stack", cfg.WorkingDir). + Logger() + + logger.Trace(). + Msg("Construct new config.") git := &Git{ config: cfg, } + logger.Trace(). + Msg("Apply defaults.") err := git.applyDefaults() if err != nil { return nil, fmt.Errorf("applying default config values: %w", err) } + logger.Trace(). + Msg("Validate git config.") err = git.validate() if err != nil { return nil, err } + logger.Trace(). + Msg("Get git version.") _, err = git.Version() return git, err } func (git *Git) applyDefaults() error { + logger := log.With(). + Str("action", "applyDefaults()"). + Str("stack", git.config.WorkingDir). + Logger() + cfg := &git.config if cfg.ProgramPath == "" { + logger.Trace(). + Msg("Config program path was null.") + + logger.Trace(). + Msg("Look for path 'git'.") programPath, err := exec.LookPath("git") if err != nil { return fmt.Errorf("%w: %v", ErrGitNotFound, err) @@ -155,6 +183,11 @@ func (git *Git) applyDefaults() error { } if cfg.WorkingDir == "" { + logger.Trace(). + Msg("Working directory was null.") + + logger.Trace(). + Msg("Get working directory.") wd, err := os.Getwd() if err != nil { return fmt.Errorf("failed to get working directory: %w", err) @@ -164,10 +197,14 @@ func (git *Git) applyDefaults() error { } if cfg.DefaultBranch == "" { + logger.Trace(). + Msg("Default branch was null. Set default to 'main'.") cfg.DefaultBranch = "main" } if cfg.DefaultRemote == "" { + logger.Trace(). + Msg("Default remote was null. Set default to 'origin'.") cfg.DefaultRemote = "origin" } @@ -177,6 +214,14 @@ func (git *Git) applyDefaults() error { func (git *Git) validate() error { cfg := git.config + logger := log.With(). + Str("action", "validate()"). + Str("stack", git.config.WorkingDir). + Logger() + + logger.Trace(). + Str("path", cfg.ProgramPath). + Msg("Get path program path information.") _, err := os.Stat(cfg.ProgramPath) if err != nil { return fmt.Errorf("failed to stat git program path \"%s\": %w: %v", @@ -192,6 +237,13 @@ func (git *Git) validate() error { // Version of the git program. func (git *Git) Version() (string, error) { + logger := log.With(). + Str("action", "Version()"). + Str("stack", git.config.WorkingDir). + Logger() + + logger.Debug(). + Msg("Get git version.") out, err := git.exec("version") if err != nil { return "", err @@ -212,6 +264,11 @@ func (git *Git) Version() (string, error) { // store revisions. // Beware: Init is a porcelain method. func (git *Git) Init(dir string, bare bool) error { + logger := log.With(). + Str("action", "Init()"). + Str("stack", git.config.WorkingDir). + Logger() + if !git.config.AllowPorcelain { return fmt.Errorf("Init: %w", ErrDenyPorcelain) } @@ -224,6 +281,8 @@ func (git *Git) Init(dir string, bare bool) error { args = append(args, "--bare") } + logger.Trace(). + Msg("Append arguments to init command and execute.") args = append(args, dir) _, err := git.exec("init", args...) if err != nil { @@ -239,6 +298,8 @@ func (git *Git) Init(dir string, bare bool) error { git.config.WorkingDir = dir if git.config.Username != "" { + logger.Trace(). + Msg("Username was set. Configure username.") _, err = git.exec("config", "--local", "user.name", git.config.Username) if err != nil { return err @@ -246,6 +307,8 @@ func (git *Git) Init(dir string, bare bool) error { } if git.config.Email != "" { + logger.Trace(). + Msg("Email was set. Configure email.") _, err = git.exec("config", "--local", "user.email", git.config.Email) if err != nil { return err @@ -268,6 +331,13 @@ func (git *Git) RemoteAdd(name string, url string) error { func (git *Git) Remotes() ([]Remote, error) { const refprefix = "refs/remotes/" + logger := log.With(). + Str("action", "Remotes()"). + Str("stack", git.config.WorkingDir). + Logger() + + logger.Trace(). + Msg("Get all remote references.") res, err := git.exec("for-each-ref", "--format", "%(refname)", refprefix) if err != nil { @@ -280,7 +350,12 @@ func (git *Git) Remotes() ([]Remote, error) { references := map[string][]string{} + logger.Trace(). + Msg("Range over references.") for _, rawref := range strings.Split(res, "\n") { + logger.Trace(). + Str("reference", rawref). + Msg("Format reference.") trimmedref := strings.TrimPrefix(rawref, refprefix) parsed := strings.Split(trimmedref, "/") if len(parsed) < 2 { @@ -289,15 +364,22 @@ func (git *Git) Remotes() ([]Remote, error) { name := parsed[0] branch := strings.Join(parsed[1:], "/") branches := references[name] + logger.Trace(). + Str("reference", branch). + Msg("Append formatted reference to branches array.") references[name] = append(branches, branch) } var remotes remoteSorter + logger.Trace(). + Msg("Create remotes array.") for name, branches := range references { remotes = append(remotes, Remote{Name: name, Branches: branches}) } + logger.Trace(). + Msg("Sort remotes.") sort.Stable(remotes) return remotes, nil } @@ -308,13 +390,24 @@ func (git *Git) Remotes() ([]Remote, error) { // // It returns only the first line of the commit message. func (git *Git) LogSummary(revs ...string) ([]LogLine, error) { + logger := log.With(). + Str("action", "LogSummary()"). + Str("stack", git.config.WorkingDir). + Logger() + if len(revs) == 0 { + logger.Trace(). + Msg("Length of revisions was 0. Append 'HEAD'.") revs = append(revs, "HEAD") } + logger.Trace(). + Msg("Append `-pretty-oneline` argument.") args := append([]string{}, "--pretty=oneline") args = append(args, revs...) + logger.Trace(). + Msg("Get list of revs.") out, err := git.exec("rev-list", args...) if err != nil { return nil, err @@ -323,6 +416,9 @@ func (git *Git) LogSummary(revs ...string) ([]LogLine, error) { logs := []LogLine{} lines := strings.Split(out, "\n") + + logger.Trace(). + Msg("Range over revs.") for _, line := range lines { l := strings.TrimSpace(line) if len(l) == 0 { @@ -334,6 +430,8 @@ func (git *Git) LogSummary(revs ...string) ([]LogLine, error) { return nil, fmt.Errorf("malformed log line") } + logger.Trace(). + Msg("Append rev to logs.") logs = append(logs, LogLine{ CommitID: l[0:index], Message: l[index+1:], @@ -349,6 +447,11 @@ func (git *Git) Add(files ...string) error { if !git.config.AllowPorcelain { return fmt.Errorf("Add: %w", ErrDenyPorcelain) } + + log.Debug(). + Str("action", "Add()"). + Str("stack", git.config.WorkingDir). + Msg("Add file to current staged index.") _, err := git.exec("add", files...) return err } @@ -357,10 +460,17 @@ func (git *Git) Add(files ...string) error { // The args are extra flags and/or arguments to git commit command line. // Beware: Commit is a porcelain method. func (git *Git) Commit(msg string, args ...string) error { + logger := log.With(). + Str("action", "Commit()"). + Str("stack", git.config.WorkingDir). + Logger() + if !git.config.AllowPorcelain { return fmt.Errorf("Commit: %w", ErrDenyPorcelain) } + logger.Trace(). + Msg("Range args.") for _, arg := range args { if arg == "-m" { return fmt.Errorf("the -m argument is already implicitly set") @@ -373,6 +483,8 @@ func (git *Git) Commit(msg string, args ...string) error { vargs = append(vargs, args...) + logger.Debug(). + Msg("Commit with args.") _, err := git.exec("commit", vargs...) return err } @@ -388,6 +500,13 @@ func (git *Git) RevParse(rev string) (string, error) { // for the given remote and reference. This will make use of the network // to fetch data from the remote configured on the git repo. func (git *Git) FetchRemoteRev(remote, ref string) (Ref, error) { + logger := log.With(). + Str("action", "FetchRemoteRev()"). + Str("stack", git.config.WorkingDir). + Logger() + + logger.Debug(). + Msg("List references in remote repository.") output, err := git.exec("ls-remote", remote, ref) if err != nil { return Ref{}, fmt.Errorf( @@ -432,17 +551,28 @@ func (git *Git) Status() (string, error) { // recurse is set, then it walks into child trees as well. If // relative is set, then only show local changes of current dir. func (git *Git) DiffTree(from, to string, relative, nameOnly, recurse bool) (string, error) { + logger := log.With(). + Str("action", "DiffTree()"). + Str("stack", git.config.WorkingDir). + Logger() + args := []string{from, to} if relative { + logger.Trace(). + Msg("Set `--relative` command line arg.") args = append(args, "--relative") } if nameOnly { + logger.Trace(). + Msg("Set `--name-only` command line arg.") args = append(args, "--name-only") } if recurse { + logger.Trace(). + Msg("Set `--r` command line arg.") args = append(args, "-r") // git help shows no long flag name } @@ -453,6 +583,11 @@ func (git *Git) DiffTree(from, to string, relative, nameOnly, recurse bool) (str // commit ids differences and return all the file names containing differences // relative to configuration WorkingDir. func (git *Git) DiffNames(from, to string) ([]string, error) { + log.Trace(). + Str("action", "DiffNames()"). + Str("stack", git.config.WorkingDir). + Str("reference", fmt.Sprintf("from `%s` to `%s`", from, to)). + Msg("Get tree differences.") diff, err := git.DiffTree(from, to, true, true, true) if err != nil { return nil, fmt.Errorf("diff-tree: %w", err) @@ -463,20 +598,42 @@ func (git *Git) DiffNames(from, to string) ([]string, error) { // NewBranch creates a new branch reference pointing to current HEAD. func (git *Git) NewBranch(name string) error { + log.Trace(). + Str("action", "NewBranch()"). + Str("stack", git.config.WorkingDir). + Str("reference", name). + Msg("Get commit ID.") _, err := git.RevParse(name) if err == nil { return fmt.Errorf("branch \"%s\" already exists", name) } + + log.Debug(). + Str("action", "NewBranch()"). + Str("stack", git.config.WorkingDir). + Str("reference", name). + Msg("Create new branch.") _, err = git.exec("update-ref", "refs/heads/"+name, "HEAD") return err } // DeleteBranch deletes the branch. func (git *Git) DeleteBranch(name string) error { + log.Trace(). + Str("action", "DeleteBranch()"). + Str("stack", git.config.WorkingDir). + Str("reference", name). + Msg("Get commit ID.") _, err := git.RevParse(name) if err != nil { return fmt.Errorf("branch \"%s\" doesn't exist", name) } + + log.Debug(). + Str("action", "DeleteBranch()"). + Str("stack", git.config.WorkingDir). + Str("reference", name). + Msg("Delete branch.") _, err = git.exec("update-ref", "-d", "refs/heads/"+name) return err } @@ -487,16 +644,26 @@ func (git *Git) DeleteBranch(name string) error { // Beware: Checkout is a porcelain method. func (git *Git) Checkout(rev string, create bool) error { if !git.config.AllowPorcelain { - return fmt.Errorf("Checkout: %w", ErrDenyPorcelain) + return fmt.Errorf("Checkout: %w.", ErrDenyPorcelain) } if create { + log.Trace(). + Str("action", "Checkout()"). + Str("stack", git.config.WorkingDir). + Str("reference", rev). + Msg("Create new branch.") err := git.NewBranch(rev) if err != nil { return err } } + log.Debug(). + Str("action", "Checkout()"). + Str("stack", git.config.WorkingDir). + Str("reference", rev). + Msg("Checkout.") _, err := git.exec("checkout", rev) return err } @@ -509,6 +676,11 @@ func (git *Git) Merge(branch string) error { return fmt.Errorf("Merge: %w", ErrDenyPorcelain) } + log.Debug(). + Str("action", "Merge()"). + Str("stack", git.config.WorkingDir). + Str("reference", branch). + Msg("Merge.") _, err := git.exec("merge", "--no-ff", branch) return err } @@ -519,6 +691,11 @@ func (git *Git) Push(remote, branch string) error { return fmt.Errorf("Push: %w", ErrDenyPorcelain) } + log.Debug(). + Str("action", "Push()"). + Str("stack", git.config.WorkingDir). + Str("reference", fmt.Sprintf("from `%s` to `%s`", branch, remote)). + Msg("Git push.") _, err := git.exec("push", remote, branch) return err } @@ -529,6 +706,11 @@ func (git *Git) Pull(remote, branch string) error { return fmt.Errorf("Pull: %w", ErrDenyPorcelain) } + log.Debug(). + Str("action", "Pull()"). + Str("stack", git.config.WorkingDir). + Str("reference", fmt.Sprintf("from `%s` to `%s`", remote, branch)). + Msg("Git pull.") _, err := git.exec("pull", remote, branch) return err } @@ -541,6 +723,11 @@ func (git *Git) FFMerge(branch string) error { return fmt.Errorf("FFMerge: %w", ErrDenyPorcelain) } + log.Debug(). + Str("action", "FFMerge()"). + Str("stack", git.config.WorkingDir). + Str("reference", branch). + Msg("Fast forward merge branch.") _, err := git.exec("merge", "--ff", branch) return err } @@ -556,6 +743,10 @@ func (git *Git) ListUntracked(dirs ...string) ([]string, error) { args = append(args, dirs...) } + log.Debug(). + Str("action", "ListUntracked()"). + Str("stack", git.config.WorkingDir). + Msg("List untracked files.") out, err := git.exec("ls-files", args...) if err != nil { return nil, fmt.Errorf("ls-files: %w", err) @@ -576,6 +767,10 @@ func (git *Git) ListUncommitted(dirs ...string) ([]string, error) { args = append(args, dirs...) } + log.Debug(). + Str("action", "ListUncommitted()"). + Str("stack", git.config.WorkingDir). + Msg("List uncommitted files.") out, err := git.exec("ls-files", args...) if err != nil { return nil, fmt.Errorf("ls-files: %w", err) @@ -611,6 +806,13 @@ func (git *Git) CurrentBranch() (string, error) { } func (git *Git) exec(command string, args ...string) (string, error) { + logger := log.With(). + Str("action", "exec()"). + Str("stack", git.config.WorkingDir). + Logger() + + logger.Trace(). + Msg("Create cmd to execute.") cmd := exec.Cmd{ Path: git.config.ProgramPath, Args: []string{git.config.ProgramPath, command}, @@ -618,13 +820,19 @@ func (git *Git) exec(command string, args ...string) (string, error) { Env: []string{}, } + logger.Trace(). + Msg("Append arguments.") cmd.Args = append(cmd.Args, args...) if git.config.InheritEnv { + logger.Trace(). + Msg("Get environment.") cmd.Env = os.Environ() } if git.config.Isolated { + logger.Trace(). + Msg("Add git config environment variables.") cmd.Env = append(cmd.Env, "GIT_CONFIG_SYSTEM=/dev/null") cmd.Env = append(cmd.Env, "GIT_CONFIG_GLOBAL=/dev/null") cmd.Env = append(cmd.Env, "GIT_CONFIG_NOGLOBAL=1") // back-compat @@ -632,6 +840,8 @@ func (git *Git) exec(command string, args ...string) (string, error) { cmd.Env = append(cmd.Env, "GIT_ATTR_NOSYSTEM=1") } + logger.Debug(). + Msg("Run command.") stdout, err := cmd.Output() if err != nil { stderr := []byte{} @@ -703,6 +913,9 @@ func (r remoteSorter) Swap(i, j int) { } func removeEmptyLines(lines []string) []string { + log.Trace(). + Str("action", "removeEmptyLines()"). + Msg("Remove empty lines.") outlines := make([]string, 0, len(lines)) for _, line := range lines { line = strings.TrimSpace(line) diff --git a/go.mod b/go.mod index ad4cc60d9..54f3390ad 100644 --- a/go.mod +++ b/go.mod @@ -28,10 +28,11 @@ require ( github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-wordwrap v1.0.0 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/rs/zerolog v1.26.1 github.com/zclconf/go-cty-yaml v1.0.2 // indirect - golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 // indirect - golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 // indirect - golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 // indirect - golang.org/x/text v0.3.5 // indirect + golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e // indirect + golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d // indirect + golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e // indirect + golang.org/x/text v0.3.6 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect ) diff --git a/go.sum b/go.sum index 574d861be..688e431b5 100644 --- a/go.sum +++ b/go.sum @@ -117,6 +117,7 @@ github.com/coreos/bbolt v1.3.0/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkE github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -158,6 +159,7 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me github.com/go-test/deep v1.0.1/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v0.0.0-20171007142547-342cbe0a0415/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= @@ -400,6 +402,9 @@ github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.26.1 h1:/ihwxqH+4z8UxyI70wM1z9yCvkWcfz/a3mj48k/Zngc= +github.com/rs/zerolog v1.26.1/go.mod h1:/wSSJWX7lVrsOwlbyTRSOJvqRlc+WjWlfes+CiJ+tmc= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= @@ -441,6 +446,7 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/zclconf/go-cty v1.0.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s= github.com/zclconf/go-cty v1.1.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s= github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= @@ -472,8 +478,9 @@ golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e h1:1SzTfNOXwIS2oWiMF+6qu0OUDKb0dauo6MoDUQyu+yU= +golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -504,6 +511,7 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -543,8 +551,9 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d h1:20cMwl2fHAzkJMEA+8J4JgqBQcQGzbisXo31MIeenXI= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -560,6 +569,7 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -601,8 +611,10 @@ golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e h1:WUoyKPm6nCo1BnNUvPGnFG3T5DUVem42yDJZZ4CNxMA= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf h1:MZ2shdL+ZM/XzY3ZGOnh4Nlpnxz5GSOhOmtHo3iPU6M= @@ -614,8 +626,9 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3 golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -664,6 +677,7 @@ golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= golang.org/x/tools v0.0.0-20201028111035-eafbe7b904eb/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/project/project.go b/project/project.go index 367851257..eaf38ca16 100644 --- a/project/project.go +++ b/project/project.go @@ -17,10 +17,16 @@ package project import ( "path/filepath" "strings" + + "github.com/rs/zerolog/log" ) // RelPath returns the dir relative to project's root. func RelPath(root, dir string) string { + log.Trace(). + Str("action", "RelPath()"). + Str("stack", root). + Msg("Trim path to get relative dir.") d := strings.TrimPrefix(dir, root) if d == "" { d = "/" @@ -36,11 +42,19 @@ func AbsPath(root, dir string) string { // FriendlyFmtDir formats the directory in a friendly way for tooling output. func FriendlyFmtDir(root, wd, dir string) (string, bool) { + log.Trace(). + Str("action", "FriendlyFmtDir()"). + Str("stack", wd). + Msg("Get relative path.") trimPart := RelPath(root, wd) if !strings.HasPrefix(dir, trimPart) { return "", false } + log.Trace(). + Str("action", "FriendlyFmtDir()"). + Str("stack", wd). + Msg("Friendly format.") dir = strings.TrimPrefix(dir, trimPart) if dir == "" { dir = "." diff --git a/stack/loader.go b/stack/loader.go index 20f4cde12..8c35a07ff 100644 --- a/stack/loader.go +++ b/stack/loader.go @@ -24,6 +24,7 @@ import ( "github.com/mineiros-io/terramate/config" "github.com/mineiros-io/terramate/hcl" "github.com/mineiros-io/terramate/project" + "github.com/rs/zerolog/log" ) // Loader is a stack loader. @@ -43,12 +44,23 @@ func NewLoader(root string) Loader { // Load loads a stack from dir directory. If the stack was previously loaded, it // returns the cached one. func (l Loader) Load(dir string) (S, error) { + logger := log.With(). + Str("action", "Load()"). + Str("stack", dir). + Logger() + + logger.Trace(). + Msg("Get relative path to root directory.") stackpath := project.RelPath(l.root, dir) if s, ok := l.stacks[stackpath]; ok { return s, nil } fname := filepath.Join(dir, config.Filename) + + logger.Debug(). + Str("configFile", fname). + Msg("Parse config file.") cfg, err := hcl.ParseFile(fname) if err != nil { return S{}, err @@ -67,6 +79,8 @@ func (l Loader) Load(dir string) (S, error) { return S{}, fmt.Errorf("stack %q is not a leaf directory", dir) } + logger.Trace(). + Msg("Set stack path and stack config.") l.set(stackpath, cfg.Stack) return l.stacks[stackpath], nil } @@ -75,10 +89,18 @@ func (l Loader) Load(dir string) (S, error) { // only in the case that path contains a stack and it was correctly parsed. // It caches the stack for later use. func (l Loader) TryLoad(dir string) (stack S, found bool, err error) { + logger := log.With(). + Str("action", "TryLoad()"). + Str("stack", dir). + Logger() + if !strings.HasPrefix(dir, l.root) { return S{}, false, fmt.Errorf("directory %q is not inside project root %q", dir, l.root) } + + logger.Trace(). + Msg("Get relative stack path to root directory.") stackpath := project.RelPath(l.root, dir) if s, ok := l.stacks[stackpath]; ok { return s, true, nil @@ -88,6 +110,10 @@ func (l Loader) TryLoad(dir string) (stack S, found bool, err error) { return S{}, false, err } fname := filepath.Join(dir, config.Filename) + + logger.Debug(). + Str("configFile", fname). + Msg("Parse config file.") cfg, err := hcl.ParseFile(fname) if err != nil { @@ -107,6 +133,8 @@ func (l Loader) TryLoad(dir string) (stack S, found bool, err error) { return S{}, false, fmt.Errorf("stack %q is not a leaf stack", dir) } + logger.Debug(). + Msg("Set stack path and stack config.") l.set(stackpath, cfg.Stack) return l.stacks[stackpath], true, nil } @@ -114,6 +142,14 @@ func (l Loader) TryLoad(dir string) (stack S, found bool, err error) { // TryLoadChanged is like TryLoad but sets the stack as changed if loaded // successfully. func (l Loader) TryLoadChanged(root, dir string) (stack S, found bool, err error) { + logger := log.With(). + Str("action", "TryLoadChanged()"). + Str("stack", dir). + Logger() + + logger.Debug(). + Str("path", dir). + Msg("Try load.") s, ok, err := l.TryLoad(dir) if ok { s.changed = true @@ -123,12 +159,20 @@ func (l Loader) TryLoadChanged(root, dir string) (stack S, found bool, err error func (l Loader) set(path string, block *hcl.Stack) { var name string + log.Debug(). + Str("action", "set()"). + Str("stack", path). + Msg("Set stack name.") if block.Name != "" { name = block.Name } else { name = filepath.Base(path) } + log.Trace(). + Str("action", "set()"). + Str("stack", path). + Msg("Set stack information.") l.stacks[path] = S{ name: name, Dir: path, @@ -145,14 +189,25 @@ func (l Loader) Set(dir string, s S) { // LoadAll loads all the stacks in the dirs directories. If dirs are relative // paths, then basedir is used as base. func (l Loader) LoadAll(root string, basedir string, dirs ...string) ([]S, error) { + logger := log.With(). + Str("action", "LoadAll()"). + Logger() + stacks := []S{} absbase := filepath.Join(root, basedir) + logger.Trace(). + Str("stack", root). + Msg("Range over directories.") for _, d := range dirs { if !filepath.IsAbs(d) { d = filepath.Join(absbase, d) } + + logger.Debug(). + Str("stack", d). + Msg("Load stack.") stack, err := l.Load(d) if err != nil { return nil, err @@ -165,6 +220,10 @@ func (l Loader) LoadAll(root string, basedir string, dirs ...string) ([]S, error func (l Loader) IsLeafStack(dir string) (bool, error) { isValid := true + log.Trace(). + Str("action", "IsLeafStack()"). + Str("stack", dir). + Msg("Walk directory.") err := filepath.Walk( dir, func(path string, info fs.FileInfo, err error) error { @@ -182,6 +241,11 @@ func (l Loader) IsLeafStack(dir string) (bool, error) { return filepath.SkipDir } + log.Trace(). + Str("action", "IsLeafStack()"). + Str("stack", dir). + Str("path", path). + Msg("Try load.") _, found, err := l.TryLoad(path) if err != nil { return err @@ -206,6 +270,11 @@ func (l Loader) lookupParentStack(dir string) (stack S, found bool, err error) { } d := filepath.Dir(dir) for { + log.Debug(). + Str("action", "lookupParentStack()"). + Str("stack", dir). + Str("path", d). + Msg("Try load directory.") stack, ok, err := l.TryLoad(d) if err != nil { return S{}, false, fmt.Errorf("looking for parent stacks: %w", err) @@ -219,6 +288,10 @@ func (l Loader) lookupParentStack(dir string) (stack S, found bool, err error) { break } + log.Trace(). + Str("action", "lookupParentStack()"). + Str("stack", dir). + Msg("Get git path.") gitpath := filepath.Join(d, ".git") if _, err := os.Stat(gitpath); err == nil { // if reached root of git project, abort scanning