github.com/swaros/contxt/module/runner@v0.0.0-20240305083542-3dbd4436ac40/init.go (about)

     1  // Copyright (c) 2023 Thomas Ziegler <thomas.zglr@googlemail.com>. All rights reserved.
     2  //
     3  // # Licensed under the MIT License
     4  //
     5  // Permission is hereby granted, free of charge, to any person obtaining a copy
     6  // of this software and associated documentation files (the "Software"), to deal
     7  // in the Software without restriction, including without limitation the rights
     8  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     9  // copies of the Software, and to permit persons to whom the Software is
    10  // furnished to do so, subject to the following conditions:
    11  //
    12  // The above copyright notice and this permission notice shall be included in all
    13  // copies or substantial portions of the Software.
    14  //
    15  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    16  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    17  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    18  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    19  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    20  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    21  // SOFTWARE.
    22  package runner
    23  
    24  import (
    25  	"os"
    26  	"runtime"
    27  
    28  	"github.com/sirupsen/logrus"
    29  	"github.com/swaros/contxt/module/configure"
    30  	"github.com/swaros/contxt/module/ctxout"
    31  	"github.com/swaros/contxt/module/systools"
    32  	"github.com/swaros/contxt/module/tasks"
    33  )
    34  
    35  func setShutDownBehavior() {
    36  	// add exit listener for shutting down all processes
    37  	systools.AddExitListener("main", func(code int) systools.ExitBehavior {
    38  		ctxout.PrintLn(ctxout.NewMOWrap(), ctxout.ForeDarkGrey, " stop all tasks: ", ctxout.CleanTag)
    39  		tasks.ShutDownProcesses(func(target string, time int, succeed bool) {
    40  			if succeed {
    41  				ctxout.PrintLn(ctxout.NewMOWrap(), ctxout.ForeDarkGrey, "  task stopped: ", ctxout.ForeBlue, target, ctxout.CleanTag)
    42  			} else {
    43  				ctxout.PrintLn(ctxout.NewMOWrap(), ctxout.ForeDarkGrey, "  stop failure: ", ctxout.ForeRed, target, ctxout.CleanTag)
    44  			}
    45  		})
    46  		return systools.Continue
    47  	})
    48  
    49  	// add exit listener for shutting down all processes
    50  	// different to the main listener, this one will kill all processes
    51  	// that are not stopped by the main listener.
    52  	// this depends on the behavior of the system, where it is hard to get all child processes
    53  	// so we use the HandleAllMyPid function to get all child processes.
    54  	// this function wraps the ps command and filters the output for the current pid on linux.
    55  	systools.AddExitListener("killProcs", func(code int) systools.ExitBehavior {
    56  		ctxout.PrintLn(ctxout.NewMOWrap(), ctxout.ForeDarkGrey, " Cleanup all child Processes if possible. ", ctxout.CleanTag)
    57  		tasks.HandleAllMyPid(func(pid int) error {
    58  
    59  			if proc, err := os.FindProcess(pid); err == nil {
    60  				if err := proc.Kill(); err != nil {
    61  					if err == os.ErrProcessDone {
    62  						ctxout.PrintLn(
    63  							ctxout.NewMOWrap(),
    64  							ctxout.ForeDarkGrey,
    65  							"  task is already stopped: ",
    66  							ctxout.ForeCyan,
    67  							pid,
    68  							ctxout.CleanTag,
    69  						)
    70  					} else {
    71  						ctxout.PrintLn(
    72  							ctxout.NewMOWrap(),
    73  							ctxout.ForeDarkGrey,
    74  							"  error while stooping task: ",
    75  							ctxout.ForeCyan,
    76  							pid,
    77  							ctxout.ForeRed,
    78  							err.Error(),
    79  							ctxout.CleanTag,
    80  						)
    81  					}
    82  					return err
    83  				} else {
    84  					ctxout.PrintLn(ctxout.NewMOWrap(), ctxout.ForeDarkGrey, "  stopped: ", ctxout.ForeBlue, pid, ctxout.CleanTag)
    85  				}
    86  			} else {
    87  				ctxout.PrintLn(ctxout.NewMOWrap(), ctxout.ForeDarkGrey, "  failed to stop: ", ctxout.ForeRed, pid, ctxout.CleanTag)
    88  				return err
    89  			}
    90  			return nil
    91  		})
    92  		return systools.Continue
    93  	})
    94  	// capture the sigterm signal so we are able to cleanup all processes
    95  	// nil means that we use the default behavior for the exit control flow
    96  	// so any systool.Exit() call will trigger the exit listeners
    97  	// this is experimental and is only enabled if the env CTX_SUTDOWN_BEHAVIOR is set to "true"
    98  	// this is a workaround for the problem that the exit listeners are not called if the application
    99  	// is killed by the system.
   100  	if os.Getenv("CTX_SUTDOWN_BEHAVIOR") == "true" {
   101  		systools.WatchSigTerm(nil)
   102  	}
   103  }
   104  
   105  // Init initializes the application
   106  // and starts the main loop
   107  func Init() error {
   108  	// create the application session
   109  	app := NewCmdSession()
   110  
   111  	// set the TemplateHndl OnLoad function to parse required files
   112  	onLoadFn := func(template *configure.RunConfig) error {
   113  		return app.SharedHelper.MergeRequiredPaths(template, app.TemplateHndl)
   114  	}
   115  	app.TemplateHndl.SetOnLoad(onLoadFn)
   116  
   117  	// set the default log level
   118  	app.Log.Logger.SetLevel(logrus.ErrorLevel)
   119  	// create the the command executor instance
   120  	functions := NewCmd(app)
   121  
   122  	// add support for utf-8 signs
   123  	glyps := ctxout.NewSignFilter(nil)
   124  	glyps.AddSign(ctxout.Sign{Glyph: "🭬", Name: "runident", Fallback: "»"})
   125  	glyps.AddSign(ctxout.Sign{Glyph: "🭮", Name: "stopident", Fallback: "«"})
   126  	glyps.AddSign(ctxout.Sign{Glyph: "", Name: "prompt", Fallback: "»"})
   127  	glyps.AddSign(ctxout.Sign{Glyph: "⠄⠆⠇⠋⠙⠸⠰⠠⠐⠈", Name: "pbar", Fallback: "-_\\|/"})
   128  	ctxout.AddPostFilter(glyps)
   129  
   130  	// enable the sign filter if possible
   131  	// in current ctxout version, must be done before NewTabOut
   132  	if runtime.GOOS != "windows" && systools.IsStdOutTerminal() {
   133  		// check if unicode is supported. if not, disable the sign filter
   134  		// this is the only way i see to check for unicode support
   135  		code1, code2, errorEx := tasks.Execute("bash", []string{"-c"}, "echo -e $TERM", func(s string, err error) bool {
   136  			if err == nil {
   137  				terminalsTheyNotSupportUt8Chars := []string{"xterm", "screen", "tmux"}
   138  				// if one of these terminals is used, we do not enable the sign filter
   139  				for _, term := range terminalsTheyNotSupportUt8Chars {
   140  					if s == term {
   141  						return false
   142  					}
   143  				}
   144  			}
   145  			glyps.Enable()
   146  			return true
   147  		}, func(p *os.Process) {
   148  
   149  		})
   150  
   151  		// just to be sure, if anything gos wrong by checking the terminal, we disable the sign filter
   152  		if errorEx != nil && code1 == 0 && code2 == 0 {
   153  			glyps.Disable()
   154  		}
   155  	}
   156  
   157  	// set the default output filter
   158  	ctxout.AddPostFilter(ctxout.NewTabOut())
   159  
   160  	// initialize the application functions
   161  	functions.MainInit()
   162  
   163  	// set the shutdown behavior
   164  	setShutDownBehavior()
   165  	// initialize the cobra commands
   166  	if err := app.Cobra.Init(functions); err != nil {
   167  		return err
   168  	}
   169  	// and execute the root command
   170  	if err := app.Cobra.RootCmd.Execute(); err != nil {
   171  		return err
   172  	}
   173  	return nil
   174  }