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