Example #1
0
// IPAM takes keyword with an IP address then calls the subcommands.
func Show(args []string) {
	doc := constants.DatastoreIntro + `Usage:
  calicoctl ipam show --ip=<IP> [--config=<CONFIG>]

Options:
  -h --help             Show this screen.
     --ip=<IP>          IP address to show.
  -c --config=<CONFIG>  Path to the file containing connection configuration in
                        YAML or JSON format.
                        [default: /etc/calico/calicoctl.cfg]

Description:
  The ipam show command prints information about a given IP address, such as
  special attributes defined for the IP or whether the IP has been reserved by
  a user of the Calico IP Address Manager.
`
	parsedArgs, err := docopt.Parse(doc, args, true, "", 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
	}

	// Create a new backend client from env vars.
	cf := parsedArgs["--config"].(string)
	client, err := clientmgr.NewClient(cf)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	ipamClient := client.IPAM()
	passedIP := parsedArgs["--ip"].(string)
	ip := argutils.ValidateIP(passedIP)
	attr, err := ipamClient.GetAssignmentAttributes(ip)

	// IP address is not assigned, this prints message like
	// `IP 192.168.71.1 is not assigned in block`. This is not exactly an error,
	// so not returning it to the caller.
	if err != nil {
		fmt.Println(err)
		return
	}

	// IP address is assigned with attributes.
	if len(attr) != 0 {
		fmt.Println(attr)
	} else {
		// IP address is assigned but attributes are not set.
		fmt.Printf("No attributes defined for IP %s\n", ip)
	}
}
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
}
// executeConfigCommand is main function called by all of the resource management commands
// in calicoctl (apply, create, replace, get and delete).  This provides common function
// for all these commands:
// 	-  Load resources from file (or if not specified determine the resource from
// 	   the command line options).
// 	-  Convert the loaded resources into a list of resources (easier to handle)
// 	-  Process each resource individually, fanning out to the appropriate methods on
//	   the client interface, collate results and exit on the first error.
func executeConfigCommand(args map[string]interface{}, action action) commandResults {
	var r interface{}
	var err error
	var resources []unversioned.Resource

	log.Info("Executing config command")

	if filename := args["--filename"]; filename != nil {
		// Filename is specified, load the resource from file and convert to a slice
		// of resources for easier handling.
		if r, err = resourcemgr.CreateResourcesFromFile(filename.(string)); err != nil {
			return commandResults{err: err, fileInvalid: true}
		}

		resources = convertToSliceOfResources(r)
	} else if r, err := getResourceFromArguments(args); err != nil {
		// Filename is not specific so extract the resource from the arguments.  This
		// is only useful for delete and get functions - but we don't need to check that
		// here since the command syntax requires a filename for the other resource
		// management commands.
		return commandResults{err: err}
	} else {
		// We extracted a single resource type with identifiers from the CLI, convert to
		// a list for simpler handling.
		resources = []unversioned.Resource{r}
	}

	if len(resources) == 0 {
		return commandResults{err: errors.New("no resources specified")}
	}

	if log.GetLevel() >= log.DebugLevel {
		log.Debugf("Resources: %v", resources)
		d, err := yaml.Marshal(resources)
		if err != nil {
			return commandResults{err: err}
		}
		log.Debugf("Data: %s", string(d))
	}

	// Load the client config and connect.
	cf := args["--config"].(string)
	client, err := clientmgr.NewClient(cf)
	if err != nil {
		return commandResults{err: err}
	}
	log.Infof("Client: %v", client)

	// Initialise the command results with the number of resources and the name of the
	// kind of resource (if only dealing with a single resource).
	var results commandResults
	var kind string
	count := make(map[string]int)
	for _, r := range resources {
		kind = r.GetTypeMetadata().Kind
		count[kind] = count[kind] + 1
		results.numResources = results.numResources + 1
	}
	if len(count) == 1 {
		results.singleKind = kind
	}

	// Now execute the command on each resource in order, exiting as soon as we hit an
	// error.
	for _, r := range resources {
		r, err = executeResourceAction(args, client, r, action)
		if err != nil {
			results.err = err
			break
		}
		results.resources = append(results.resources, r)
		results.numHandled = results.numHandled + 1
	}

	return results
}
// IPAM takes keyword with an IP address then calls the subcommands.
func Release(args []string) {
	doc := constants.DatastoreIntro + `Usage:
  calicoctl ipam release --ip=<IP> [--config=<CONFIG>]

Options:
  -h --help             Show this screen.
     --ip=<IP>          IP address to release.
  -c --config=<CONFIG>  Path to the file containing connection configuration in
                        YAML or JSON format.
                        [default: /etc/calico/calicoctl.cfg]

Description:
  The ipam release command releases an IP address from the Calico IP Address
  Manager that was been previously assigned to an endpoint.  When an IP address
  is released, it becomes available for assignment to any endpoint.

  Note that this does not remove the IP from any existing endpoints that may be
  using it, so only use this command to clean up addresses from endpoints that
  were not cleanly removed from Calico.
`
	parsedArgs, err := docopt.Parse(doc, args, true, "", 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
	}

	// Create a new backend client from env vars.
	cf := parsedArgs["--config"].(string)
	client, err := clientmgr.NewClient(cf)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	ipamClient := client.IPAM()
	passedIP := parsedArgs["--ip"].(string)

	ip := argutils.ValidateIP(passedIP)
	ips := []net.IP{ip}

	// Call ReleaseIPs releases the IP and returns an empty slice as unallocatedIPs if
	// release was successful else it returns back the slice with the IP passed in.
	unallocatedIPs, err := ipamClient.ReleaseIPs(ips)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		os.Exit(1)
	}

	// Couldn't release the IP if the slice is not empty or IP might already be released/unassigned.
	// This is not exactly an error, so not returning it to the caller.
	if len(unallocatedIPs) != 0 {
		fmt.Printf("IP address %s is not assigned\n", ip)
		os.Exit(1)
	}

	// If unallocatedIPs slice is empty then IP was released Successfully.
	fmt.Printf("Successfully released IP address %s\n", ip)
}