コード例 #1
0
func Config(args []string) {
	doc := constants.DatastoreIntro + `Usage:
  calicoctl config set <NAME> <VALUE> [--node=<NODE>]
                                      [--raw=(bgp|felix)]
                                      [--config=<CONFIG>]
  calicoctl config unset <NAME> [--node=<NODE>]
                                [--raw=(bgp|felix)]
                                [--config=<CONFIG>]
  calicoctl config get <NAME> [--node=<NODE>]
                              [--raw=(bgp|felix)]
                              [--config=<CONFIG>]

Examples:
  # Turn off the full BGP node-to-node mesh
  calicoctl config set nodeToNodeMesh off

  # Set global log level to warning
  calicoctl config set logLevel warning

  # Set log level to info for node "node1"
  calicoctl config set logLevel info --node=node1

  # Display the current setting for the nodeToNodeMesh
  calicoctl config get nodeToNodeMesh

Options:
  -n --node=<NODE>      The node name.
     --raw=(bgp|felix)  Apply raw configuration for the specified component.
                        This option should be used with care; the data is not
                        validated and it is possible to configure or remove
                        data that may prevent the component from working as
                        expected.
  -c --config=<CONFIG>  Path to the file containing connection configuration in
                        YAML or JSON format.
                        [default: /etc/calico/calicoctl.cfg]

Description:

These commands can be used to manage global system-wide configuration and some
node-specific low level configuration.

The --node option is used to specify the node name for low-level configuration
that is specific to a particular node.

For configuration that has both global values and node-specific values, the
--node parameter is optional:  including the parameter will manage the
node-specific value,  excluding it will manage the global value.  For these
options, if the node-specific value is unset, the global value will be used on
the node.

For configuration that is only global, the --node option should not be
included.  Unsetting the global value will return it to it's original default.

For configuration that is node-specific only, the --node option should be
included.  Unsetting the node value will remove the configuration, and for
supported configuration will then inherit the value from the global settings.

The table below details the valid config options.

 Name            | Scope       | Value                                  |
-----------------+-------------+----------------------------------------+
 logLevel        | global,node | none,debug,info,warning,error,critical |
 nodeToNodeMesh  | global      | on,off                                 |
 asNumber        | global      | 0-4294967295                           |
 ipip            | global      | on,off                                 |
`
	parsedArgs, err := docopt.Parse(doc, args, true, "calicoctl", false, false)
	if err != nil {
		fmt.Printf("Invalid option: 'calicoctl %s'. Use flag '--help' to read about a specific subcommand.\n", strings.Join(args, " "))
		os.Exit(1)
	}
	if len(parsedArgs) == 0 {
		return
	}

	// Load the client config and connect.
	cf := parsedArgs["--config"].(string)
	client, err := clientmgr.NewClient(cf)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	// From the command line arguments construct the Config object to send to the client.
	node := argutils.ArgStringOrBlank(parsedArgs, "--node")
	raw := argutils.ArgStringOrBlank(parsedArgs, "--raw")
	name := argutils.ArgStringOrBlank(parsedArgs, "<NAME>")
	value := argutils.ArgStringOrBlank(parsedArgs, "<VALUE>")

	// For now we map each option through to separate config methods, but
	// eventually we'll aim to have a config style resource and this will
	// become more generic.
	var ct configType
	switch raw {
	case "felix":
		ct = rawFelixConfig{name: name, c: client.Config()}
	case "bgp":
		ct = rawBGPConfig{name: name, c: client.Config()}
	case "":
		switch strings.ToLower(name) {
		case "loglevel":
			ct = loglevel{client.Config()}
		case "nodetonodemesh":
			ct = nodemesh{client.Config()}
		case "asnumber":
			ct = asnum{client.Config()}
		case "ipip":
			ct = ipip{client.Config()}
		default:
			fmt.Printf("Error executing command: unrecognised config name '%s'\n", name)
			os.Exit(1)
		}

	default:
		fmt.Printf("Error executing command: unrecognised component '%s'\n", raw)
		os.Exit(1)
	}

	if parsedArgs["set"].(bool) {
		err = ct.set(value, node)
	} else if parsedArgs["unset"].(bool) {
		err = ct.unset(node)
	} else {
		err = ct.get(node)
	}

	if err != nil {
		fmt.Printf("Error executing command: %s\n", err)
		os.Exit(1)
	}

	return
}
コード例 #2
0
// getResourceFromArguments returns a resource instance from the command line arguments.
func getResourceFromArguments(args map[string]interface{}) (unversioned.Resource, error) {
	kind := args["<KIND>"].(string)
	name := argutils.ArgStringOrBlank(args, "<NAME>")
	node := argutils.ArgStringOrBlank(args, "--node")
	workload := argutils.ArgStringOrBlank(args, "--workload")
	orchestrator := argutils.ArgStringOrBlank(args, "--orchestrator")
	resScope := argutils.ArgStringOrBlank(args, "--scope")
	switch strings.ToLower(kind) {
	case "node", "nodes":
		p := api.NewNode()
		p.Metadata.Name = name
		return *p, nil
	case "hostendpoint", "hostendpoints":
		h := api.NewHostEndpoint()
		h.Metadata.Name = name
		h.Metadata.Node = node
		return *h, nil
	case "workloadendpoint", "workloadendpoints":
		h := api.NewWorkloadEndpoint()
		h.Metadata.Name = name
		h.Metadata.Orchestrator = orchestrator
		h.Metadata.Workload = workload
		h.Metadata.Node = node
		return *h, nil
	case "profile", "profiles":
		p := api.NewProfile()
		p.Metadata.Name = name
		return *p, nil
	case "policy", "policies":
		p := api.NewPolicy()
		p.Metadata.Name = name
		return *p, nil
	case "ippool", "ippools":
		p := api.NewIPPool()
		if name != "" {
			_, cidr, err := net.ParseCIDR(name)
			if err != nil {
				return nil, err
			}
			p.Metadata.CIDR = *cidr
		}
		return *p, nil
	case "bgppeer", "bgppeers":
		p := api.NewBGPPeer()
		if name != "" {
			err := p.Metadata.PeerIP.UnmarshalText([]byte(name))
			if err != nil {
				return nil, err
			}
		}
		p.Metadata.Node = node
		switch resScope {
		case "node":
			p.Metadata.Scope = scope.Node
		case "global":
			p.Metadata.Scope = scope.Global
		case "":
			p.Metadata.Scope = scope.Undefined
		default:
			return nil, fmt.Errorf("Unrecognized scope '%s', must be one of: global, node", resScope)
		}
		return *p, nil

	default:
		return nil, fmt.Errorf("Resource type '%s' is not supported", kind)
	}
}
コード例 #3
0
ファイル: run.go プロジェクト: matthewdupre/calico-containers
// Run function collects diagnostic information and logs
func Run(args []string) {
	var err error
	doc := fmt.Sprintf(`Usage:
  calicoctl node run [--ip=<IP>] [--ip6=<IP6>] [--as=<AS_NUM>]
                     [--name=<NAME>]
                     [--log-dir=<LOG_DIR>]
                     [--node-image=<DOCKER_IMAGE_NAME>]
                     [--backend=(bird|gobgp|none)]
                     [--config=<CONFIG>]
                     [--no-default-ippools]
                     [--dryrun]

Options:
  -h --help                Show this screen.
     --as=<AS_NUM>         The default AS number for this node.  If this is not
                           specified, the node will use the global AS number
                           (see 'calicoctl config' for details).
     --name=<NAME>         The name of the Calico node.  If this is not
                           supplied it defaults to the host name.
     --ip=<IP>             The local management address to use.  If this is not
                           specified, the node will attempt to auto-discover
                           the local IP address to use - however, it is
                           recommended to specify the required address to use.
     --ip6=<IP6>           The local IPv6 management address to use.  If this
                           is not specified, the node will not route IPv6.
     --log-dir=<LOG_DIR>   The directory containing Calico logs.
                           [default: /var/log/calico]
     --node-image=<DOCKER_IMAGE_NAME>
                           Docker image to use for Calico's
                           per-node container.
                           [default: calico/node:%s]
     --backend=(bird|gobgp|none)
                           Specify which networking backend to use.  When set
                           to "none", Calico node runs in policy only mode.
                           The option to run with gobgp is currently
                           experimental.
                           [default: bird]
     --dryrun              Output the appropriate Docker command, without
                           starting the container.
     --no-default-ippools  Do not create default pools upon startup.
                           Default IP pools will be created if this is not set
                           and there are no pre-existing Calico IP pools.
  -c --config=<CONFIG>     Filename containing connection configuration in
                           YAML or JSON format.
                           [default: /etc/calico/calicoctl.cfg]

Description:
  This command is used to start a Calico node container instance.  The
  Calico node is used to provide Calico networking on your compute host.

  This command is used to quickly start the Calico node container using Docker
  and by running with the --dryrun option can display the appropriate Docker
  command without actually running the command - this is useful if you intend
  to deploy Calico and therefore should include in your system startup
  configuraiton (e.g. systemd).

  For quickstart demonstration, this command may be run with no parameters.
`, VERSION)
	arguments, err := docopt.Parse(doc, args, true, "", false, false)
	if err != nil {
		log.Info(err)
		fmt.Printf("Invalid option: 'calicoctl %s'. Use flag '--help' to read about a specific subcommand.\n", strings.Join(args, " "))
		os.Exit(1)
	}
	if len(arguments) == 0 {
		return
	}

	// Extract all the parameters.
	ipv4 := argutils.ArgStringOrBlank(arguments, "--ip")
	ipv6 := argutils.ArgStringOrBlank(arguments, "--ip6")
	logDir := argutils.ArgStringOrBlank(arguments, "--log-dir")
	asNumber := argutils.ArgStringOrBlank(arguments, "--as")
	img := argutils.ArgStringOrBlank(arguments, "--node-image")
	backend := argutils.ArgStringOrBlank(arguments, "--backend")
	dryrun := argutils.ArgBoolOrFalse(arguments, "--dryrun")
	name := argutils.ArgStringOrBlank(arguments, "--name")
	nopools := argutils.ArgBoolOrFalse(arguments, "--no-default-ippools")
	config := argutils.ArgStringOrBlank(arguments, "--config")

	// Validate parameters.
	if ipv4 != "" {
		ip := argutils.ValidateIP(ipv4)
		if ip.Version() != 4 {
			fmt.Println("Error executing command: --ip is wrong IP version")
			os.Exit(1)
		}
	}
	if ipv6 != "" {
		ip := argutils.ValidateIP(ipv6)
		if ip.Version() != 6 {
			fmt.Println("Error executing command: --ip6 is wrong IP version")
			os.Exit(1)
		}
	}
	if asNumber != "" {
		argutils.ValidateASNumber(asNumber)
	}
	backendMatch := regexp.MustCompile("^(none|bird|gobgp)$")
	if !backendMatch.MatchString(backend) {
		fmt.Printf("Error executing command: unknown backend '%s'\n", backend)
		os.Exit(1)
	}

	// Use the hostname if a name is not specified.
	if name == "" {
		name, err = os.Hostname()
		if err != nil || name == "" {
			fmt.Println("Error executing command: unable to determine node name")
			os.Exit(1)
		}
	}

	// Load the etcd configuraiton.
	cfg, err := clientmgr.LoadClientConfig(config)
	if err != nil {
		fmt.Println("Error executing command: invalid config file")
		os.Exit(1)
	}
	if cfg.BackendType != api.EtcdV2 {
		fmt.Println("Error executing command: unsupported backend specified in config")
		os.Exit(1)
	}
	etcdcfg := cfg.BackendConfig.(*etcd.EtcdConfig)

	// Convert the nopools boolean to either an empty string or "true".
	noPoolsString := ""
	if nopools {
		noPoolsString = "true"
	}

	// Create a mapping of environment variables to values.
	envs := map[string]string{
		"HOSTNAME": name,
		"IP":       ipv4,
		"IP6":      ipv6,
		"CALICO_NETWORKING_BACKEND": backend,
		"AS":                        asNumber,
		"NO_DEFAULT_POOLS":          noPoolsString,
		"CALICO_LIBNETWORK_ENABLED": "true",
	}

	// Create a map of read only bindings.
	vols := map[string]string{
		logDir:                 "/var/log/calico",
		"/var/run/calico":      "/var/run/calico",
		"/lib/modules":         "/lib/modules",
		"/run/docker/plugins":  "/run/docker/plugins",
		"/var/run/docker.sock": "/var/run/docker.sock",
	}

	if etcdcfg.EtcdEndpoints == "" {
		envs["ETCD_AUTHORITY"] = etcdcfg.EtcdAuthority
		envs["ETCD_SCHEME"] = etcdcfg.EtcdScheme
		envs["ETCD_ENDPOINTS"] = ""
	} else {
		envs["ETCD_ENDPOINTS"] = etcdcfg.EtcdEndpoints
		envs["ETCD_AUTHORITY"] = ""
		envs["ETCD_SCHEME"] = ""
	}
	if etcdcfg.EtcdCACertFile != "" {
		envs["ETCD_CA_CERT_FILE"] = ETCD_CA_CERT_NODE_FILE
		vols[etcdcfg.EtcdCACertFile] = ETCD_CA_CERT_NODE_FILE
	}
	if etcdcfg.EtcdKeyFile != "" && etcdcfg.EtcdCertFile != "" {
		envs["ETCD_KEY_FILE"] = ETCD_KEY_NODE_FILE
		vols[etcdcfg.EtcdKeyFile] = ETCD_KEY_NODE_FILE
		envs["ETCD_CERT_FILE"] = ETCD_CERT_NODE_FILE
		vols[etcdcfg.EtcdCertFile] = ETCD_CERT_NODE_FILE
	}

	// Create the Docker command to execute (or display).  Start with the
	// fixed parts.
	cmd := []string{"docker", "run", "-d", "--net=host", "--privileged",
		"--name=calico-node"}

	// Add the environment variable pass-through.
	for k, v := range envs {
		cmd = append(cmd, "-e", fmt.Sprintf("%s=%s", k, v))
	}

	// Add the volume mounts.
	for k, v := range vols {
		cmd = append(cmd, "-v", fmt.Sprintf("%s:%s", k, v))
	}

	// Add the container image name
	cmd = append(cmd, img)

	if dryrun {
		fmt.Println("Use the following command to run Calico node:")
		fmt.Printf("\n%s\n\n", strings.Join(cmd, " "))
		fmt.Println("If you intend to run Calico node in an init system, such as systemd, remove")
		fmt.Println("the -d option so that the commands does not detach from the process.")
		return
	}

	// This is not a dry run.  Check that we are running as root.
	enforceRoot()

	// Normally, Felix will load the modules it needs, but when running inside a
	// container it might not be able to do so. Ensure the required modules are
	// loaded each time the node starts.
	// We only make a best effort attempt because the command may fail if the
	// modules are built in.
	if !runningInContainer() {
		log.Info("Running in container")
		loadModules()
		setupIPForwarding()
		setNFConntrackMax()
	}

	// Run the docker command.
	fmt.Println("Running the following command:")
	fmt.Printf("\n%s\n\n", strings.Join(cmd, " "))

	err = exec.Command(cmd[0], cmd[1:]...).Run()
	if err != nil {
		fmt.Println("Error executing command:")
		fmt.Println(err)
		os.Exit(1)
	}
}
コード例 #4
0
// Run function collects diagnostic information and logs
func Run(args []string) {
	var err error
	doc := fmt.Sprintf(`Usage:
  calicoctl node run [--ip=<IP>] [--ip6=<IP6>] [--as=<AS_NUM>]
                     [--name=<NAME>]
                     [--log-dir=<LOG_DIR>]
                     [--node-image=<DOCKER_IMAGE_NAME>]
                     [--backend=(bird|gobgp|none)]
                     [--config=<CONFIG>]
                     [--no-default-ippools]
                     [--dryrun]
                     [--init-system]
                     [--disable-docker-networking]
                     [--docker-networking-ifprefix=<IFPREFIX>]

Options:
  -h --help                Show this screen.
     --name=<NAME>         The name of the Calico node.  If this is not
                           supplied it defaults to the host name.
     --as=<AS_NUM>         Set the AS number for this node.  If omitted, it
                           will use the value configured on the node resource.
                           If there is no configured value and --as option is
                           omitted, the node will inherit the global AS number
                           (see 'calicoctl config' for details).
     --ip=<IP>             Set the local IPv4 routing address for this node.
                           If omitted, it will use the value configured on the
                           node resource.  If there is no configured value
                           and the --ip option is omitted, the node will
                           attempt to autodetect an IP address to use.  Use a
                           value of 'autodetect' to always force autodetection
                           of the IP each time the node starts.
     --ip6=<IP6>           Set the local IPv6 routing address for this node.
                           If omitted, it will use the value configured on the
                           node resource.  If there is no configured value
                           and the --ip6 option is omitted, the node will not
                           route IPv6.
     --log-dir=<LOG_DIR>   The directory containing Calico logs.
                           [default: /var/log/calico]
     --node-image=<DOCKER_IMAGE_NAME>
                           Docker image to use for Calico's per-node container.
                           [default: calico/node:%s]
     --backend=(bird|gobgp|none)
                           Specify which networking backend to use.  When set
                           to "none", Calico node runs in policy only mode.
                           The option to run with gobgp is currently
                           experimental.
                           [default: bird]
     --dryrun              Output the appropriate command, without starting the
                           container.
     --init-system         Run the appropriate command to use with an init
                           system.
     --no-default-ippools  Do not create default pools upon startup.
                           Default IP pools will be created if this is not set
                           and there are no pre-existing Calico IP pools.
     --disable-docker-networking
                           Disable Docker networking.
     --docker-networking-ifprefix=<IFPREFIX>
                           Interface prefix to use for the network interface
                           within the Docker containers that have been networked
                           by the Calico driver.
                           [default: cali]
  -c --config=<CONFIG>     Path to the file containing connection
                           configuration in YAML or JSON format.
                           [default: /etc/calico/calicoctl.cfg]

Description:
  This command is used to start a calico/node container instance which provides
  Calico networking and network policy on your compute host.
`, VERSION)
	arguments, err := docopt.Parse(doc, args, true, "", false, false)
	if err != nil {
		log.Info(err)
		fmt.Printf("Invalid option: 'calicoctl %s'. Use flag '--help' to read about a specific subcommand.\n", strings.Join(args, " "))
		os.Exit(1)
	}
	if len(arguments) == 0 {
		return
	}

	// Extract all the parameters.
	ipv4 := argutils.ArgStringOrBlank(arguments, "--ip")
	ipv6 := argutils.ArgStringOrBlank(arguments, "--ip6")
	logDir := argutils.ArgStringOrBlank(arguments, "--log-dir")
	asNumber := argutils.ArgStringOrBlank(arguments, "--as")
	img := argutils.ArgStringOrBlank(arguments, "--node-image")
	backend := argutils.ArgStringOrBlank(arguments, "--backend")
	dryrun := argutils.ArgBoolOrFalse(arguments, "--dryrun")
	name := argutils.ArgStringOrBlank(arguments, "--name")
	nopools := argutils.ArgBoolOrFalse(arguments, "--no-default-ippools")
	config := argutils.ArgStringOrBlank(arguments, "--config")
	disableDockerNw := argutils.ArgBoolOrFalse(arguments, "--disable-docker-networking")
	initSystem := argutils.ArgBoolOrFalse(arguments, "--init-system")
	ifprefix := argutils.ArgStringOrBlank(arguments, "--docker-networking-ifprefix")

	// Validate parameters.
	if ipv4 != "" && ipv4 != "autodetect" {
		ip := argutils.ValidateIP(ipv4)
		if ip.Version() != 4 {
			fmt.Println("Error executing command: --ip is wrong IP version")
			os.Exit(1)
		}
	}
	if ipv6 != "" {
		ip := argutils.ValidateIP(ipv6)
		if ip.Version() != 6 {
			fmt.Println("Error executing command: --ip6 is wrong IP version")
			os.Exit(1)
		}
	}
	if asNumber != "" {
		// The calico/node image does not accept dotted notation for
		// the AS number, so convert.
		asNumber = argutils.ValidateASNumber(asNumber).String()
	}

	if !backendMatch.MatchString(backend) {
		fmt.Printf("Error executing command: unknown backend '%s'\n", backend)
		os.Exit(1)
	}

	// Use the hostname if a name is not specified.  We should always
	// pass in a fixed value to the node container so that if the user
	// changes the hostname, the calico/node won't start using a different
	// name.
	if name == "" {
		name, err = os.Hostname()
		if err != nil || name == "" {
			fmt.Println("Error executing command: unable to determine node name")
			os.Exit(1)
		}
	}

	// Load the etcd configuraiton.
	cfg, err := clientmgr.LoadClientConfig(config)
	if err != nil {
		fmt.Println("Error executing command: invalid config file")
		os.Exit(1)
	}
	if cfg.Spec.DatastoreType != api.EtcdV2 {
		fmt.Println("Error executing command: unsupported backend specified in config")
		os.Exit(1)
	}
	etcdcfg := cfg.Spec.EtcdConfig

	// Convert the nopools boolean to either an empty string or "true".
	noPoolsString := ""
	if nopools {
		noPoolsString = "true"
	}

	// Create a mapping of environment variables to values.
	envs := map[string]string{
		"NODENAME":                  name,
		"CALICO_NETWORKING_BACKEND": backend,
		"NO_DEFAULT_POOLS":          noPoolsString,
		"CALICO_LIBNETWORK_ENABLED": fmt.Sprint(!disableDockerNw),
	}

	// Validate the ifprefix to only allow alphanumeric characters
	if !ifprefixMatch.MatchString(ifprefix) {
		fmt.Printf("Error executing command: invalid interface prefix '%s'\n", ifprefix)
		os.Exit(1)
	}

	// Set CALICO_LIBNETWORK_IFPREFIX env variable if Docker network is enabled.
	if !disableDockerNw {
		envs["CALICO_LIBNETWORK_IFPREFIX"] = ifprefix
	}

	// Add in optional environments.
	if asNumber != "" {
		envs["AS"] = asNumber
	}
	if ipv4 != "" {
		envs["IP"] = ipv4
	}
	if ipv6 != "" {
		envs["IP6"] = ipv6
	}

	// Create a map of read only bindings.
	vols := map[string]string{
		logDir:            "/var/log/calico",
		"/var/run/calico": "/var/run/calico",
		"/lib/modules":    "/lib/modules",
	}

	if !disableDockerNw {
		log.Info("Include docker networking volume mounts")
		vols["/run/docker/plugins"] = "/run/docker/plugins"
		vols["/var/run/docker.sock"] = "/var/run/docker.sock"
	}

	if etcdcfg.EtcdEndpoints == "" {
		envs["ETCD_AUTHORITY"] = etcdcfg.EtcdAuthority
		envs["ETCD_SCHEME"] = etcdcfg.EtcdScheme
		envs["ETCD_ENDPOINTS"] = ""
	} else {
		envs["ETCD_ENDPOINTS"] = etcdcfg.EtcdEndpoints
		envs["ETCD_AUTHORITY"] = ""
		envs["ETCD_SCHEME"] = ""
	}
	if etcdcfg.EtcdCACertFile != "" {
		envs["ETCD_CA_CERT_FILE"] = ETCD_CA_CERT_NODE_FILE
		vols[etcdcfg.EtcdCACertFile] = ETCD_CA_CERT_NODE_FILE
	}
	if etcdcfg.EtcdKeyFile != "" && etcdcfg.EtcdCertFile != "" {
		envs["ETCD_KEY_FILE"] = ETCD_KEY_NODE_FILE
		vols[etcdcfg.EtcdKeyFile] = ETCD_KEY_NODE_FILE
		envs["ETCD_CERT_FILE"] = ETCD_CERT_NODE_FILE
		vols[etcdcfg.EtcdCertFile] = ETCD_CERT_NODE_FILE
	}

	// Create the Docker command to execute (or display).  Start with the
	// fixed parts.  If this is not for an init system, we'll include the
	// detach flag (to prevent the command blocking), and use Dockers built
	// in restart mechanism.  If this is for an init-system we want the
	// command to remain attached and for Docker to remove the dead
	// container so that it can be restarted by the init system.
	cmd := []string{"docker", "run", "--net=host", "--privileged",
		"--name=calico-node"}
	if initSystem {
		cmd = append(cmd, "--rm")
	} else {
		cmd = append(cmd, "-d", "--restart=always")
	}

	// Add the environment variable pass-through.
	for k, v := range envs {
		cmd = append(cmd, "-e", fmt.Sprintf("%s=%s", k, v))
	}

	// Add the volume mounts.
	for k, v := range vols {
		cmd = append(cmd, "-v", fmt.Sprintf("%s:%s", k, v))
	}

	// Add the container image name
	cmd = append(cmd, img)

	if dryrun {
		fmt.Println("Use the following command to start the calico/node container:")
		fmt.Printf("\n%s\n\n", strings.Join(cmd, " "))

		if !initSystem {
			fmt.Println("If you are running calico/node in an init system, use the --init-system flag")
			fmt.Println("to display the appropriate start and stop commands.")
		} else {
			fmt.Println("Use the following command to stop the calico/node container:")
			fmt.Printf("\ndocker stop calico-node\n\n")
		}
		return
	}

	// This is not a dry run.  Check that we are running as root.
	enforceRoot()

	// Normally, Felix will load the modules it needs, but when running inside a
	// container it might not be able to do so. Ensure the required modules are
	// loaded each time the node starts.
	// We only make a best effort attempt because the command may fail if the
	// modules are built in.
	if !runningInContainer() {
		log.Info("Running in container")
		loadModules()
		setupIPForwarding()
		setNFConntrackMax()
	}

	// Make sure the calico-node is not already running before we attempt
	// to start the node.
	fmt.Println("Removing old calico-node container (if running).")
	err = exec.Command("docker", "rm", "-f", "calico-node").Run()
	if err != nil {
		log.WithError(err).Debug("Unable to remove calico-node container (ok if container was not running)")
	}

	// Run the docker command.
	fmt.Println("Running the following command to start calico-node:")
	fmt.Printf("\n%s\n\n", strings.Join(cmd, " "))
	fmt.Println("Image may take a short time to download if it is not available locally.")

	// Now execute the actual Docker run command and check for the
	// unable to find image message.
	err = exec.Command(cmd[0], cmd[1:]...).Run()
	if err != nil {
		fmt.Printf("Error executing command: %v\n", err)
		os.Exit(1)
	}

	// Create the command to follow the docker logs for the calico/node
	fmt.Println("Container started, checking progress logs.")
	logCmd := exec.Command("docker", "logs", "calico-node", "--follow")

	// Get the stdout pipe
	outPipe, err := logCmd.StdoutPipe()
	if err != nil {
		fmt.Printf("Error executing command:  unable to check calico/node logs: %v\n", err)
		os.Exit(1)
	}
	outScanner := bufio.NewScanner(outPipe)

	// Start following the logs.
	err = logCmd.Start()
	if err != nil {
		fmt.Printf("Error executing command:  unable to check calico/node logs: %v\n", err)
		fmt.Println(err)
		os.Exit(1)
	}

	// Protect against calico processes taking too long to start, or docker
	// logs hanging without output.
	time.AfterFunc(checkLogTimeout, func() {
		logCmd.Process.Kill()
	})

	// Read stdout until the node fails, or until we see the output
	// indicating success.
	started := false
	for outScanner.Scan() {
		line := outScanner.Text()
		fmt.Println(line)
		if line == "Calico node started successfully" {
			started = true
			break
		}
	}

	// If we didn't successfully start then notify the user.
	if outScanner.Err() != nil {
		fmt.Println("Error executing command: error reading calico/node logs, check logs for details")
	} else if !started {
		fmt.Println("Error executing command: calico/node has terminated, check logs for details")
	}

	// Kill the process if it is still running.
	logCmd.Process.Kill()
	logCmd.Wait()
}