github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/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 "os" 11 "path/filepath" 12 "runtime" 13 "strings" 14 "time" 15 16 "github.com/juju/cmd" 17 "github.com/juju/errors" 18 "github.com/juju/loggo" 19 "github.com/juju/utils/exec" 20 21 jujucmd "github.com/juju/juju/cmd" 22 agentcmd "github.com/juju/juju/cmd/jujud/agent" 23 "github.com/juju/juju/juju/names" 24 "github.com/juju/juju/juju/sockets" 25 // Import the providers. 26 _ "github.com/juju/juju/provider/all" 27 "github.com/juju/juju/storage/looputil" 28 "github.com/juju/juju/worker/logsender" 29 "github.com/juju/juju/worker/uniter/runner/jujuc" 30 ) 31 32 var jujudDoc = ` 33 juju provides easy, intelligent service orchestration on top of environments 34 such as OpenStack, Amazon AWS, or bare metal. jujud is a component of juju. 35 36 https://juju.ubuntu.com/ 37 38 The jujud command can also forward invocations over RPC for execution by the 39 juju unit agent. When used in this way, it expects to be called via a symlink 40 named for the desired remote command, and expects JUJU_AGENT_SOCKET and 41 JUJU_CONTEXT_ID be set in its environment. 42 ` 43 44 const ( 45 // exit_err is the value that is returned when the user has run juju in an invalid way. 46 exit_err = 2 47 // exit_panic is the value that is returned when we exit due to an unhandled panic. 48 exit_panic = 3 49 ) 50 51 func getenv(name string) (string, error) { 52 value := os.Getenv(name) 53 if value == "" { 54 return "", fmt.Errorf("%s not set", name) 55 } 56 return value, nil 57 } 58 59 func getwd() (string, error) { 60 dir, err := os.Getwd() 61 if err != nil { 62 return "", err 63 } 64 abs, err := filepath.Abs(dir) 65 if err != nil { 66 return "", err 67 } 68 return abs, nil 69 } 70 71 // jujuCMain uses JUJU_CONTEXT_ID and JUJU_AGENT_SOCKET to ask a running unit agent 72 // to execute a Command on our behalf. Individual commands should be exposed 73 // by symlinking the command name to this executable. 74 func jujuCMain(commandName string, ctx *cmd.Context, args []string) (code int, err error) { 75 code = 1 76 contextId, err := getenv("JUJU_CONTEXT_ID") 77 if err != nil { 78 return 79 } 80 dir, err := getwd() 81 if err != nil { 82 return 83 } 84 req := jujuc.Request{ 85 ContextId: contextId, 86 Dir: dir, 87 CommandName: commandName, 88 Args: args[1:], 89 } 90 socketPath, err := getenv("JUJU_AGENT_SOCKET") 91 if err != nil { 92 return 93 } 94 client, err := sockets.Dial(socketPath) 95 if err != nil { 96 return 97 } 98 defer client.Close() 99 var resp exec.ExecResponse 100 err = client.Call("Jujuc.Main", req, &resp) 101 if err != nil && err.Error() == jujuc.ErrNoStdin.Error() { 102 req.Stdin, err = ioutil.ReadAll(os.Stdin) 103 if err != nil { 104 err = errors.Annotate(err, "cannot read stdin") 105 return 106 } 107 req.StdinSet = true 108 err = client.Call("Jujuc.Main", req, &resp) 109 } 110 if err != nil { 111 return 112 } 113 os.Stdout.Write(resp.Stdout) 114 os.Stderr.Write(resp.Stderr) 115 return resp.Code, nil 116 } 117 118 // Main registers subcommands for the jujud executable, and hands over control 119 // to the cmd package. 120 func jujuDMain(args []string, ctx *cmd.Context) (code int, err error) { 121 // Assuming an average of 200 bytes per log message, use up to 122 // 200MB for the log buffer. 123 logCh, err := logsender.InstallBufferedLogWriter(1048576) 124 if err != nil { 125 return 1, errors.Trace(err) 126 } 127 128 jujud := jujucmd.NewSuperCommand(cmd.SuperCommandParams{ 129 Name: "jujud", 130 Doc: jujudDoc, 131 }) 132 jujud.Log.Factory = &writerFactory{} 133 jujud.Register(NewBootstrapCommand()) 134 135 // TODO(katco-): AgentConf type is doing too much. The 136 // MachineAgent type has called out the separate concerns; the 137 // AgentConf should be split up to follow suit. 138 agentConf := agentcmd.NewAgentConf("") 139 machineAgentFactory := agentcmd.MachineAgentFactoryFn( 140 agentConf, logCh, looputil.NewLoopDeviceManager(), 141 ) 142 jujud.Register(agentcmd.NewMachineAgentCmd(ctx, machineAgentFactory, agentConf, agentConf)) 143 144 jujud.Register(agentcmd.NewUnitAgent(ctx, logCh)) 145 146 code = cmd.Main(jujud, ctx, args[1:]) 147 return code, nil 148 } 149 150 // This function exists to preserve test functionality. 151 // On windows we need to catch the return code from main for 152 // service functionality purposes, but on unix we can just os.Exit 153 func MainWrapper(args []string) { 154 os.Exit(Main(args)) 155 } 156 157 // Main is not redundant with main(), because it provides an entry point 158 // for testing with arbitrary command line arguments. 159 func Main(args []string) int { 160 defer func() { 161 if r := recover(); r != nil { 162 buf := make([]byte, 4096) 163 buf = buf[:runtime.Stack(buf, false)] 164 logger.Criticalf("Unhandled panic: \n%v\n%s", r, buf) 165 os.Exit(exit_panic) 166 } 167 }() 168 var code int = 1 169 ctx, err := cmd.DefaultContext() 170 if err != nil { 171 fmt.Fprintf(os.Stderr, "error: %v\n", err) 172 os.Exit(exit_err) 173 } 174 commandName := filepath.Base(args[0]) 175 if commandName == names.Jujud { 176 code, err = jujuDMain(args, ctx) 177 } else if commandName == names.Jujuc { 178 fmt.Fprint(os.Stderr, jujudDoc) 179 code = exit_err 180 err = fmt.Errorf("jujuc should not be called directly") 181 } else if commandName == names.JujuRun { 182 code = cmd.Main(&RunCommand{}, ctx, args[1:]) 183 } else { 184 code, err = jujuCMain(commandName, ctx, args) 185 } 186 if err != nil { 187 fmt.Fprintf(os.Stderr, "error: %v\n", err) 188 } 189 return code 190 } 191 192 type writerFactory struct{} 193 194 func (*writerFactory) NewWriter(target io.Writer) loggo.Writer { 195 return &jujudWriter{target: target} 196 } 197 198 type jujudWriter struct { 199 target io.Writer 200 unitFormatter simpleFormatter 201 defaultFormatter loggo.DefaultFormatter 202 } 203 204 var _ loggo.Writer = (*jujudWriter)(nil) 205 206 func (w *jujudWriter) Write(level loggo.Level, module, filename string, line int, timestamp time.Time, message string) { 207 if strings.HasPrefix(module, "unit.") { 208 fmt.Fprintln(w.target, w.unitFormatter.Format(level, module, timestamp, message)) 209 } else { 210 fmt.Fprintln(w.target, w.defaultFormatter.Format(level, module, filename, line, timestamp, message)) 211 } 212 } 213 214 type simpleFormatter struct{} 215 216 func (*simpleFormatter) Format(level loggo.Level, module string, timestamp time.Time, message string) string { 217 ts := timestamp.In(time.UTC).Format("2006-01-02 15:04:05") 218 // Just show the last element of the module. 219 lastDot := strings.LastIndex(module, ".") 220 module = module[lastDot+1:] 221 return fmt.Sprintf("%s %s %s %s", ts, level, module, message) 222 }