-
Notifications
You must be signed in to change notification settings - Fork 4.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Sujata's peering-cli branch * Added error message for connecting to cluster * We can export service to peer * export handling multiple peers * export handles multiple peers * export now can handle multiple services * Export after 1st cleanup * Successful export * Added the namespace option * Add .changelog entry * go mod tidy * Stub unit tests for peering export command * added export in peering.go * Adding export_test * Moved the code to services from peers and cleaned the serviceNamespace * Added support for exporting to partitions * Fixed partition bug * Added unit tests for export command * Add multi-tenancy flags * gofmt * Add some helpful comments * Exclude namespace + partition flags when running OSS * cleaned up partition stuff * Validate required flags differently for OSS vs. ENT * Update success output to include only the requested consumers * cleaned up * fixed broken test * gofmt * Include all flags in OSS build * Remove example previously added to peering command * Move stray import into correct block * Update changelog entry to include support for exporting to a partition * Add required-ness label to consumer-peers flag description * Update command/services/export/export.go Co-authored-by: Dan Stough <[email protected]> * Add docs placeholder for new services export command * Moved piece of code to OSS * Break config entry init + update into separate functions * fixed * Vary existing service export comparison for OSS vs. ENT * Move OSS-specific test to export_oss_test.go * Set config entry name based on partition being exported from * Set namespace on added services * Adding namespace * Remove export documentation We will include documentation in a followup PR * Consolidate code from export_oss into export.go * Consolidated export_oss_test.go and export_test.go * Add example of partition export to command synopsis * Allow empty peers flag if partitions flag provided * Add test coverage for -consumer-partitions flag * Update command/services/export/export.go Co-authored-by: Jared Kirschner <[email protected]> * Update command/services/export/export.go Co-authored-by: Jared Kirschner <[email protected]> * Update changelog entry * Use "cluster peers" to clear up any possible confusion * Update test assertions --------- Co-authored-by: 20sr20 <[email protected]> Co-authored-by: Dan Stough <[email protected]> Co-authored-by: Jared Kirschner <[email protected]>
- Loading branch information
1 parent
da94cbd
commit b438a07
Showing
5 changed files
with
422 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
```release-note:feature | ||
cli: Adds new command - `consul services export` - for exporting a service to a peer or partition | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,260 @@ | ||
package export | ||
|
||
import ( | ||
"errors" | ||
"flag" | ||
"fmt" | ||
"strings" | ||
|
||
"github.com/mitchellh/cli" | ||
|
||
"github.com/hashicorp/consul/agent" | ||
"github.com/hashicorp/consul/api" | ||
"github.com/hashicorp/consul/command/flags" | ||
) | ||
|
||
func New(ui cli.Ui) *cmd { | ||
c := &cmd{UI: ui} | ||
c.init() | ||
return c | ||
} | ||
|
||
type cmd struct { | ||
UI cli.Ui | ||
flags *flag.FlagSet | ||
http *flags.HTTPFlags | ||
help string | ||
|
||
serviceName string | ||
peerNames string | ||
partitionNames string | ||
} | ||
|
||
func (c *cmd) init() { | ||
c.flags = flag.NewFlagSet("", flag.ContinueOnError) | ||
|
||
c.flags.StringVar(&c.serviceName, "name", "", "(Required) Specify the name of the service you want to export.") | ||
c.flags.StringVar(&c.peerNames, "consumer-peers", "", "(Required) A comma-separated list of cluster peers to export the service to. In Consul Enterprise, this flag is optional if -consumer-partitions is specified.") | ||
c.flags.StringVar(&c.partitionNames, "consumer-partitions", "", "(Enterprise only) A comma-separated list of admin partitions within the same datacenter to export the service to. This flag is optional if -consumer-peers is specified.") | ||
|
||
c.http = &flags.HTTPFlags{} | ||
flags.Merge(c.flags, c.http.ClientFlags()) | ||
flags.Merge(c.flags, c.http.MultiTenancyFlags()) | ||
c.help = flags.Usage(help, c.flags) | ||
} | ||
|
||
func (c *cmd) Run(args []string) int { | ||
if err := c.flags.Parse(args); err != nil { | ||
return 1 | ||
} | ||
|
||
if err := c.validateFlags(); err != nil { | ||
c.UI.Error(err.Error()) | ||
return 1 | ||
} | ||
|
||
peerNames, err := c.getPeerNames() | ||
if err != nil { | ||
c.UI.Error(err.Error()) | ||
return 1 | ||
} | ||
|
||
partitionNames, err := c.getPartitionNames() | ||
if err != nil { | ||
c.UI.Error(err.Error()) | ||
return 1 | ||
} | ||
|
||
client, err := c.http.APIClient() | ||
if err != nil { | ||
c.UI.Error(fmt.Sprintf("Error connect to Consul agent: %s", err)) | ||
return 1 | ||
} | ||
|
||
// Name matches partition, so "default" if none specified | ||
cfgName := "default" | ||
if c.http.Partition() != "" { | ||
cfgName = c.http.Partition() | ||
} | ||
|
||
entry, _, err := client.ConfigEntries().Get(api.ExportedServices, cfgName, &api.QueryOptions{Namespace: ""}) | ||
if err != nil && !strings.Contains(err.Error(), agent.ConfigEntryNotFoundErr) { | ||
c.UI.Error(fmt.Sprintf("Error reading config entry %s/%s: %v", "exported-services", "default", err)) | ||
return 1 | ||
} | ||
|
||
var cfg *api.ExportedServicesConfigEntry | ||
if entry == nil { | ||
cfg = c.initializeConfigEntry(cfgName, peerNames, partitionNames) | ||
} else { | ||
existingCfg, ok := entry.(*api.ExportedServicesConfigEntry) | ||
if !ok { | ||
c.UI.Error(fmt.Sprintf("Existing config entry has incorrect type: %t", entry)) | ||
return 1 | ||
} | ||
|
||
cfg = c.updateConfigEntry(existingCfg, peerNames, partitionNames) | ||
} | ||
|
||
ok, _, err := client.ConfigEntries().CAS(cfg, cfg.GetModifyIndex(), nil) | ||
if err != nil { | ||
c.UI.Error(fmt.Sprintf("Error writing config entry: %s", err)) | ||
return 1 | ||
} else if !ok { | ||
c.UI.Error(fmt.Sprintf("Config entry was changed during update. Please try again")) | ||
return 1 | ||
} | ||
|
||
switch { | ||
case len(c.peerNames) > 0 && len(c.partitionNames) > 0: | ||
c.UI.Info(fmt.Sprintf("Successfully exported service %q to cluster peers %q and to partitions %q", c.serviceName, c.peerNames, c.partitionNames)) | ||
case len(c.peerNames) > 0: | ||
c.UI.Info(fmt.Sprintf("Successfully exported service %q to cluster peers %q", c.serviceName, c.peerNames)) | ||
case len(c.partitionNames) > 0: | ||
c.UI.Info(fmt.Sprintf("Successfully exported service %q to partitions %q", c.serviceName, c.partitionNames)) | ||
} | ||
|
||
return 0 | ||
} | ||
|
||
func (c *cmd) validateFlags() error { | ||
if c.serviceName == "" { | ||
return errors.New("Missing the required -name flag") | ||
} | ||
|
||
if c.peerNames == "" && c.partitionNames == "" { | ||
return errors.New("Missing the required -consumer-peers or -consumer-partitions flag") | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (c *cmd) getPeerNames() ([]string, error) { | ||
var peerNames []string | ||
if c.peerNames != "" { | ||
peerNames = strings.Split(c.peerNames, ",") | ||
for _, peerName := range peerNames { | ||
if peerName == "" { | ||
return nil, fmt.Errorf("Invalid peer %q", peerName) | ||
} | ||
} | ||
} | ||
return peerNames, nil | ||
} | ||
|
||
func (c *cmd) getPartitionNames() ([]string, error) { | ||
var partitionNames []string | ||
if c.partitionNames != "" { | ||
partitionNames = strings.Split(c.partitionNames, ",") | ||
for _, partitionName := range partitionNames { | ||
if partitionName == "" { | ||
return nil, fmt.Errorf("Invalid partition %q", partitionName) | ||
} | ||
} | ||
} | ||
return partitionNames, nil | ||
} | ||
|
||
func (c *cmd) initializeConfigEntry(cfgName string, peerNames, partitionNames []string) *api.ExportedServicesConfigEntry { | ||
return &api.ExportedServicesConfigEntry{ | ||
Name: cfgName, | ||
Services: []api.ExportedService{ | ||
{ | ||
Name: c.serviceName, | ||
Namespace: c.http.Namespace(), | ||
Consumers: buildConsumers(peerNames, partitionNames), | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
func (c *cmd) updateConfigEntry(cfg *api.ExportedServicesConfigEntry, peerNames, partitionNames []string) *api.ExportedServicesConfigEntry { | ||
serviceExists := false | ||
|
||
for i, service := range cfg.Services { | ||
if service.Name == c.serviceName && service.Namespace == c.http.Namespace() { | ||
serviceExists = true | ||
|
||
// Add a consumer for each peer where one doesn't already exist | ||
for _, peerName := range peerNames { | ||
peerExists := false | ||
for _, consumer := range service.Consumers { | ||
if consumer.Peer == peerName { | ||
peerExists = true | ||
break | ||
} | ||
} | ||
if !peerExists { | ||
cfg.Services[i].Consumers = append(cfg.Services[i].Consumers, api.ServiceConsumer{Peer: peerName}) | ||
} | ||
} | ||
|
||
// Add a consumer for each partition where one doesn't already exist | ||
for _, partitionName := range partitionNames { | ||
partitionExists := false | ||
|
||
for _, consumer := range service.Consumers { | ||
if consumer.Partition == partitionName { | ||
partitionExists = true | ||
break | ||
} | ||
} | ||
if !partitionExists { | ||
cfg.Services[i].Consumers = append(cfg.Services[i].Consumers, api.ServiceConsumer{Partition: partitionName}) | ||
} | ||
} | ||
} | ||
} | ||
|
||
if !serviceExists { | ||
cfg.Services = append(cfg.Services, api.ExportedService{ | ||
Name: c.serviceName, | ||
Namespace: c.http.Namespace(), | ||
Consumers: buildConsumers(peerNames, partitionNames), | ||
}) | ||
} | ||
|
||
return cfg | ||
} | ||
|
||
func buildConsumers(peerNames []string, partitionNames []string) []api.ServiceConsumer { | ||
var consumers []api.ServiceConsumer | ||
for _, peer := range peerNames { | ||
consumers = append(consumers, api.ServiceConsumer{ | ||
Peer: peer, | ||
}) | ||
} | ||
for _, partition := range partitionNames { | ||
consumers = append(consumers, api.ServiceConsumer{ | ||
Partition: partition, | ||
}) | ||
} | ||
return consumers | ||
} | ||
|
||
//======== | ||
|
||
func (c *cmd) Synopsis() string { | ||
return synopsis | ||
} | ||
|
||
func (c *cmd) Help() string { | ||
return flags.Usage(c.help, nil) | ||
} | ||
|
||
const ( | ||
synopsis = "Export a service from one peer or admin partition to another" | ||
help = ` | ||
Usage: consul services export [options] -name <service name> -consumer-peers <other cluster name> | ||
Export a service to a peered cluster. | ||
$ consul services export -name=web -consumer-peers=other-cluster | ||
Use the -consumer-partitions flag instead of -consumer-peers to export to a different partition in the same cluster. | ||
$ consul services export -name=web -consumer-partitions=other-partition | ||
Additional flags and more advanced use cases are detailed below. | ||
` | ||
) |
Oops, something went wrong.