You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
consul/agent/structs/config_entry_exports.go

208 lines
5.7 KiB

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package structs
import (
"encoding/json"
"fmt"
"github.com/hashicorp/consul/acl"
)
// ExportedServicesConfigEntry is the top-level struct for exporting a service to be exposed
// across other admin partitions.
type ExportedServicesConfigEntry struct {
Name string
// Services is a list of services to be exported and the list of partitions
// to expose them to.
Services []ExportedService `json:",omitempty"`
Meta map[string]string `json:",omitempty"`
Hash uint64 `json:",omitempty" hash:"ignore"`
acl.EnterpriseMeta `hcl:",squash" mapstructure:",squash"`
RaftIndex `hash:"ignore"`
}
func (e *ExportedServicesConfigEntry) SetHash(h uint64) {
e.Hash = h
}
func (e *ExportedServicesConfigEntry) GetHash() uint64 {
return e.Hash
}
// ExportedService manages the exporting of a service in the local partition to
// other partitions.
type ExportedService struct {
// Name is the name of the service to be exported.
Name string
// Namespace is the namespace to export the service from.
Namespace string `json:",omitempty"`
// Consumers is a list of downstream consumers of the service to be exported.
Consumers []ServiceConsumer `json:",omitempty"`
}
// ServiceConsumer represents a downstream consumer of the service to be exported.
// At most one of Partition or Peer must be specified.
type ServiceConsumer struct {
// Partition is the admin partition to export the service to.
// Deprecated: Peer should be used for both remote peers and local partitions.
Partition string `json:",omitempty"`
// Peer is the name of the peer to export the service to.
Peer string `json:",omitempty" alias:"peer_name"`
}
func (e *ExportedServicesConfigEntry) ToMap() map[string]map[string][]string {
resp := make(map[string]map[string][]string)
for _, svc := range e.Services {
if _, ok := resp[svc.Namespace]; !ok {
resp[svc.Namespace] = make(map[string][]string)
}
if _, ok := resp[svc.Namespace][svc.Name]; !ok {
consumers := make([]string, 0, len(svc.Consumers))
for _, c := range svc.Consumers {
consumers = append(consumers, c.Partition)
}
resp[svc.Namespace][svc.Name] = consumers
resp[svc.Namespace][svc.Name+SidecarProxySuffix] = consumers
}
}
return resp
}
func (e *ExportedServicesConfigEntry) Clone() *ExportedServicesConfigEntry {
e2 := *e
e2.Services = make([]ExportedService, len(e.Services))
for _, svc := range e.Services {
exportedSvc := svc
exportedSvc.Consumers = make([]ServiceConsumer, len(svc.Consumers))
for _, consumer := range svc.Consumers {
exportedSvc.Consumers = append(exportedSvc.Consumers, consumer)
}
e2.Services = append(e2.Services, exportedSvc)
}
return &e2
}
func (e *ExportedServicesConfigEntry) GetKind() string {
return ExportedServices
}
func (e *ExportedServicesConfigEntry) GetName() string {
if e == nil {
return ""
}
return e.Name
}
func (e *ExportedServicesConfigEntry) GetMeta() map[string]string {
if e == nil {
return nil
}
return e.Meta
}
func (e *ExportedServicesConfigEntry) Normalize() error {
if e == nil {
return fmt.Errorf("config entry is nil")
}
e.EnterpriseMeta = *DefaultEnterpriseMetaInPartition(e.Name)
e.EnterpriseMeta.Normalize()
for i := range e.Services {
e.Services[i].Namespace = acl.NormalizeNamespace(e.Services[i].Namespace)
}
h, err := HashConfigEntry(e)
if err != nil {
return err
}
e.Hash = h
return nil
}
func (e *ExportedServicesConfigEntry) Validate() error {
if err := validateExportedServicesName(e.Name); err != nil {
return err
}
if err := validateConfigEntryMeta(e.Meta); err != nil {
return err
}
for i, svc := range e.Services {
if svc.Name == "" {
return fmt.Errorf("Services[%d]: service name cannot be empty", i)
}
if svc.Namespace == WildcardSpecifier && svc.Name != WildcardSpecifier {
return fmt.Errorf("Services[%d]: service name must be wildcard if namespace is wildcard", i)
}
if len(svc.Consumers) == 0 {
return fmt.Errorf("Services[%d]: must have at least one consumer", i)
}
for j, consumer := range svc.Consumers {
if consumer.Peer != "" && consumer.Partition != "" {
return fmt.Errorf("Services[%d].Consumers[%d]: must define at most one of Peer or Partition", i, j)
}
if consumer.Partition == WildcardSpecifier {
return fmt.Errorf("Services[%d].Consumers[%d]: exporting to all partitions (wildcard) is not supported", i, j)
}
if consumer.Peer == WildcardSpecifier {
return fmt.Errorf("Services[%d].Consumers[%d]: exporting to all peers (wildcard) is not supported", i, j)
}
}
}
return nil
}
func (e *ExportedServicesConfigEntry) CanRead(authz acl.Authorizer) error {
var authzContext acl.AuthorizerContext
e.FillAuthzContext(&authzContext)
return authz.ToAllowAuthorizer().MeshReadAllowed(&authzContext)
}
func (e *ExportedServicesConfigEntry) CanWrite(authz acl.Authorizer) error {
var authzContext acl.AuthorizerContext
e.FillAuthzContext(&authzContext)
return authz.ToAllowAuthorizer().MeshWriteAllowed(&authzContext)
}
func (e *ExportedServicesConfigEntry) GetRaftIndex() *RaftIndex {
if e == nil {
return &RaftIndex{}
}
return &e.RaftIndex
}
func (e *ExportedServicesConfigEntry) GetEnterpriseMeta() *acl.EnterpriseMeta {
if e == nil {
return nil
}
return &e.EnterpriseMeta
}
// MarshalJSON adds the Kind field so that the JSON can be decoded back into the
// correct type.
// This method is implemented on the structs type (as apposed to the api type)
// because that is what the API currently uses to return a response.
func (e *ExportedServicesConfigEntry) MarshalJSON() ([]byte, error) {
type Alias ExportedServicesConfigEntry
source := &struct {
Kind string
*Alias
}{
Kind: ExportedServices,
Alias: (*Alias)(e),
}
return json.Marshal(source)
}