github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/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  	"github.com/juju/juju/agent"
    22  	jujucmd "github.com/juju/juju/cmd"
    23  	agentcmd "github.com/juju/juju/cmd/jujud/agent"
    24  	"github.com/juju/juju/cmd/jujud/dumplogs"
    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://jujucharms.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  	defer logger.Debugf("jujud complete, code %d, err %v", code, err)
   135  	logCh, err := logsender.InstallBufferedLogWriter(1048576)
   136  	if err != nil {
   137  		return 1, errors.Trace(err)
   138  	}
   139  
   140  	jujud := jujucmd.NewSuperCommand(cmd.SuperCommandParams{
   141  		Name: "jujud",
   142  		Doc:  jujudDoc,
   143  	})
   144  
   145  	jujud.Log.NewWriter = func(target io.Writer) loggo.Writer {
   146  		return &jujudWriter{target: target}
   147  	}
   148  
   149  	jujud.Register(NewBootstrapCommand())
   150  
   151  	// TODO(katco-): AgentConf type is doing too much. The
   152  	// MachineAgent type has called out the separate concerns; the
   153  	// AgentConf should be split up to follow suit.
   154  	agentConf := agentcmd.NewAgentConf("")
   155  	machineAgentFactory := agentcmd.MachineAgentFactoryFn(agentConf, logCh, "")
   156  	jujud.Register(agentcmd.NewMachineAgentCmd(ctx, machineAgentFactory, agentConf, agentConf))
   157  
   158  	jujud.Register(agentcmd.NewUnitAgent(ctx, logCh))
   159  
   160  	jujud.Register(NewUpgradeMongoCommand())
   161  
   162  	code = cmd.Main(jujud, ctx, args[1:])
   163  	return code, nil
   164  }
   165  
   166  // This function exists to preserve test functionality.
   167  // On windows we need to catch the return code from main for
   168  // service functionality purposes, but on unix we can just os.Exit
   169  func MainWrapper(args []string) {
   170  	os.Exit(Main(args))
   171  }
   172  
   173  // Main is not redundant with main(), because it provides an entry point
   174  // for testing with arbitrary command line arguments.
   175  func Main(args []string) int {
   176  	defer func() {
   177  		if r := recover(); r != nil {
   178  			buf := make([]byte, 4096)
   179  			buf = buf[:runtime.Stack(buf, false)]
   180  			logger.Criticalf("Unhandled panic: \n%v\n%s", r, buf)
   181  			os.Exit(exit_panic)
   182  		}
   183  	}()
   184  
   185  	ctx, err := cmd.DefaultContext()
   186  	if err != nil {
   187  		fmt.Fprintf(os.Stderr, "error: %v\n", err)
   188  		os.Exit(exit_err)
   189  	}
   190  
   191  	code := 1
   192  	commandName := filepath.Base(args[0])
   193  	switch commandName {
   194  	case names.Jujud:
   195  		code, err = jujuDMain(args, ctx)
   196  	case names.Jujuc:
   197  		fmt.Fprint(os.Stderr, jujudDoc)
   198  		code = exit_err
   199  		err = fmt.Errorf("jujuc should not be called directly")
   200  	case names.JujuRun:
   201  		run := &RunCommand{
   202  			MachineLockName: agent.MachineLockName,
   203  		}
   204  		code = cmd.Main(run, 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  }
   219  
   220  func (w *jujudWriter) Write(entry loggo.Entry) {
   221  	if strings.HasPrefix(entry.Module, "unit.") {
   222  		fmt.Fprintln(w.target, w.unitFormat(entry))
   223  	} else {
   224  		fmt.Fprintln(w.target, loggo.DefaultFormatter(entry))
   225  	}
   226  }
   227  
   228  func (w *jujudWriter) unitFormat(entry loggo.Entry) string {
   229  	ts := entry.Timestamp.In(time.UTC).Format("2006-01-02 15:04:05")
   230  	// Just show the last element of the module.
   231  	lastDot := strings.LastIndex(entry.Module, ".")
   232  	module := entry.Module[lastDot+1:]
   233  	return fmt.Sprintf("%s %s %s %s", ts, entry.Level, module, entry.Message)
   234  }