2023-04-20 12:40:22 +00:00
|
|
|
// Copyright (c) HashiCorp, Inc.
|
|
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
|
2023-04-14 16:24:46 +00:00
|
|
|
package configentry
|
|
|
|
|
|
|
|
import (
|
|
|
|
"github.com/hashicorp/consul/acl"
|
|
|
|
"github.com/hashicorp/consul/agent/structs"
|
|
|
|
"github.com/hashicorp/consul/agent/submatview"
|
|
|
|
"github.com/hashicorp/consul/proto/private/pbcommon"
|
|
|
|
"github.com/hashicorp/consul/proto/private/pbconfigentry"
|
|
|
|
"github.com/hashicorp/consul/proto/private/pbsubscribe"
|
|
|
|
)
|
|
|
|
|
|
|
|
var _ submatview.View = (*ConfigEntryView)(nil)
|
|
|
|
|
|
|
|
// ConfigEntryView implements a submatview.View for a single config entry.
|
|
|
|
type ConfigEntryView struct {
|
|
|
|
state structs.ConfigEntry
|
|
|
|
}
|
|
|
|
|
|
|
|
// Reset resets the state to nil for the ConfigEntryView
|
|
|
|
func (v *ConfigEntryView) Reset() {
|
|
|
|
v.state = nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Result returns the structs.ConfigEntryResponse stored by this view.
|
|
|
|
func (v *ConfigEntryView) Result(index uint64) any {
|
|
|
|
return &structs.ConfigEntryResponse{
|
|
|
|
QueryMeta: structs.QueryMeta{
|
|
|
|
Index: index,
|
|
|
|
Backend: structs.QueryBackendStreaming,
|
|
|
|
},
|
|
|
|
Entry: v.state,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update updates the state containing a config entry based on events
|
|
|
|
func (v *ConfigEntryView) Update(events []*pbsubscribe.Event) error {
|
|
|
|
for _, event := range events {
|
|
|
|
update := event.GetConfigEntry()
|
|
|
|
if update == nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
switch update.Op {
|
|
|
|
case pbsubscribe.ConfigEntryUpdate_Delete:
|
|
|
|
v.state = nil
|
|
|
|
case pbsubscribe.ConfigEntryUpdate_Upsert:
|
|
|
|
v.state = pbconfigentry.ConfigEntryToStructs(update.ConfigEntry)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var _ submatview.View = (*ConfigEntryListView)(nil)
|
|
|
|
|
|
|
|
// ConfigEntryListView implements a submatview.View for a list of config entries
|
|
|
|
// that are all of the same kind (name is treated as unique).
|
|
|
|
type ConfigEntryListView struct {
|
|
|
|
kind string
|
|
|
|
entMeta acl.EnterpriseMeta
|
|
|
|
state map[string]structs.ConfigEntry
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewConfigEntryListView contructs a ConfigEntryListView based on the enterprise meta data and the kind
|
|
|
|
func NewConfigEntryListView(kind string, entMeta acl.EnterpriseMeta) *ConfigEntryListView {
|
|
|
|
view := &ConfigEntryListView{kind: kind, entMeta: entMeta}
|
|
|
|
view.Reset()
|
|
|
|
return view
|
|
|
|
}
|
|
|
|
|
|
|
|
// Reset resets the states of the list view to an empty map of Config Entries
|
|
|
|
func (v *ConfigEntryListView) Reset() {
|
|
|
|
v.state = make(map[string]structs.ConfigEntry)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Result returns the structs.IndexedConfigEntries stored by this view.
|
|
|
|
func (v *ConfigEntryListView) Result(index uint64) any {
|
|
|
|
entries := make([]structs.ConfigEntry, 0, len(v.state))
|
|
|
|
for _, entry := range v.state {
|
|
|
|
entries = append(entries, entry)
|
|
|
|
}
|
|
|
|
|
|
|
|
return &structs.IndexedConfigEntries{
|
|
|
|
Kind: v.kind,
|
|
|
|
Entries: entries,
|
|
|
|
QueryMeta: structs.QueryMeta{
|
|
|
|
Index: index,
|
|
|
|
Backend: structs.QueryBackendStreaming,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update updates the states containing a config entry based on events
|
|
|
|
func (v *ConfigEntryListView) Update(events []*pbsubscribe.Event) error {
|
|
|
|
for _, event := range filterByEnterpriseMeta(events, v.entMeta) {
|
|
|
|
update := event.GetConfigEntry()
|
|
|
|
configEntry := pbconfigentry.ConfigEntryToStructs(update.ConfigEntry)
|
|
|
|
name := structs.NewServiceName(configEntry.GetName(), configEntry.GetEnterpriseMeta()).String()
|
|
|
|
|
|
|
|
switch update.Op {
|
|
|
|
case pbsubscribe.ConfigEntryUpdate_Delete:
|
|
|
|
delete(v.state, name)
|
|
|
|
case pbsubscribe.ConfigEntryUpdate_Upsert:
|
|
|
|
v.state[name] = configEntry
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// filterByEnterpriseMeta filters the given set of events to remove those that
|
|
|
|
// don't match the request's enterprise meta - this is necessary because when
|
|
|
|
// subscribing to a topic with SubjectWildcard we'll get events for resources
|
|
|
|
// in all partitions and namespaces.
|
|
|
|
func filterByEnterpriseMeta(events []*pbsubscribe.Event, entMeta acl.EnterpriseMeta) []*pbsubscribe.Event {
|
|
|
|
partition := entMeta.PartitionOrDefault()
|
|
|
|
namespace := entMeta.NamespaceOrDefault()
|
|
|
|
|
|
|
|
filtered := make([]*pbsubscribe.Event, 0, len(events))
|
|
|
|
for _, event := range events {
|
|
|
|
var eventEntMeta *pbcommon.EnterpriseMeta
|
|
|
|
switch payload := event.Payload.(type) {
|
|
|
|
case *pbsubscribe.Event_ConfigEntry:
|
|
|
|
eventEntMeta = payload.ConfigEntry.ConfigEntry.GetEnterpriseMeta()
|
|
|
|
case *pbsubscribe.Event_Service:
|
|
|
|
eventEntMeta = payload.Service.GetEnterpriseMeta()
|
|
|
|
default:
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if partition != acl.WildcardName && !acl.EqualPartitions(partition, eventEntMeta.GetPartition()) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if namespace != acl.WildcardName && !acl.EqualNamespaces(namespace, eventEntMeta.GetNamespace()) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
filtered = append(filtered, event)
|
|
|
|
}
|
|
|
|
return filtered
|
|
|
|
}
|