github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/cmd/jujud/main.go (about) 1 // Copyright 2012-2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package main 5 6 import ( 7 "fmt" 8 "io" 9 "io/ioutil" 10 "math/rand" 11 "os" 12 "path/filepath" 13 "runtime" 14 "strings" 15 "time" 16 17 "github.com/juju/clock" 18 "github.com/juju/cmd" 19 "github.com/juju/errors" 20 "github.com/juju/loggo" 21 proxyutils "github.com/juju/proxy" 22 "github.com/juju/utils/exec" 23 24 jujucmd "github.com/juju/juju/cmd" 25 agentcmd "github.com/juju/juju/cmd/jujud/agent" 26 "github.com/juju/juju/cmd/jujud/dumplogs" 27 "github.com/juju/juju/cmd/jujud/introspect" 28 cmdutil "github.com/juju/juju/cmd/jujud/util" 29 components "github.com/juju/juju/component/all" 30 "github.com/juju/juju/core/machinelock" 31 "github.com/juju/juju/juju/names" 32 "github.com/juju/juju/juju/sockets" 33 34 // Import the providers. 35 _ "github.com/juju/juju/provider/all" 36 "github.com/juju/juju/upgrades" 37 "github.com/juju/juju/utils/proxy" 38 "github.com/juju/juju/worker/logsender" 39 "github.com/juju/juju/worker/uniter/runner/jujuc" 40 ) 41 42 var log = loggo.GetLogger("juju.cmd.jujud") 43 44 func init() { 45 if err := components.RegisterForServer(); err != nil { 46 log.Criticalf("unabled to register server components: %v", err) 47 os.Exit(1) 48 } 49 } 50 51 func init() { 52 rand.Seed(time.Now().UTC().UnixNano()) 53 } 54 55 var jujudDoc = ` 56 juju provides easy, intelligent service orchestration on top of models 57 such as OpenStack, Amazon AWS, or bare metal. jujud is a component of juju. 58 59 https://jujucharms.com/ 60 61 The jujud command can also forward invocations over RPC for execution by the 62 juju unit agent. When used in this way, it expects to be called via a symlink 63 named for the desired remote command, and expects JUJU_AGENT_SOCKET and 64 JUJU_CONTEXT_ID be set in its model. 65 ` 66 67 const ( 68 // exit_err is the value that is returned when the user has run juju in an invalid way. 69 exit_err = 2 70 // exit_panic is the value that is returned when we exit due to an unhandled panic. 71 exit_panic = 3 72 ) 73 74 func getenv(name string) (string, error) { 75 value := os.Getenv(name) 76 if value == "" { 77 return "", errors.Errorf("%s not set", name) 78 } 79 return value, nil 80 } 81 82 func getwd() (string, error) { 83 dir, err := os.Getwd() 84 if err != nil { 85 return "", err 86 } 87 abs, err := filepath.Abs(dir) 88 if err != nil { 89 return "", err 90 } 91 return abs, nil 92 } 93 94 // hookToolMain uses JUJU_CONTEXT_ID and JUJU_AGENT_SOCKET to ask a running unit agent 95 // to execute a Command on our behalf. Individual commands should be exposed 96 // by symlinking the command name to this executable. 97 func hookToolMain(commandName string, ctx *cmd.Context, args []string) (code int, err error) { 98 code = 1 99 contextId, err := getenv("JUJU_CONTEXT_ID") 100 if err != nil { 101 return 102 } 103 dir, err := getwd() 104 if err != nil { 105 return 106 } 107 req := jujuc.Request{ 108 ContextId: contextId, 109 Dir: dir, 110 CommandName: commandName, 111 Args: args[1:], 112 } 113 socketPath, err := getenv("JUJU_AGENT_SOCKET") 114 if err != nil { 115 return 116 } 117 client, err := sockets.Dial(socketPath) 118 if err != nil { 119 return 120 } 121 defer client.Close() 122 var resp exec.ExecResponse 123 err = client.Call("Jujuc.Main", req, &resp) 124 if err != nil && err.Error() == jujuc.ErrNoStdin.Error() { 125 req.Stdin, err = ioutil.ReadAll(os.Stdin) 126 if err != nil { 127 err = errors.Annotate(err, "cannot read stdin") 128 return 129 } 130 req.StdinSet = true 131 err = client.Call("Jujuc.Main", req, &resp) 132 } 133 if err != nil { 134 return 135 } 136 os.Stdout.Write(resp.Stdout) 137 os.Stderr.Write(resp.Stderr) 138 return resp.Code, nil 139 } 140 141 // Main registers subcommands for the jujud executable, and hands over control 142 // to the cmd package. 143 func jujuDMain(args []string, ctx *cmd.Context) (code int, err error) { 144 // Assuming an average of 200 bytes per log message, use up to 145 // 200MB for the log buffer. 146 defer logger.Debugf("jujud complete, code %d, err %v", code, err) 147 bufferedLogger, err := logsender.InstallBufferedLogWriter(1048576) 148 if err != nil { 149 return 1, errors.Trace(err) 150 } 151 152 // Set the default transport to use the in-process proxy 153 // configuration. 154 if err := proxy.DefaultConfig.Set(proxyutils.DetectProxies()); err != nil { 155 return 1, errors.Trace(err) 156 } 157 if err := proxy.DefaultConfig.InstallInDefaultTransport(); err != nil { 158 return 1, errors.Trace(err) 159 } 160 161 jujud := jujucmd.NewSuperCommand(cmd.SuperCommandParams{ 162 Name: "jujud", 163 Doc: jujudDoc, 164 }) 165 166 jujud.Log.NewWriter = func(target io.Writer) loggo.Writer { 167 return &jujudWriter{target: target} 168 } 169 170 jujud.Register(NewBootstrapCommand()) 171 172 // TODO(katco-): AgentConf type is doing too much. The 173 // MachineAgent type has called out the separate concerns; the 174 // AgentConf should be split up to follow suit. 175 agentConf := agentcmd.NewAgentConf("") 176 machineAgentFactory := agentcmd.MachineAgentFactoryFn( 177 agentConf, 178 bufferedLogger, 179 agentcmd.DefaultIntrospectionSocketName, 180 upgrades.PreUpgradeSteps, 181 "", 182 ) 183 jujud.Register(agentcmd.NewMachineAgentCmd(ctx, machineAgentFactory, agentConf, agentConf)) 184 185 unitAgent, err := agentcmd.NewUnitAgent(ctx, bufferedLogger) 186 if err != nil { 187 return -1, errors.Trace(err) 188 } 189 jujud.Register(unitAgent) 190 191 caasOperatorAgent, err := agentcmd.NewCaasOperatorAgent(ctx, bufferedLogger) 192 if err != nil { 193 return -1, errors.Trace(err) 194 } 195 jujud.Register(caasOperatorAgent) 196 197 jujud.Register(NewUpgradeMongoCommand()) 198 jujud.Register(agentcmd.NewCheckConnectionCommand(agentConf, agentcmd.ConnectAsAgent)) 199 200 code = cmd.Main(jujud, ctx, args[1:]) 201 return code, nil 202 } 203 204 // This function exists to preserve test functionality. 205 // On windows we need to catch the return code from main for 206 // service functionality purposes, but on unix we can just os.Exit 207 func MainWrapper(args []string) { 208 os.Exit(Main(args)) 209 } 210 211 // Main is not redundant with main(), because it provides an entry point 212 // for testing with arbitrary command line arguments. 213 func Main(args []string) int { 214 defer func() { 215 if r := recover(); r != nil { 216 buf := make([]byte, 4096) 217 buf = buf[:runtime.Stack(buf, false)] 218 logger.Criticalf("Unhandled panic: \n%v\n%s", r, buf) 219 os.Exit(exit_panic) 220 } 221 }() 222 223 ctx, err := cmd.DefaultContext() 224 if err != nil { 225 cmd.WriteError(os.Stderr, err) 226 os.Exit(exit_err) 227 } 228 229 code := 1 230 commandName := filepath.Base(args[0]) 231 switch commandName { 232 case names.Jujud: 233 code, err = jujuDMain(args, ctx) 234 case names.JujuRun: 235 lock, err := machinelock.New(machinelock.Config{ 236 AgentName: "juju-run", 237 Clock: clock.WallClock, 238 Logger: loggo.GetLogger("juju.machinelock"), 239 LogFilename: filepath.Join(cmdutil.LogDir, machinelock.Filename), 240 }) 241 if err != nil { 242 code = exit_err 243 } else { 244 run := &RunCommand{MachineLock: lock} 245 code = cmd.Main(run, ctx, args[1:]) 246 } 247 case names.JujuDumpLogs: 248 code = cmd.Main(dumplogs.NewCommand(), ctx, args[1:]) 249 case names.JujuIntrospect: 250 code = cmd.Main(&introspect.IntrospectCommand{}, ctx, args[1:]) 251 default: 252 code, err = hookToolMain(commandName, ctx, args) 253 } 254 if err != nil { 255 cmd.WriteError(ctx.Stderr, err) 256 } 257 return code 258 } 259 260 type jujudWriter struct { 261 target io.Writer 262 } 263 264 func (w *jujudWriter) Write(entry loggo.Entry) { 265 if strings.HasPrefix(entry.Module, "unit.") { 266 fmt.Fprintln(w.target, w.unitFormat(entry)) 267 } else { 268 fmt.Fprintln(w.target, loggo.DefaultFormatter(entry)) 269 } 270 } 271 272 func (w *jujudWriter) unitFormat(entry loggo.Entry) string { 273 ts := entry.Timestamp.In(time.UTC).Format("2006-01-02 15:04:05") 274 // Just show the last element of the module. 275 lastDot := strings.LastIndex(entry.Module, ".") 276 module := entry.Module[lastDot+1:] 277 return fmt.Sprintf("%s %s %s %s", ts, entry.Level, module, entry.Message) 278 }