hashicorp-copywrite[bot] 5fb9df1640
[COMPLIANCE] License changes (#18443)
* Adding explicit MPL license for sub-package

This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository.

* Adding explicit MPL license for sub-package

This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository.

* Updating the license from MPL to Business Source License

Going forward, this project will be licensed under the Business Source License v1.1. Please see our blog post for more details at <Blog URL>, FAQ at www.hashicorp.com/licensing-faq, and details of the license at www.hashicorp.com/bsl.

* add missing license headers

* Update copyright file headers to BUSL-1.1

* Update copyright file headers to BUSL-1.1

* Update copyright file headers to BUSL-1.1

* Update copyright file headers to BUSL-1.1

* Update copyright file headers to BUSL-1.1

* Update copyright file headers to BUSL-1.1

* Update copyright file headers to BUSL-1.1

* Update copyright file headers to BUSL-1.1

* Update copyright file headers to BUSL-1.1

* Update copyright file headers to BUSL-1.1

* Update copyright file headers to BUSL-1.1

* Update copyright file headers to BUSL-1.1

* Update copyright file headers to BUSL-1.1

* Update copyright file headers to BUSL-1.1

* Update copyright file headers to BUSL-1.1

---------

Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com>
2023-08-11 09:12:13 -04:00

264 lines
7.1 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
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.
`
)