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  }