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  }