diff --git a/.changelog/8575.txt b/.changelog/8575.txt new file mode 100644 index 0000000000..5aef310441 --- /dev/null +++ b/.changelog/8575.txt @@ -0,0 +1,11 @@ +```release-note:improvement +api: Added constants for common tag keys and values in the `Tags` field of the `AgentMember` struct. +``` + +```release-note:improvement +api: Added `IsConsulServer` method to the `AgentMember` type to easily determine whether the agent is a server. +``` + +```release-note:improvement +api: Added `ACLMode` method to the `AgentMember` type to determine what ACL mode the agent is operating in. +``` diff --git a/api/agent.go b/api/agent.go index 8e0ae5e507..b707265962 100644 --- a/api/agent.go +++ b/api/agent.go @@ -121,6 +121,70 @@ type AgentServiceConnectProxyConfig struct { Expose ExposeConfig `json:",omitempty"` } +const ( + // MemberTagKeyACLMode is the key used to indicate what ACL mode the agent is + // operating in. The values of this key will be one of the MemberACLMode constants + // with the key not being present indicating ACLModeUnknown. + MemberTagKeyACLMode = "acls" + + // MemberTagRole is the key used to indicate that the member is a server or not. + MemberTagKeyRole = "role" + + // MemberTagValueRoleServer is the value of the MemberTagKeyRole used to indicate + // that the member represents a Consul server. + MemberTagValueRoleServer = "consul" + + // MemberTagKeySegment is the key name of the tag used to indicate which network + // segment this member is in. + // Network Segments are a Consul Enterprise feature. + MemberTagKeySegment = "segment" + + // MemberTagKeyBootstrap is the key name of the tag used to indicate whether this + // agent was started with the "bootstrap" configuration enabled + MemberTagKeyBootstrap = "bootstrap" + // MemberTagValueBootstrap is the value of the MemberTagKeyBootstrap key when the + // agent was started with the "bootstrap" configuration enabled. + MemberTagValueBootstrap = "1" + + // MemberTagKeyBootstrapExpect is the key name of the tag used to indicate whether + // this agent was started with the "bootstrap_expect" configuration set to a non-zero + // value. The value of this key will be the string for of that configuration value. + MemberTagKeyBootstrapExpect = "expect" + + // MemberTagKeyUseTLS is the key name of the tag used to indicate whther this agent + // was configured to use TLS. + MemberTagKeyUseTLS = "use_tls" + // MemberTagValueUseTLS is the value of the MemberTagKeyUseTLS when the agent was + // configured to use TLS. Any other value indicates that it was not setup in + // that manner. + MemberTagValueUseTLS = "1" + + // MemberTagKeyReadReplica is the key used to indicate that the member is a read + // replica server (will remain a Raft non-voter). + // Read Replicas are a Consul Enterprise feature. + MemberTagKeyReadReplica = "nonvoter" + // MemberTagValueReadReplica is the value of the MemberTagKeyReadReplica key when + // the member is in fact a read-replica. Any other value indicates that it is not. + // Read Replicas are a Consul Enterprise feature. + MemberTagValueReadReplica = "1" +) + +type MemberACLMode string + +const ( + // ACLModeDisables indicates that ACLs are disabled for this agent + ACLModeDisabled MemberACLMode = "0" + // ACLModeEnabled indicates that ACLs are enabled and operating in new ACL + // mode (v1.4.0+ ACLs) + ACLModeEnabled MemberACLMode = "1" + // ACLModeLegacy indicates that ACLs are enabled and operating in legacy mode. + ACLModeLegacy MemberACLMode = "2" + // ACLModeUnkown is used to indicate that the AgentMember.Tags didn't advertise + // an ACL mode at all. This is the case for Consul versions before v1.4.0 and + // should be treated similarly to ACLModeLegacy. + ACLModeUnknown MemberACLMode = "3" +) + // AgentMember represents a cluster member known to the agent type AgentMember struct { Name string @@ -144,6 +208,30 @@ type AgentMember struct { DelegateCur uint8 } +// ACLMode returns the ACL mode this agent is operating in. +func (m *AgentMember) ACLMode() MemberACLMode { + mode := m.Tags[MemberTagKeyACLMode] + + // the key may not have existed but then an + // empty string will be returned and we will + // handle that in the default case of the switch + switch MemberACLMode(mode) { + case ACLModeDisabled: + return ACLModeDisabled + case ACLModeEnabled: + return ACLModeEnabled + case ACLModeLegacy: + return ACLModeLegacy + default: + return ACLModeUnknown + } +} + +// IsConsulServer returns true when this member is a Consul server. +func (m *AgentMember) IsConsulServer() bool { + return m.Tags[MemberTagKeyRole] == MemberTagValueRoleServer +} + // AllSegments is used to select for all segments in MembersOpts. const AllSegments = "_all" diff --git a/api/agent_test.go b/api/agent_test.go index dd96491ccc..b0b7701a27 100644 --- a/api/agent_test.go +++ b/api/agent_test.go @@ -1798,3 +1798,91 @@ func TestAgentService_ExposeChecks(t *testing.T) { require.True(t, svc.Proxy.Expose.Checks) require.Equal(t, path, svc.Proxy.Expose.Paths[0]) } + +func TestMemberACLMode(t *testing.T) { + type testCase struct { + tagValue string + expectedMode MemberACLMode + } + + cases := map[string]testCase{ + "disabled": { + tagValue: "0", + expectedMode: ACLModeDisabled, + }, + "enabled": { + tagValue: "1", + expectedMode: ACLModeEnabled, + }, + "legacy": { + tagValue: "2", + expectedMode: ACLModeLegacy, + }, + "unknown-3": { + tagValue: "3", + expectedMode: ACLModeUnknown, + }, + "unknown-other": { + tagValue: "77", + expectedMode: ACLModeUnknown, + }, + "unknown-not-present": { + tagValue: "", + expectedMode: ACLModeUnknown, + }, + } + + for name, tcase := range cases { + t.Run(name, func(t *testing.T) { + tags := map[string]string{} + + if tcase.tagValue != "" { + tags[MemberTagKeyACLMode] = tcase.tagValue + } + + m := AgentMember{ + Tags: tags, + } + + require.Equal(t, tcase.expectedMode, m.ACLMode()) + }) + } +} + +func TestMemberIsConsulServer(t *testing.T) { + type testCase struct { + tagValue string + isServer bool + } + + cases := map[string]testCase{ + "not-present": { + tagValue: "", + isServer: false, + }, + "server": { + tagValue: MemberTagValueRoleServer, + isServer: true, + }, + "client": { + tagValue: "client", + isServer: false, + }, + } + + for name, tcase := range cases { + t.Run(name, func(t *testing.T) { + tags := map[string]string{} + + if tcase.tagValue != "" { + tags[MemberTagKeyRole] = tcase.tagValue + } + + m := AgentMember{ + Tags: tags, + } + + require.Equal(t, tcase.isServer, m.IsConsulServer()) + }) + } +}