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 }