diff --git a/agent/agent.go b/agent/agent.go index e4b95ff993ff..fcc7b510bbfe 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -26,7 +26,6 @@ import ( "github.com/armon/go-metrics/prometheus" "github.com/rboyer/safeio" "golang.org/x/net/http2" - "golang.org/x/net/http2/h2c" "google.golang.org/grpc" "google.golang.org/grpc/keepalive" @@ -34,7 +33,6 @@ import ( "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-memdb" "github.com/hashicorp/go-multierror" - "github.com/hashicorp/hcp-scada-provider/capability" "github.com/hashicorp/raft" "github.com/hashicorp/serf/serf" @@ -51,7 +49,6 @@ import ( external "github.com/hashicorp/consul/agent/grpc-external" grpcDNS "github.com/hashicorp/consul/agent/grpc-external/services/dns" middleware "github.com/hashicorp/consul/agent/grpc-middleware" - "github.com/hashicorp/consul/agent/hcp/scada" "github.com/hashicorp/consul/agent/leafcert" "github.com/hashicorp/consul/agent/local" "github.com/hashicorp/consul/agent/netutil" @@ -432,10 +429,6 @@ type Agent struct { // xdsServer serves the XDS protocol for configuring Envoy proxies. xdsServer *xds.Server - // scadaProvider is set when HashiCorp Cloud Platform integration is configured and exposes the agent's API over - // an encrypted session to HCP - scadaProvider scada.Provider - // enterpriseAgent embeds fields that we only access in consul-enterprise builds enterpriseAgent @@ -492,7 +485,6 @@ func New(bd BaseDeps) (*Agent, error) { cache: bd.Cache, leafCertManager: bd.LeafCertManager, routineManager: routine.NewManager(bd.Logger), - scadaProvider: bd.HCP.Provider, } // TODO: create rpcClientHealth in BaseDeps once NetRPC is available without Agent @@ -1109,12 +1101,6 @@ func (a *Agent) startListeners(addrs []net.Addr) ([]net.Listener, error) { } l = &tcpKeepAliveListener{l.(*net.TCPListener)} - case *capability.Addr: - l, err = a.scadaProvider.Listen(x.Capability()) - if err != nil { - return nil, err - } - default: closeAll() return nil, fmt.Errorf("unsupported address type %T", addr) @@ -1173,11 +1159,6 @@ func (a *Agent) listenHTTP() ([]apiServer, error) { MaxHeaderBytes: a.config.HTTPMaxHeaderBytes, } - if scada.IsCapability(l.Addr()) { - // wrap in http2 server handler - httpServer.Handler = h2c.NewHandler(srv.handler(), &http2.Server{}) - } - // Load the connlimit helper into the server connLimitFn := a.httpConnLimiter.HTTPConnStateFuncWithDefault429Handler(10 * time.Millisecond) @@ -1195,9 +1176,6 @@ func (a *Agent) listenHTTP() ([]apiServer, error) { } httpAddrs := a.config.HTTPAddrs - if a.config.IsCloudEnabled() && a.scadaProvider != nil { - httpAddrs = append(httpAddrs, scada.CAPCoreAPI) - } if err := start("http", httpAddrs); err != nil { closeListeners(ln) @@ -1601,8 +1579,6 @@ func newConsulConfig(runtimeCfg *config.RuntimeConfig, logger hclog.Logger) (*co cfg.RequestLimitsWriteRate = runtimeCfg.RequestLimitsWriteRate cfg.Locality = runtimeCfg.StructLocality() - cfg.Cloud = runtimeCfg.Cloud - cfg.Reporting.License.Enabled = runtimeCfg.Reporting.License.Enabled cfg.Reporting.SnapshotRetentionTime = runtimeCfg.Reporting.SnapshotRetentionTime @@ -1782,11 +1758,6 @@ func (a *Agent) ShutdownAgent() error { a.rpcClientHealth.Close() a.rpcClientConfigEntry.Close() - // Shutdown SCADA provider - if a.scadaProvider != nil { - a.scadaProvider.Stop() - } - var err error if a.delegate != nil { err = a.delegate.Shutdown() diff --git a/agent/agent_test.go b/agent/agent_test.go index 801cccd46e68..62963b94a73c 100644 --- a/agent/agent_test.go +++ b/agent/agent_test.go @@ -33,6 +33,9 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/tcpproxy" + "github.com/hashicorp/go-hclog" + "github.com/hashicorp/serf/coordinate" + "github.com/hashicorp/serf/serf" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "golang.org/x/sync/errgroup" @@ -40,19 +43,12 @@ import ( "google.golang.org/grpc" "google.golang.org/protobuf/encoding/protojson" - "github.com/hashicorp/go-hclog" - "github.com/hashicorp/hcp-scada-provider/capability" - "github.com/hashicorp/serf/coordinate" - "github.com/hashicorp/serf/serf" - "github.com/hashicorp/consul/agent/cache" cachetype "github.com/hashicorp/consul/agent/cache-types" "github.com/hashicorp/consul/agent/checks" "github.com/hashicorp/consul/agent/config" "github.com/hashicorp/consul/agent/connect" "github.com/hashicorp/consul/agent/consul" - "github.com/hashicorp/consul/agent/hcp" - "github.com/hashicorp/consul/agent/hcp/scada" "github.com/hashicorp/consul/agent/leafcert" "github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/agent/token" @@ -6389,69 +6385,6 @@ peering { }) } -func TestAgent_startListeners_scada(t *testing.T) { - t.Parallel() - pvd := scada.NewMockProvider(t) - c := capability.NewAddr("testcap") - pvd.EXPECT().Listen(c.Capability()).Return(nil, nil).Once() - bd := BaseDeps{ - Deps: consul.Deps{ - Logger: hclog.NewInterceptLogger(nil), - Tokens: new(token.Store), - GRPCConnPool: &fakeGRPCConnPool{}, - HCP: hcp.Deps{ - Provider: pvd, - }, - Registry: resource.NewRegistry(), - }, - RuntimeConfig: &config.RuntimeConfig{}, - Cache: cache.New(cache.Options{}), - NetRPC: &LazyNetRPC{}, - } - - bd.LeafCertManager = leafcert.NewManager(leafcert.Deps{ - CertSigner: leafcert.NewNetRPCCertSigner(bd.NetRPC), - RootsReader: leafcert.NewCachedRootsReader(bd.Cache, "dc1"), - Config: leafcert.Config{}, - }) - - cfg := config.RuntimeConfig{BuildDate: time.Date(2000, 1, 1, 0, 0, 1, 0, time.UTC)} - bd, err := initEnterpriseBaseDeps(bd, &cfg) - require.NoError(t, err) - - agent, err := New(bd) - mockDelegate := delegateMock{} - mockDelegate.On("LicenseCheck").Return() - agent.delegate = &mockDelegate - require.NoError(t, err) - - _, err = agent.startListeners([]net.Addr{c}) - require.NoError(t, err) -} - -func TestAgent_scadaProvider(t *testing.T) { - pvd := scada.NewMockProvider(t) - - // this listener is used when mocking out the scada provider - l, err := net.Listen("tcp4", fmt.Sprintf("127.0.0.1:%d", freeport.GetOne(t))) - require.NoError(t, err) - defer require.NoError(t, l.Close()) - - pvd.EXPECT().Listen(scada.CAPCoreAPI.Capability()).Return(l, nil).Once() - pvd.EXPECT().Stop().Return(nil).Once() - a := TestAgent{ - HCL: `cloud = { resource_id = "test-resource-id" client_id = "test-client-id" client_secret = "test-client-secret" }`, - OverrideDeps: func(deps *BaseDeps) { - deps.HCP.Provider = pvd - }, - } - defer a.Shutdown() - require.NoError(t, a.Start(t)) - - _, err = api.NewClient(&api.Config{Address: l.Addr().String()}) - require.NoError(t, err) -} - func TestAgent_checkServerLastSeen(t *testing.T) { bd := BaseDeps{ Deps: consul.Deps{ diff --git a/agent/config/builder.go b/agent/config/builder.go index 38bd75791732..4fb3e013bccc 100644 --- a/agent/config/builder.go +++ b/agent/config/builder.go @@ -4,7 +4,6 @@ package config import ( - "crypto/tls" "encoding/base64" "encoding/json" "errors" @@ -37,7 +36,6 @@ import ( "github.com/hashicorp/consul/agent/consul/authmethod/ssoauth" consulrate "github.com/hashicorp/consul/agent/consul/rate" "github.com/hashicorp/consul/agent/consul/state" - hcpconfig "github.com/hashicorp/consul/agent/hcp/config" "github.com/hashicorp/consul/agent/rpc/middleware" "github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/agent/token" @@ -1004,7 +1002,6 @@ func (b *builder) build() (rt RuntimeConfig, err error) { AutoEncryptIPSAN: autoEncryptIPSAN, AutoEncryptAllowTLS: autoEncryptAllowTLS, AutoConfig: autoConfig, - Cloud: b.cloudConfigVal(c), ConnectEnabled: connectEnabled, ConnectCAProvider: connectCAProvider, ConnectCAConfig: connectCAConfig, @@ -1135,8 +1132,7 @@ func (b *builder) build() (rt RuntimeConfig, err error) { LocalProxyConfigResyncInterval: 30 * time.Second, } - // host metrics are enabled if consul is configured with HashiCorp Cloud Platform integration - rt.Telemetry.EnableHostMetrics = boolValWithDefault(c.Telemetry.EnableHostMetrics, rt.IsCloudEnabled()) + rt.Telemetry.EnableHostMetrics = boolValWithDefault(c.Telemetry.EnableHostMetrics, false) rt.TLS, err = b.buildTLSConfig(rt, c.TLS) if err != nil { @@ -1979,7 +1975,6 @@ func (b *builder) uiConfigVal(v RawUIConfig) UIConfig { MetricsProviderOptionsJSON: stringVal(v.MetricsProviderOptionsJSON), MetricsProxy: b.uiMetricsProxyVal(v.MetricsProxy), DashboardURLTemplates: v.DashboardURLTemplates, - HCPEnabled: os.Getenv("CONSUL_HCP_ENABLED") == "true", } } @@ -2610,75 +2605,6 @@ func validateAutoConfigAuthorizer(rt RuntimeConfig) error { return nil } -func (b *builder) cloudConfigVal(v Config) hcpconfig.CloudConfig { - // Load the same environment variables expected by hcp-sdk-go - envHostname, ok := os.LookupEnv("HCP_API_ADDRESS") - if !ok { - if legacyEnvHostname, ok := os.LookupEnv("HCP_API_HOST"); ok { - // Remove only https scheme prefixes from the deprecated environment - // variable for specifying the API host. Mirrors the same behavior as - // hcp-sdk-go. - if strings.HasPrefix(strings.ToLower(legacyEnvHostname), "https://") { - legacyEnvHostname = legacyEnvHostname[8:] - } - envHostname = legacyEnvHostname - } - } - - var envTLSConfig *tls.Config - if os.Getenv("HCP_AUTH_TLS") == "insecure" || - os.Getenv("HCP_SCADA_TLS") == "insecure" || - os.Getenv("HCP_API_TLS") == "insecure" { - envTLSConfig = &tls.Config{InsecureSkipVerify: true} - } - - val := hcpconfig.CloudConfig{ - ResourceID: os.Getenv("HCP_RESOURCE_ID"), - ClientID: os.Getenv("HCP_CLIENT_ID"), - ClientSecret: os.Getenv("HCP_CLIENT_SECRET"), - AuthURL: os.Getenv("HCP_AUTH_URL"), - Hostname: envHostname, - ScadaAddress: os.Getenv("HCP_SCADA_ADDRESS"), - TLSConfig: envTLSConfig, - } - - // Node id might get overridden in setup.go:142 - nodeID := stringVal(v.NodeID) - val.NodeID = types.NodeID(nodeID) - val.NodeName = b.nodeName(v.NodeName) - - if v.Cloud == nil { - return val - } - - // Load configuration file variables for anything not set by environment variables - if val.AuthURL == "" { - val.AuthURL = stringVal(v.Cloud.AuthURL) - } - - if val.Hostname == "" { - val.Hostname = stringVal(v.Cloud.Hostname) - } - - if val.ScadaAddress == "" { - val.ScadaAddress = stringVal(v.Cloud.ScadaAddress) - } - - if val.ResourceID == "" { - val.ResourceID = stringVal(v.Cloud.ResourceID) - } - - if val.ClientID == "" { - val.ClientID = stringVal(v.Cloud.ClientID) - } - - if val.ClientSecret == "" { - val.ClientSecret = stringVal(v.Cloud.ClientSecret) - } - - return val -} - // decodeBytes returns the encryption key decoded. func decodeBytes(key string) ([]byte, error) { return base64.StdEncoding.DecodeString(key) diff --git a/agent/config/builder_test.go b/agent/config/builder_test.go index 439152ebfd59..bf558138df4e 100644 --- a/agent/config/builder_test.go +++ b/agent/config/builder_test.go @@ -15,7 +15,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - hcpconfig "github.com/hashicorp/consul/agent/hcp/config" "github.com/hashicorp/consul/types" ) @@ -558,30 +557,6 @@ func TestBuilder_parsePrefixFilter(t *testing.T) { }) } -func TestBuidler_hostMetricsWithCloud(t *testing.T) { - devMode := true - - // We hardcode `node_name` to make the tests pass because the default might not be DNS compliant which throws error in local setup - builderOpts := LoadOpts{ - DevMode: &devMode, - DefaultConfig: FileSource{ - Name: "test", - Format: "hcl", - Data: ` - node_name = "test" - cloud{ resource_id = "abc" client_id = "abc" client_secret = "abc"} - `, - }, - } - - result, err := Load(builderOpts) - require.NoError(t, err) - require.Empty(t, result.Warnings) - cfg := result.RuntimeConfig - require.NotNil(t, cfg) - require.True(t, cfg.Telemetry.EnableHostMetrics) -} - func TestBuilder_CheckExperimentsInSecondaryDatacenters(t *testing.T) { type testcase struct { @@ -632,111 +607,6 @@ func TestBuilder_CheckExperimentsInSecondaryDatacenters(t *testing.T) { } } -func TestBuilder_CloudConfigWithEnvironmentVars(t *testing.T) { - tests := map[string]struct { - hcl string - env map[string]string - expected hcpconfig.CloudConfig - }{ - "ConfigurationOnly": { - hcl: `cloud{ resource_id = "config-resource-id" client_id = "config-client-id" - client_secret = "config-client-secret" auth_url = "auth.config.com" - hostname = "api.config.com" scada_address = "scada.config.com"}`, - expected: hcpconfig.CloudConfig{ - ResourceID: "config-resource-id", - ClientID: "config-client-id", - ClientSecret: "config-client-secret", - AuthURL: "auth.config.com", - Hostname: "api.config.com", - ScadaAddress: "scada.config.com", - }, - }, - "EnvVarsOnly": { - env: map[string]string{ - "HCP_RESOURCE_ID": "env-resource-id", - "HCP_CLIENT_ID": "env-client-id", - "HCP_CLIENT_SECRET": "env-client-secret", - "HCP_AUTH_URL": "auth.env.com", - "HCP_API_ADDRESS": "api.env.com", - "HCP_SCADA_ADDRESS": "scada.env.com", - }, - expected: hcpconfig.CloudConfig{ - ResourceID: "env-resource-id", - ClientID: "env-client-id", - ClientSecret: "env-client-secret", - AuthURL: "auth.env.com", - Hostname: "api.env.com", - ScadaAddress: "scada.env.com", - }, - }, - "EnvVarsOverrideConfig": { - hcl: `cloud{ resource_id = "config-resource-id" client_id = "config-client-id" - client_secret = "config-client-secret" auth_url = "auth.config.com" - hostname = "api.config.com" scada_address = "scada.config.com"}`, - env: map[string]string{ - "HCP_RESOURCE_ID": "env-resource-id", - "HCP_CLIENT_ID": "env-client-id", - "HCP_CLIENT_SECRET": "env-client-secret", - "HCP_AUTH_URL": "auth.env.com", - "HCP_API_ADDRESS": "api.env.com", - "HCP_SCADA_ADDRESS": "scada.env.com", - }, - expected: hcpconfig.CloudConfig{ - ResourceID: "env-resource-id", - ClientID: "env-client-id", - ClientSecret: "env-client-secret", - AuthURL: "auth.env.com", - Hostname: "api.env.com", - ScadaAddress: "scada.env.com", - }, - }, - "Combination": { - hcl: `cloud{ resource_id = "config-resource-id" client_id = "config-client-id" - client_secret = "config-client-secret"}`, - env: map[string]string{ - "HCP_AUTH_URL": "auth.env.com", - "HCP_API_ADDRESS": "api.env.com", - "HCP_SCADA_ADDRESS": "scada.env.com", - }, - expected: hcpconfig.CloudConfig{ - ResourceID: "config-resource-id", - ClientID: "config-client-id", - ClientSecret: "config-client-secret", - AuthURL: "auth.env.com", - Hostname: "api.env.com", - ScadaAddress: "scada.env.com", - }, - }, - } - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - for k, v := range tc.env { - t.Setenv(k, v) - } - devMode := true - builderOpts := LoadOpts{ - DevMode: &devMode, - Overrides: []Source{ - FileSource{ - Name: "overrides", - Format: "hcl", - Data: tc.hcl, - }, - }, - } - loaded, err := Load(builderOpts) - require.NoError(t, err) - - nodeName, err := os.Hostname() - require.NoError(t, err) - tc.expected.NodeName = nodeName - - actual := loaded.RuntimeConfig.Cloud - require.Equal(t, tc.expected, actual) - }) - } -} - func TestBuilder_DatacenterDNSCompatibleWarning(t *testing.T) { type testCase struct { name string diff --git a/agent/config/config.deepcopy.go b/agent/config/config.deepcopy.go index 0a855d59d93a..5b49d78fa6ec 100644 --- a/agent/config/config.deepcopy.go +++ b/agent/config/config.deepcopy.go @@ -3,758 +3,17 @@ package config import ( - "crypto/tls" - "crypto/x509" - "crypto/x509/pkix" - "encoding/asn1" "github.com/armon/go-metrics" "github.com/armon/go-metrics/prometheus" "github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/types" - "math/big" "net" - "net/url" "time" ) // DeepCopy generates a deep copy of *RuntimeConfig func (o *RuntimeConfig) DeepCopy() *RuntimeConfig { var cp RuntimeConfig = *o - if o.Cloud.TLSConfig != nil { - cp.Cloud.TLSConfig = new(tls.Config) - *cp.Cloud.TLSConfig = *o.Cloud.TLSConfig - if o.Cloud.TLSConfig.Certificates != nil { - cp.Cloud.TLSConfig.Certificates = make([]tls.Certificate, len(o.Cloud.TLSConfig.Certificates)) - copy(cp.Cloud.TLSConfig.Certificates, o.Cloud.TLSConfig.Certificates) - for i5 := range o.Cloud.TLSConfig.Certificates { - if o.Cloud.TLSConfig.Certificates[i5].Certificate != nil { - cp.Cloud.TLSConfig.Certificates[i5].Certificate = make([][]byte, len(o.Cloud.TLSConfig.Certificates[i5].Certificate)) - copy(cp.Cloud.TLSConfig.Certificates[i5].Certificate, o.Cloud.TLSConfig.Certificates[i5].Certificate) - for i7 := range o.Cloud.TLSConfig.Certificates[i5].Certificate { - if o.Cloud.TLSConfig.Certificates[i5].Certificate[i7] != nil { - cp.Cloud.TLSConfig.Certificates[i5].Certificate[i7] = make([]byte, len(o.Cloud.TLSConfig.Certificates[i5].Certificate[i7])) - copy(cp.Cloud.TLSConfig.Certificates[i5].Certificate[i7], o.Cloud.TLSConfig.Certificates[i5].Certificate[i7]) - } - } - } - if o.Cloud.TLSConfig.Certificates[i5].SupportedSignatureAlgorithms != nil { - cp.Cloud.TLSConfig.Certificates[i5].SupportedSignatureAlgorithms = make([]tls.SignatureScheme, len(o.Cloud.TLSConfig.Certificates[i5].SupportedSignatureAlgorithms)) - copy(cp.Cloud.TLSConfig.Certificates[i5].SupportedSignatureAlgorithms, o.Cloud.TLSConfig.Certificates[i5].SupportedSignatureAlgorithms) - } - if o.Cloud.TLSConfig.Certificates[i5].OCSPStaple != nil { - cp.Cloud.TLSConfig.Certificates[i5].OCSPStaple = make([]byte, len(o.Cloud.TLSConfig.Certificates[i5].OCSPStaple)) - copy(cp.Cloud.TLSConfig.Certificates[i5].OCSPStaple, o.Cloud.TLSConfig.Certificates[i5].OCSPStaple) - } - if o.Cloud.TLSConfig.Certificates[i5].SignedCertificateTimestamps != nil { - cp.Cloud.TLSConfig.Certificates[i5].SignedCertificateTimestamps = make([][]byte, len(o.Cloud.TLSConfig.Certificates[i5].SignedCertificateTimestamps)) - copy(cp.Cloud.TLSConfig.Certificates[i5].SignedCertificateTimestamps, o.Cloud.TLSConfig.Certificates[i5].SignedCertificateTimestamps) - for i7 := range o.Cloud.TLSConfig.Certificates[i5].SignedCertificateTimestamps { - if o.Cloud.TLSConfig.Certificates[i5].SignedCertificateTimestamps[i7] != nil { - cp.Cloud.TLSConfig.Certificates[i5].SignedCertificateTimestamps[i7] = make([]byte, len(o.Cloud.TLSConfig.Certificates[i5].SignedCertificateTimestamps[i7])) - copy(cp.Cloud.TLSConfig.Certificates[i5].SignedCertificateTimestamps[i7], o.Cloud.TLSConfig.Certificates[i5].SignedCertificateTimestamps[i7]) - } - } - } - if o.Cloud.TLSConfig.Certificates[i5].Leaf != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf = new(x509.Certificate) - *cp.Cloud.TLSConfig.Certificates[i5].Leaf = *o.Cloud.TLSConfig.Certificates[i5].Leaf - if o.Cloud.TLSConfig.Certificates[i5].Leaf.Raw != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.Raw = make([]byte, len(o.Cloud.TLSConfig.Certificates[i5].Leaf.Raw)) - copy(cp.Cloud.TLSConfig.Certificates[i5].Leaf.Raw, o.Cloud.TLSConfig.Certificates[i5].Leaf.Raw) - } - if o.Cloud.TLSConfig.Certificates[i5].Leaf.RawTBSCertificate != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.RawTBSCertificate = make([]byte, len(o.Cloud.TLSConfig.Certificates[i5].Leaf.RawTBSCertificate)) - copy(cp.Cloud.TLSConfig.Certificates[i5].Leaf.RawTBSCertificate, o.Cloud.TLSConfig.Certificates[i5].Leaf.RawTBSCertificate) - } - if o.Cloud.TLSConfig.Certificates[i5].Leaf.RawSubjectPublicKeyInfo != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.RawSubjectPublicKeyInfo = make([]byte, len(o.Cloud.TLSConfig.Certificates[i5].Leaf.RawSubjectPublicKeyInfo)) - copy(cp.Cloud.TLSConfig.Certificates[i5].Leaf.RawSubjectPublicKeyInfo, o.Cloud.TLSConfig.Certificates[i5].Leaf.RawSubjectPublicKeyInfo) - } - if o.Cloud.TLSConfig.Certificates[i5].Leaf.RawSubject != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.RawSubject = make([]byte, len(o.Cloud.TLSConfig.Certificates[i5].Leaf.RawSubject)) - copy(cp.Cloud.TLSConfig.Certificates[i5].Leaf.RawSubject, o.Cloud.TLSConfig.Certificates[i5].Leaf.RawSubject) - } - if o.Cloud.TLSConfig.Certificates[i5].Leaf.RawIssuer != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.RawIssuer = make([]byte, len(o.Cloud.TLSConfig.Certificates[i5].Leaf.RawIssuer)) - copy(cp.Cloud.TLSConfig.Certificates[i5].Leaf.RawIssuer, o.Cloud.TLSConfig.Certificates[i5].Leaf.RawIssuer) - } - if o.Cloud.TLSConfig.Certificates[i5].Leaf.Signature != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.Signature = make([]byte, len(o.Cloud.TLSConfig.Certificates[i5].Leaf.Signature)) - copy(cp.Cloud.TLSConfig.Certificates[i5].Leaf.Signature, o.Cloud.TLSConfig.Certificates[i5].Leaf.Signature) - } - if o.Cloud.TLSConfig.Certificates[i5].Leaf.SerialNumber != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.SerialNumber = new(big.Int) - *cp.Cloud.TLSConfig.Certificates[i5].Leaf.SerialNumber = *o.Cloud.TLSConfig.Certificates[i5].Leaf.SerialNumber - } - if o.Cloud.TLSConfig.Certificates[i5].Leaf.Issuer.Country != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.Issuer.Country = make([]string, len(o.Cloud.TLSConfig.Certificates[i5].Leaf.Issuer.Country)) - copy(cp.Cloud.TLSConfig.Certificates[i5].Leaf.Issuer.Country, o.Cloud.TLSConfig.Certificates[i5].Leaf.Issuer.Country) - } - if o.Cloud.TLSConfig.Certificates[i5].Leaf.Issuer.Organization != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.Issuer.Organization = make([]string, len(o.Cloud.TLSConfig.Certificates[i5].Leaf.Issuer.Organization)) - copy(cp.Cloud.TLSConfig.Certificates[i5].Leaf.Issuer.Organization, o.Cloud.TLSConfig.Certificates[i5].Leaf.Issuer.Organization) - } - if o.Cloud.TLSConfig.Certificates[i5].Leaf.Issuer.OrganizationalUnit != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.Issuer.OrganizationalUnit = make([]string, len(o.Cloud.TLSConfig.Certificates[i5].Leaf.Issuer.OrganizationalUnit)) - copy(cp.Cloud.TLSConfig.Certificates[i5].Leaf.Issuer.OrganizationalUnit, o.Cloud.TLSConfig.Certificates[i5].Leaf.Issuer.OrganizationalUnit) - } - if o.Cloud.TLSConfig.Certificates[i5].Leaf.Issuer.Locality != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.Issuer.Locality = make([]string, len(o.Cloud.TLSConfig.Certificates[i5].Leaf.Issuer.Locality)) - copy(cp.Cloud.TLSConfig.Certificates[i5].Leaf.Issuer.Locality, o.Cloud.TLSConfig.Certificates[i5].Leaf.Issuer.Locality) - } - if o.Cloud.TLSConfig.Certificates[i5].Leaf.Issuer.Province != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.Issuer.Province = make([]string, len(o.Cloud.TLSConfig.Certificates[i5].Leaf.Issuer.Province)) - copy(cp.Cloud.TLSConfig.Certificates[i5].Leaf.Issuer.Province, o.Cloud.TLSConfig.Certificates[i5].Leaf.Issuer.Province) - } - if o.Cloud.TLSConfig.Certificates[i5].Leaf.Issuer.StreetAddress != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.Issuer.StreetAddress = make([]string, len(o.Cloud.TLSConfig.Certificates[i5].Leaf.Issuer.StreetAddress)) - copy(cp.Cloud.TLSConfig.Certificates[i5].Leaf.Issuer.StreetAddress, o.Cloud.TLSConfig.Certificates[i5].Leaf.Issuer.StreetAddress) - } - if o.Cloud.TLSConfig.Certificates[i5].Leaf.Issuer.PostalCode != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.Issuer.PostalCode = make([]string, len(o.Cloud.TLSConfig.Certificates[i5].Leaf.Issuer.PostalCode)) - copy(cp.Cloud.TLSConfig.Certificates[i5].Leaf.Issuer.PostalCode, o.Cloud.TLSConfig.Certificates[i5].Leaf.Issuer.PostalCode) - } - if o.Cloud.TLSConfig.Certificates[i5].Leaf.Issuer.Names != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.Issuer.Names = make([]pkix.AttributeTypeAndValue, len(o.Cloud.TLSConfig.Certificates[i5].Leaf.Issuer.Names)) - copy(cp.Cloud.TLSConfig.Certificates[i5].Leaf.Issuer.Names, o.Cloud.TLSConfig.Certificates[i5].Leaf.Issuer.Names) - for i10 := range o.Cloud.TLSConfig.Certificates[i5].Leaf.Issuer.Names { - if o.Cloud.TLSConfig.Certificates[i5].Leaf.Issuer.Names[i10].Type != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.Issuer.Names[i10].Type = make([]int, len(o.Cloud.TLSConfig.Certificates[i5].Leaf.Issuer.Names[i10].Type)) - copy(cp.Cloud.TLSConfig.Certificates[i5].Leaf.Issuer.Names[i10].Type, o.Cloud.TLSConfig.Certificates[i5].Leaf.Issuer.Names[i10].Type) - } - } - } - if o.Cloud.TLSConfig.Certificates[i5].Leaf.Issuer.ExtraNames != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.Issuer.ExtraNames = make([]pkix.AttributeTypeAndValue, len(o.Cloud.TLSConfig.Certificates[i5].Leaf.Issuer.ExtraNames)) - copy(cp.Cloud.TLSConfig.Certificates[i5].Leaf.Issuer.ExtraNames, o.Cloud.TLSConfig.Certificates[i5].Leaf.Issuer.ExtraNames) - for i10 := range o.Cloud.TLSConfig.Certificates[i5].Leaf.Issuer.ExtraNames { - if o.Cloud.TLSConfig.Certificates[i5].Leaf.Issuer.ExtraNames[i10].Type != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.Issuer.ExtraNames[i10].Type = make([]int, len(o.Cloud.TLSConfig.Certificates[i5].Leaf.Issuer.ExtraNames[i10].Type)) - copy(cp.Cloud.TLSConfig.Certificates[i5].Leaf.Issuer.ExtraNames[i10].Type, o.Cloud.TLSConfig.Certificates[i5].Leaf.Issuer.ExtraNames[i10].Type) - } - } - } - if o.Cloud.TLSConfig.Certificates[i5].Leaf.Subject.Country != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.Subject.Country = make([]string, len(o.Cloud.TLSConfig.Certificates[i5].Leaf.Subject.Country)) - copy(cp.Cloud.TLSConfig.Certificates[i5].Leaf.Subject.Country, o.Cloud.TLSConfig.Certificates[i5].Leaf.Subject.Country) - } - if o.Cloud.TLSConfig.Certificates[i5].Leaf.Subject.Organization != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.Subject.Organization = make([]string, len(o.Cloud.TLSConfig.Certificates[i5].Leaf.Subject.Organization)) - copy(cp.Cloud.TLSConfig.Certificates[i5].Leaf.Subject.Organization, o.Cloud.TLSConfig.Certificates[i5].Leaf.Subject.Organization) - } - if o.Cloud.TLSConfig.Certificates[i5].Leaf.Subject.OrganizationalUnit != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.Subject.OrganizationalUnit = make([]string, len(o.Cloud.TLSConfig.Certificates[i5].Leaf.Subject.OrganizationalUnit)) - copy(cp.Cloud.TLSConfig.Certificates[i5].Leaf.Subject.OrganizationalUnit, o.Cloud.TLSConfig.Certificates[i5].Leaf.Subject.OrganizationalUnit) - } - if o.Cloud.TLSConfig.Certificates[i5].Leaf.Subject.Locality != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.Subject.Locality = make([]string, len(o.Cloud.TLSConfig.Certificates[i5].Leaf.Subject.Locality)) - copy(cp.Cloud.TLSConfig.Certificates[i5].Leaf.Subject.Locality, o.Cloud.TLSConfig.Certificates[i5].Leaf.Subject.Locality) - } - if o.Cloud.TLSConfig.Certificates[i5].Leaf.Subject.Province != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.Subject.Province = make([]string, len(o.Cloud.TLSConfig.Certificates[i5].Leaf.Subject.Province)) - copy(cp.Cloud.TLSConfig.Certificates[i5].Leaf.Subject.Province, o.Cloud.TLSConfig.Certificates[i5].Leaf.Subject.Province) - } - if o.Cloud.TLSConfig.Certificates[i5].Leaf.Subject.StreetAddress != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.Subject.StreetAddress = make([]string, len(o.Cloud.TLSConfig.Certificates[i5].Leaf.Subject.StreetAddress)) - copy(cp.Cloud.TLSConfig.Certificates[i5].Leaf.Subject.StreetAddress, o.Cloud.TLSConfig.Certificates[i5].Leaf.Subject.StreetAddress) - } - if o.Cloud.TLSConfig.Certificates[i5].Leaf.Subject.PostalCode != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.Subject.PostalCode = make([]string, len(o.Cloud.TLSConfig.Certificates[i5].Leaf.Subject.PostalCode)) - copy(cp.Cloud.TLSConfig.Certificates[i5].Leaf.Subject.PostalCode, o.Cloud.TLSConfig.Certificates[i5].Leaf.Subject.PostalCode) - } - if o.Cloud.TLSConfig.Certificates[i5].Leaf.Subject.Names != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.Subject.Names = make([]pkix.AttributeTypeAndValue, len(o.Cloud.TLSConfig.Certificates[i5].Leaf.Subject.Names)) - copy(cp.Cloud.TLSConfig.Certificates[i5].Leaf.Subject.Names, o.Cloud.TLSConfig.Certificates[i5].Leaf.Subject.Names) - for i10 := range o.Cloud.TLSConfig.Certificates[i5].Leaf.Subject.Names { - if o.Cloud.TLSConfig.Certificates[i5].Leaf.Subject.Names[i10].Type != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.Subject.Names[i10].Type = make([]int, len(o.Cloud.TLSConfig.Certificates[i5].Leaf.Subject.Names[i10].Type)) - copy(cp.Cloud.TLSConfig.Certificates[i5].Leaf.Subject.Names[i10].Type, o.Cloud.TLSConfig.Certificates[i5].Leaf.Subject.Names[i10].Type) - } - } - } - if o.Cloud.TLSConfig.Certificates[i5].Leaf.Subject.ExtraNames != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.Subject.ExtraNames = make([]pkix.AttributeTypeAndValue, len(o.Cloud.TLSConfig.Certificates[i5].Leaf.Subject.ExtraNames)) - copy(cp.Cloud.TLSConfig.Certificates[i5].Leaf.Subject.ExtraNames, o.Cloud.TLSConfig.Certificates[i5].Leaf.Subject.ExtraNames) - for i10 := range o.Cloud.TLSConfig.Certificates[i5].Leaf.Subject.ExtraNames { - if o.Cloud.TLSConfig.Certificates[i5].Leaf.Subject.ExtraNames[i10].Type != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.Subject.ExtraNames[i10].Type = make([]int, len(o.Cloud.TLSConfig.Certificates[i5].Leaf.Subject.ExtraNames[i10].Type)) - copy(cp.Cloud.TLSConfig.Certificates[i5].Leaf.Subject.ExtraNames[i10].Type, o.Cloud.TLSConfig.Certificates[i5].Leaf.Subject.ExtraNames[i10].Type) - } - } - } - if o.Cloud.TLSConfig.Certificates[i5].Leaf.Extensions != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.Extensions = make([]pkix.Extension, len(o.Cloud.TLSConfig.Certificates[i5].Leaf.Extensions)) - copy(cp.Cloud.TLSConfig.Certificates[i5].Leaf.Extensions, o.Cloud.TLSConfig.Certificates[i5].Leaf.Extensions) - for i9 := range o.Cloud.TLSConfig.Certificates[i5].Leaf.Extensions { - if o.Cloud.TLSConfig.Certificates[i5].Leaf.Extensions[i9].Id != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.Extensions[i9].Id = make([]int, len(o.Cloud.TLSConfig.Certificates[i5].Leaf.Extensions[i9].Id)) - copy(cp.Cloud.TLSConfig.Certificates[i5].Leaf.Extensions[i9].Id, o.Cloud.TLSConfig.Certificates[i5].Leaf.Extensions[i9].Id) - } - if o.Cloud.TLSConfig.Certificates[i5].Leaf.Extensions[i9].Value != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.Extensions[i9].Value = make([]byte, len(o.Cloud.TLSConfig.Certificates[i5].Leaf.Extensions[i9].Value)) - copy(cp.Cloud.TLSConfig.Certificates[i5].Leaf.Extensions[i9].Value, o.Cloud.TLSConfig.Certificates[i5].Leaf.Extensions[i9].Value) - } - } - } - if o.Cloud.TLSConfig.Certificates[i5].Leaf.ExtraExtensions != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.ExtraExtensions = make([]pkix.Extension, len(o.Cloud.TLSConfig.Certificates[i5].Leaf.ExtraExtensions)) - copy(cp.Cloud.TLSConfig.Certificates[i5].Leaf.ExtraExtensions, o.Cloud.TLSConfig.Certificates[i5].Leaf.ExtraExtensions) - for i9 := range o.Cloud.TLSConfig.Certificates[i5].Leaf.ExtraExtensions { - if o.Cloud.TLSConfig.Certificates[i5].Leaf.ExtraExtensions[i9].Id != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.ExtraExtensions[i9].Id = make([]int, len(o.Cloud.TLSConfig.Certificates[i5].Leaf.ExtraExtensions[i9].Id)) - copy(cp.Cloud.TLSConfig.Certificates[i5].Leaf.ExtraExtensions[i9].Id, o.Cloud.TLSConfig.Certificates[i5].Leaf.ExtraExtensions[i9].Id) - } - if o.Cloud.TLSConfig.Certificates[i5].Leaf.ExtraExtensions[i9].Value != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.ExtraExtensions[i9].Value = make([]byte, len(o.Cloud.TLSConfig.Certificates[i5].Leaf.ExtraExtensions[i9].Value)) - copy(cp.Cloud.TLSConfig.Certificates[i5].Leaf.ExtraExtensions[i9].Value, o.Cloud.TLSConfig.Certificates[i5].Leaf.ExtraExtensions[i9].Value) - } - } - } - if o.Cloud.TLSConfig.Certificates[i5].Leaf.UnhandledCriticalExtensions != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.UnhandledCriticalExtensions = make([]asn1.ObjectIdentifier, len(o.Cloud.TLSConfig.Certificates[i5].Leaf.UnhandledCriticalExtensions)) - copy(cp.Cloud.TLSConfig.Certificates[i5].Leaf.UnhandledCriticalExtensions, o.Cloud.TLSConfig.Certificates[i5].Leaf.UnhandledCriticalExtensions) - for i9 := range o.Cloud.TLSConfig.Certificates[i5].Leaf.UnhandledCriticalExtensions { - if o.Cloud.TLSConfig.Certificates[i5].Leaf.UnhandledCriticalExtensions[i9] != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.UnhandledCriticalExtensions[i9] = make([]int, len(o.Cloud.TLSConfig.Certificates[i5].Leaf.UnhandledCriticalExtensions[i9])) - copy(cp.Cloud.TLSConfig.Certificates[i5].Leaf.UnhandledCriticalExtensions[i9], o.Cloud.TLSConfig.Certificates[i5].Leaf.UnhandledCriticalExtensions[i9]) - } - } - } - if o.Cloud.TLSConfig.Certificates[i5].Leaf.ExtKeyUsage != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.ExtKeyUsage = make([]x509.ExtKeyUsage, len(o.Cloud.TLSConfig.Certificates[i5].Leaf.ExtKeyUsage)) - copy(cp.Cloud.TLSConfig.Certificates[i5].Leaf.ExtKeyUsage, o.Cloud.TLSConfig.Certificates[i5].Leaf.ExtKeyUsage) - } - if o.Cloud.TLSConfig.Certificates[i5].Leaf.UnknownExtKeyUsage != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.UnknownExtKeyUsage = make([]asn1.ObjectIdentifier, len(o.Cloud.TLSConfig.Certificates[i5].Leaf.UnknownExtKeyUsage)) - copy(cp.Cloud.TLSConfig.Certificates[i5].Leaf.UnknownExtKeyUsage, o.Cloud.TLSConfig.Certificates[i5].Leaf.UnknownExtKeyUsage) - for i9 := range o.Cloud.TLSConfig.Certificates[i5].Leaf.UnknownExtKeyUsage { - if o.Cloud.TLSConfig.Certificates[i5].Leaf.UnknownExtKeyUsage[i9] != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.UnknownExtKeyUsage[i9] = make([]int, len(o.Cloud.TLSConfig.Certificates[i5].Leaf.UnknownExtKeyUsage[i9])) - copy(cp.Cloud.TLSConfig.Certificates[i5].Leaf.UnknownExtKeyUsage[i9], o.Cloud.TLSConfig.Certificates[i5].Leaf.UnknownExtKeyUsage[i9]) - } - } - } - if o.Cloud.TLSConfig.Certificates[i5].Leaf.SubjectKeyId != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.SubjectKeyId = make([]byte, len(o.Cloud.TLSConfig.Certificates[i5].Leaf.SubjectKeyId)) - copy(cp.Cloud.TLSConfig.Certificates[i5].Leaf.SubjectKeyId, o.Cloud.TLSConfig.Certificates[i5].Leaf.SubjectKeyId) - } - if o.Cloud.TLSConfig.Certificates[i5].Leaf.AuthorityKeyId != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.AuthorityKeyId = make([]byte, len(o.Cloud.TLSConfig.Certificates[i5].Leaf.AuthorityKeyId)) - copy(cp.Cloud.TLSConfig.Certificates[i5].Leaf.AuthorityKeyId, o.Cloud.TLSConfig.Certificates[i5].Leaf.AuthorityKeyId) - } - if o.Cloud.TLSConfig.Certificates[i5].Leaf.OCSPServer != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.OCSPServer = make([]string, len(o.Cloud.TLSConfig.Certificates[i5].Leaf.OCSPServer)) - copy(cp.Cloud.TLSConfig.Certificates[i5].Leaf.OCSPServer, o.Cloud.TLSConfig.Certificates[i5].Leaf.OCSPServer) - } - if o.Cloud.TLSConfig.Certificates[i5].Leaf.IssuingCertificateURL != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.IssuingCertificateURL = make([]string, len(o.Cloud.TLSConfig.Certificates[i5].Leaf.IssuingCertificateURL)) - copy(cp.Cloud.TLSConfig.Certificates[i5].Leaf.IssuingCertificateURL, o.Cloud.TLSConfig.Certificates[i5].Leaf.IssuingCertificateURL) - } - if o.Cloud.TLSConfig.Certificates[i5].Leaf.DNSNames != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.DNSNames = make([]string, len(o.Cloud.TLSConfig.Certificates[i5].Leaf.DNSNames)) - copy(cp.Cloud.TLSConfig.Certificates[i5].Leaf.DNSNames, o.Cloud.TLSConfig.Certificates[i5].Leaf.DNSNames) - } - if o.Cloud.TLSConfig.Certificates[i5].Leaf.EmailAddresses != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.EmailAddresses = make([]string, len(o.Cloud.TLSConfig.Certificates[i5].Leaf.EmailAddresses)) - copy(cp.Cloud.TLSConfig.Certificates[i5].Leaf.EmailAddresses, o.Cloud.TLSConfig.Certificates[i5].Leaf.EmailAddresses) - } - if o.Cloud.TLSConfig.Certificates[i5].Leaf.IPAddresses != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.IPAddresses = make([]net.IP, len(o.Cloud.TLSConfig.Certificates[i5].Leaf.IPAddresses)) - copy(cp.Cloud.TLSConfig.Certificates[i5].Leaf.IPAddresses, o.Cloud.TLSConfig.Certificates[i5].Leaf.IPAddresses) - for i9 := range o.Cloud.TLSConfig.Certificates[i5].Leaf.IPAddresses { - if o.Cloud.TLSConfig.Certificates[i5].Leaf.IPAddresses[i9] != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.IPAddresses[i9] = make([]byte, len(o.Cloud.TLSConfig.Certificates[i5].Leaf.IPAddresses[i9])) - copy(cp.Cloud.TLSConfig.Certificates[i5].Leaf.IPAddresses[i9], o.Cloud.TLSConfig.Certificates[i5].Leaf.IPAddresses[i9]) - } - } - } - if o.Cloud.TLSConfig.Certificates[i5].Leaf.URIs != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.URIs = make([]*url.URL, len(o.Cloud.TLSConfig.Certificates[i5].Leaf.URIs)) - copy(cp.Cloud.TLSConfig.Certificates[i5].Leaf.URIs, o.Cloud.TLSConfig.Certificates[i5].Leaf.URIs) - for i9 := range o.Cloud.TLSConfig.Certificates[i5].Leaf.URIs { - if o.Cloud.TLSConfig.Certificates[i5].Leaf.URIs[i9] != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.URIs[i9] = new(url.URL) - *cp.Cloud.TLSConfig.Certificates[i5].Leaf.URIs[i9] = *o.Cloud.TLSConfig.Certificates[i5].Leaf.URIs[i9] - if o.Cloud.TLSConfig.Certificates[i5].Leaf.URIs[i9].User != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.URIs[i9].User = new(url.Userinfo) - *cp.Cloud.TLSConfig.Certificates[i5].Leaf.URIs[i9].User = *o.Cloud.TLSConfig.Certificates[i5].Leaf.URIs[i9].User - } - } - } - } - if o.Cloud.TLSConfig.Certificates[i5].Leaf.PermittedDNSDomains != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.PermittedDNSDomains = make([]string, len(o.Cloud.TLSConfig.Certificates[i5].Leaf.PermittedDNSDomains)) - copy(cp.Cloud.TLSConfig.Certificates[i5].Leaf.PermittedDNSDomains, o.Cloud.TLSConfig.Certificates[i5].Leaf.PermittedDNSDomains) - } - if o.Cloud.TLSConfig.Certificates[i5].Leaf.ExcludedDNSDomains != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.ExcludedDNSDomains = make([]string, len(o.Cloud.TLSConfig.Certificates[i5].Leaf.ExcludedDNSDomains)) - copy(cp.Cloud.TLSConfig.Certificates[i5].Leaf.ExcludedDNSDomains, o.Cloud.TLSConfig.Certificates[i5].Leaf.ExcludedDNSDomains) - } - if o.Cloud.TLSConfig.Certificates[i5].Leaf.PermittedIPRanges != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.PermittedIPRanges = make([]*net.IPNet, len(o.Cloud.TLSConfig.Certificates[i5].Leaf.PermittedIPRanges)) - copy(cp.Cloud.TLSConfig.Certificates[i5].Leaf.PermittedIPRanges, o.Cloud.TLSConfig.Certificates[i5].Leaf.PermittedIPRanges) - for i9 := range o.Cloud.TLSConfig.Certificates[i5].Leaf.PermittedIPRanges { - if o.Cloud.TLSConfig.Certificates[i5].Leaf.PermittedIPRanges[i9] != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.PermittedIPRanges[i9] = new(net.IPNet) - *cp.Cloud.TLSConfig.Certificates[i5].Leaf.PermittedIPRanges[i9] = *o.Cloud.TLSConfig.Certificates[i5].Leaf.PermittedIPRanges[i9] - if o.Cloud.TLSConfig.Certificates[i5].Leaf.PermittedIPRanges[i9].IP != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.PermittedIPRanges[i9].IP = make([]byte, len(o.Cloud.TLSConfig.Certificates[i5].Leaf.PermittedIPRanges[i9].IP)) - copy(cp.Cloud.TLSConfig.Certificates[i5].Leaf.PermittedIPRanges[i9].IP, o.Cloud.TLSConfig.Certificates[i5].Leaf.PermittedIPRanges[i9].IP) - } - if o.Cloud.TLSConfig.Certificates[i5].Leaf.PermittedIPRanges[i9].Mask != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.PermittedIPRanges[i9].Mask = make([]byte, len(o.Cloud.TLSConfig.Certificates[i5].Leaf.PermittedIPRanges[i9].Mask)) - copy(cp.Cloud.TLSConfig.Certificates[i5].Leaf.PermittedIPRanges[i9].Mask, o.Cloud.TLSConfig.Certificates[i5].Leaf.PermittedIPRanges[i9].Mask) - } - } - } - } - if o.Cloud.TLSConfig.Certificates[i5].Leaf.ExcludedIPRanges != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.ExcludedIPRanges = make([]*net.IPNet, len(o.Cloud.TLSConfig.Certificates[i5].Leaf.ExcludedIPRanges)) - copy(cp.Cloud.TLSConfig.Certificates[i5].Leaf.ExcludedIPRanges, o.Cloud.TLSConfig.Certificates[i5].Leaf.ExcludedIPRanges) - for i9 := range o.Cloud.TLSConfig.Certificates[i5].Leaf.ExcludedIPRanges { - if o.Cloud.TLSConfig.Certificates[i5].Leaf.ExcludedIPRanges[i9] != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.ExcludedIPRanges[i9] = new(net.IPNet) - *cp.Cloud.TLSConfig.Certificates[i5].Leaf.ExcludedIPRanges[i9] = *o.Cloud.TLSConfig.Certificates[i5].Leaf.ExcludedIPRanges[i9] - if o.Cloud.TLSConfig.Certificates[i5].Leaf.ExcludedIPRanges[i9].IP != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.ExcludedIPRanges[i9].IP = make([]byte, len(o.Cloud.TLSConfig.Certificates[i5].Leaf.ExcludedIPRanges[i9].IP)) - copy(cp.Cloud.TLSConfig.Certificates[i5].Leaf.ExcludedIPRanges[i9].IP, o.Cloud.TLSConfig.Certificates[i5].Leaf.ExcludedIPRanges[i9].IP) - } - if o.Cloud.TLSConfig.Certificates[i5].Leaf.ExcludedIPRanges[i9].Mask != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.ExcludedIPRanges[i9].Mask = make([]byte, len(o.Cloud.TLSConfig.Certificates[i5].Leaf.ExcludedIPRanges[i9].Mask)) - copy(cp.Cloud.TLSConfig.Certificates[i5].Leaf.ExcludedIPRanges[i9].Mask, o.Cloud.TLSConfig.Certificates[i5].Leaf.ExcludedIPRanges[i9].Mask) - } - } - } - } - if o.Cloud.TLSConfig.Certificates[i5].Leaf.PermittedEmailAddresses != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.PermittedEmailAddresses = make([]string, len(o.Cloud.TLSConfig.Certificates[i5].Leaf.PermittedEmailAddresses)) - copy(cp.Cloud.TLSConfig.Certificates[i5].Leaf.PermittedEmailAddresses, o.Cloud.TLSConfig.Certificates[i5].Leaf.PermittedEmailAddresses) - } - if o.Cloud.TLSConfig.Certificates[i5].Leaf.ExcludedEmailAddresses != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.ExcludedEmailAddresses = make([]string, len(o.Cloud.TLSConfig.Certificates[i5].Leaf.ExcludedEmailAddresses)) - copy(cp.Cloud.TLSConfig.Certificates[i5].Leaf.ExcludedEmailAddresses, o.Cloud.TLSConfig.Certificates[i5].Leaf.ExcludedEmailAddresses) - } - if o.Cloud.TLSConfig.Certificates[i5].Leaf.PermittedURIDomains != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.PermittedURIDomains = make([]string, len(o.Cloud.TLSConfig.Certificates[i5].Leaf.PermittedURIDomains)) - copy(cp.Cloud.TLSConfig.Certificates[i5].Leaf.PermittedURIDomains, o.Cloud.TLSConfig.Certificates[i5].Leaf.PermittedURIDomains) - } - if o.Cloud.TLSConfig.Certificates[i5].Leaf.ExcludedURIDomains != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.ExcludedURIDomains = make([]string, len(o.Cloud.TLSConfig.Certificates[i5].Leaf.ExcludedURIDomains)) - copy(cp.Cloud.TLSConfig.Certificates[i5].Leaf.ExcludedURIDomains, o.Cloud.TLSConfig.Certificates[i5].Leaf.ExcludedURIDomains) - } - if o.Cloud.TLSConfig.Certificates[i5].Leaf.CRLDistributionPoints != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.CRLDistributionPoints = make([]string, len(o.Cloud.TLSConfig.Certificates[i5].Leaf.CRLDistributionPoints)) - copy(cp.Cloud.TLSConfig.Certificates[i5].Leaf.CRLDistributionPoints, o.Cloud.TLSConfig.Certificates[i5].Leaf.CRLDistributionPoints) - } - if o.Cloud.TLSConfig.Certificates[i5].Leaf.PolicyIdentifiers != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.PolicyIdentifiers = make([]asn1.ObjectIdentifier, len(o.Cloud.TLSConfig.Certificates[i5].Leaf.PolicyIdentifiers)) - copy(cp.Cloud.TLSConfig.Certificates[i5].Leaf.PolicyIdentifiers, o.Cloud.TLSConfig.Certificates[i5].Leaf.PolicyIdentifiers) - for i9 := range o.Cloud.TLSConfig.Certificates[i5].Leaf.PolicyIdentifiers { - if o.Cloud.TLSConfig.Certificates[i5].Leaf.PolicyIdentifiers[i9] != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.PolicyIdentifiers[i9] = make([]int, len(o.Cloud.TLSConfig.Certificates[i5].Leaf.PolicyIdentifiers[i9])) - copy(cp.Cloud.TLSConfig.Certificates[i5].Leaf.PolicyIdentifiers[i9], o.Cloud.TLSConfig.Certificates[i5].Leaf.PolicyIdentifiers[i9]) - } - } - } - if o.Cloud.TLSConfig.Certificates[i5].Leaf.Policies != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.Policies = make([]x509.OID, len(o.Cloud.TLSConfig.Certificates[i5].Leaf.Policies)) - copy(cp.Cloud.TLSConfig.Certificates[i5].Leaf.Policies, o.Cloud.TLSConfig.Certificates[i5].Leaf.Policies) - } - if o.Cloud.TLSConfig.Certificates[i5].Leaf.PolicyMappings != nil { - cp.Cloud.TLSConfig.Certificates[i5].Leaf.PolicyMappings = make([]x509.PolicyMapping, len(o.Cloud.TLSConfig.Certificates[i5].Leaf.PolicyMappings)) - copy(cp.Cloud.TLSConfig.Certificates[i5].Leaf.PolicyMappings, o.Cloud.TLSConfig.Certificates[i5].Leaf.PolicyMappings) - } - } - } - } - if o.Cloud.TLSConfig.NameToCertificate != nil { - cp.Cloud.TLSConfig.NameToCertificate = make(map[string]*tls.Certificate, len(o.Cloud.TLSConfig.NameToCertificate)) - for k5, v5 := range o.Cloud.TLSConfig.NameToCertificate { - var cp_Cloud_TLSConfig_NameToCertificate_v5 *tls.Certificate - if v5 != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5 = new(tls.Certificate) - *cp_Cloud_TLSConfig_NameToCertificate_v5 = *v5 - if v5.Certificate != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Certificate = make([][]byte, len(v5.Certificate)) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Certificate, v5.Certificate) - for i8 := range v5.Certificate { - if v5.Certificate[i8] != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Certificate[i8] = make([]byte, len(v5.Certificate[i8])) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Certificate[i8], v5.Certificate[i8]) - } - } - } - if v5.SupportedSignatureAlgorithms != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.SupportedSignatureAlgorithms = make([]tls.SignatureScheme, len(v5.SupportedSignatureAlgorithms)) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.SupportedSignatureAlgorithms, v5.SupportedSignatureAlgorithms) - } - if v5.OCSPStaple != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.OCSPStaple = make([]byte, len(v5.OCSPStaple)) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.OCSPStaple, v5.OCSPStaple) - } - if v5.SignedCertificateTimestamps != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.SignedCertificateTimestamps = make([][]byte, len(v5.SignedCertificateTimestamps)) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.SignedCertificateTimestamps, v5.SignedCertificateTimestamps) - for i8 := range v5.SignedCertificateTimestamps { - if v5.SignedCertificateTimestamps[i8] != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.SignedCertificateTimestamps[i8] = make([]byte, len(v5.SignedCertificateTimestamps[i8])) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.SignedCertificateTimestamps[i8], v5.SignedCertificateTimestamps[i8]) - } - } - } - if v5.Leaf != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf = new(x509.Certificate) - *cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf = *v5.Leaf - if v5.Leaf.Raw != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.Raw = make([]byte, len(v5.Leaf.Raw)) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.Raw, v5.Leaf.Raw) - } - if v5.Leaf.RawTBSCertificate != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.RawTBSCertificate = make([]byte, len(v5.Leaf.RawTBSCertificate)) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.RawTBSCertificate, v5.Leaf.RawTBSCertificate) - } - if v5.Leaf.RawSubjectPublicKeyInfo != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.RawSubjectPublicKeyInfo = make([]byte, len(v5.Leaf.RawSubjectPublicKeyInfo)) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.RawSubjectPublicKeyInfo, v5.Leaf.RawSubjectPublicKeyInfo) - } - if v5.Leaf.RawSubject != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.RawSubject = make([]byte, len(v5.Leaf.RawSubject)) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.RawSubject, v5.Leaf.RawSubject) - } - if v5.Leaf.RawIssuer != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.RawIssuer = make([]byte, len(v5.Leaf.RawIssuer)) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.RawIssuer, v5.Leaf.RawIssuer) - } - if v5.Leaf.Signature != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.Signature = make([]byte, len(v5.Leaf.Signature)) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.Signature, v5.Leaf.Signature) - } - if v5.Leaf.SerialNumber != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.SerialNumber = new(big.Int) - *cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.SerialNumber = *v5.Leaf.SerialNumber - } - if v5.Leaf.Issuer.Country != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.Issuer.Country = make([]string, len(v5.Leaf.Issuer.Country)) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.Issuer.Country, v5.Leaf.Issuer.Country) - } - if v5.Leaf.Issuer.Organization != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.Issuer.Organization = make([]string, len(v5.Leaf.Issuer.Organization)) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.Issuer.Organization, v5.Leaf.Issuer.Organization) - } - if v5.Leaf.Issuer.OrganizationalUnit != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.Issuer.OrganizationalUnit = make([]string, len(v5.Leaf.Issuer.OrganizationalUnit)) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.Issuer.OrganizationalUnit, v5.Leaf.Issuer.OrganizationalUnit) - } - if v5.Leaf.Issuer.Locality != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.Issuer.Locality = make([]string, len(v5.Leaf.Issuer.Locality)) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.Issuer.Locality, v5.Leaf.Issuer.Locality) - } - if v5.Leaf.Issuer.Province != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.Issuer.Province = make([]string, len(v5.Leaf.Issuer.Province)) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.Issuer.Province, v5.Leaf.Issuer.Province) - } - if v5.Leaf.Issuer.StreetAddress != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.Issuer.StreetAddress = make([]string, len(v5.Leaf.Issuer.StreetAddress)) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.Issuer.StreetAddress, v5.Leaf.Issuer.StreetAddress) - } - if v5.Leaf.Issuer.PostalCode != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.Issuer.PostalCode = make([]string, len(v5.Leaf.Issuer.PostalCode)) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.Issuer.PostalCode, v5.Leaf.Issuer.PostalCode) - } - if v5.Leaf.Issuer.Names != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.Issuer.Names = make([]pkix.AttributeTypeAndValue, len(v5.Leaf.Issuer.Names)) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.Issuer.Names, v5.Leaf.Issuer.Names) - for i11 := range v5.Leaf.Issuer.Names { - if v5.Leaf.Issuer.Names[i11].Type != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.Issuer.Names[i11].Type = make([]int, len(v5.Leaf.Issuer.Names[i11].Type)) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.Issuer.Names[i11].Type, v5.Leaf.Issuer.Names[i11].Type) - } - } - } - if v5.Leaf.Issuer.ExtraNames != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.Issuer.ExtraNames = make([]pkix.AttributeTypeAndValue, len(v5.Leaf.Issuer.ExtraNames)) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.Issuer.ExtraNames, v5.Leaf.Issuer.ExtraNames) - for i11 := range v5.Leaf.Issuer.ExtraNames { - if v5.Leaf.Issuer.ExtraNames[i11].Type != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.Issuer.ExtraNames[i11].Type = make([]int, len(v5.Leaf.Issuer.ExtraNames[i11].Type)) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.Issuer.ExtraNames[i11].Type, v5.Leaf.Issuer.ExtraNames[i11].Type) - } - } - } - if v5.Leaf.Subject.Country != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.Subject.Country = make([]string, len(v5.Leaf.Subject.Country)) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.Subject.Country, v5.Leaf.Subject.Country) - } - if v5.Leaf.Subject.Organization != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.Subject.Organization = make([]string, len(v5.Leaf.Subject.Organization)) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.Subject.Organization, v5.Leaf.Subject.Organization) - } - if v5.Leaf.Subject.OrganizationalUnit != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.Subject.OrganizationalUnit = make([]string, len(v5.Leaf.Subject.OrganizationalUnit)) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.Subject.OrganizationalUnit, v5.Leaf.Subject.OrganizationalUnit) - } - if v5.Leaf.Subject.Locality != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.Subject.Locality = make([]string, len(v5.Leaf.Subject.Locality)) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.Subject.Locality, v5.Leaf.Subject.Locality) - } - if v5.Leaf.Subject.Province != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.Subject.Province = make([]string, len(v5.Leaf.Subject.Province)) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.Subject.Province, v5.Leaf.Subject.Province) - } - if v5.Leaf.Subject.StreetAddress != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.Subject.StreetAddress = make([]string, len(v5.Leaf.Subject.StreetAddress)) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.Subject.StreetAddress, v5.Leaf.Subject.StreetAddress) - } - if v5.Leaf.Subject.PostalCode != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.Subject.PostalCode = make([]string, len(v5.Leaf.Subject.PostalCode)) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.Subject.PostalCode, v5.Leaf.Subject.PostalCode) - } - if v5.Leaf.Subject.Names != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.Subject.Names = make([]pkix.AttributeTypeAndValue, len(v5.Leaf.Subject.Names)) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.Subject.Names, v5.Leaf.Subject.Names) - for i11 := range v5.Leaf.Subject.Names { - if v5.Leaf.Subject.Names[i11].Type != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.Subject.Names[i11].Type = make([]int, len(v5.Leaf.Subject.Names[i11].Type)) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.Subject.Names[i11].Type, v5.Leaf.Subject.Names[i11].Type) - } - } - } - if v5.Leaf.Subject.ExtraNames != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.Subject.ExtraNames = make([]pkix.AttributeTypeAndValue, len(v5.Leaf.Subject.ExtraNames)) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.Subject.ExtraNames, v5.Leaf.Subject.ExtraNames) - for i11 := range v5.Leaf.Subject.ExtraNames { - if v5.Leaf.Subject.ExtraNames[i11].Type != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.Subject.ExtraNames[i11].Type = make([]int, len(v5.Leaf.Subject.ExtraNames[i11].Type)) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.Subject.ExtraNames[i11].Type, v5.Leaf.Subject.ExtraNames[i11].Type) - } - } - } - if v5.Leaf.Extensions != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.Extensions = make([]pkix.Extension, len(v5.Leaf.Extensions)) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.Extensions, v5.Leaf.Extensions) - for i10 := range v5.Leaf.Extensions { - if v5.Leaf.Extensions[i10].Id != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.Extensions[i10].Id = make([]int, len(v5.Leaf.Extensions[i10].Id)) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.Extensions[i10].Id, v5.Leaf.Extensions[i10].Id) - } - if v5.Leaf.Extensions[i10].Value != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.Extensions[i10].Value = make([]byte, len(v5.Leaf.Extensions[i10].Value)) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.Extensions[i10].Value, v5.Leaf.Extensions[i10].Value) - } - } - } - if v5.Leaf.ExtraExtensions != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.ExtraExtensions = make([]pkix.Extension, len(v5.Leaf.ExtraExtensions)) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.ExtraExtensions, v5.Leaf.ExtraExtensions) - for i10 := range v5.Leaf.ExtraExtensions { - if v5.Leaf.ExtraExtensions[i10].Id != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.ExtraExtensions[i10].Id = make([]int, len(v5.Leaf.ExtraExtensions[i10].Id)) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.ExtraExtensions[i10].Id, v5.Leaf.ExtraExtensions[i10].Id) - } - if v5.Leaf.ExtraExtensions[i10].Value != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.ExtraExtensions[i10].Value = make([]byte, len(v5.Leaf.ExtraExtensions[i10].Value)) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.ExtraExtensions[i10].Value, v5.Leaf.ExtraExtensions[i10].Value) - } - } - } - if v5.Leaf.UnhandledCriticalExtensions != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.UnhandledCriticalExtensions = make([]asn1.ObjectIdentifier, len(v5.Leaf.UnhandledCriticalExtensions)) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.UnhandledCriticalExtensions, v5.Leaf.UnhandledCriticalExtensions) - for i10 := range v5.Leaf.UnhandledCriticalExtensions { - if v5.Leaf.UnhandledCriticalExtensions[i10] != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.UnhandledCriticalExtensions[i10] = make([]int, len(v5.Leaf.UnhandledCriticalExtensions[i10])) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.UnhandledCriticalExtensions[i10], v5.Leaf.UnhandledCriticalExtensions[i10]) - } - } - } - if v5.Leaf.ExtKeyUsage != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.ExtKeyUsage = make([]x509.ExtKeyUsage, len(v5.Leaf.ExtKeyUsage)) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.ExtKeyUsage, v5.Leaf.ExtKeyUsage) - } - if v5.Leaf.UnknownExtKeyUsage != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.UnknownExtKeyUsage = make([]asn1.ObjectIdentifier, len(v5.Leaf.UnknownExtKeyUsage)) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.UnknownExtKeyUsage, v5.Leaf.UnknownExtKeyUsage) - for i10 := range v5.Leaf.UnknownExtKeyUsage { - if v5.Leaf.UnknownExtKeyUsage[i10] != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.UnknownExtKeyUsage[i10] = make([]int, len(v5.Leaf.UnknownExtKeyUsage[i10])) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.UnknownExtKeyUsage[i10], v5.Leaf.UnknownExtKeyUsage[i10]) - } - } - } - if v5.Leaf.SubjectKeyId != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.SubjectKeyId = make([]byte, len(v5.Leaf.SubjectKeyId)) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.SubjectKeyId, v5.Leaf.SubjectKeyId) - } - if v5.Leaf.AuthorityKeyId != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.AuthorityKeyId = make([]byte, len(v5.Leaf.AuthorityKeyId)) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.AuthorityKeyId, v5.Leaf.AuthorityKeyId) - } - if v5.Leaf.OCSPServer != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.OCSPServer = make([]string, len(v5.Leaf.OCSPServer)) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.OCSPServer, v5.Leaf.OCSPServer) - } - if v5.Leaf.IssuingCertificateURL != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.IssuingCertificateURL = make([]string, len(v5.Leaf.IssuingCertificateURL)) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.IssuingCertificateURL, v5.Leaf.IssuingCertificateURL) - } - if v5.Leaf.DNSNames != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.DNSNames = make([]string, len(v5.Leaf.DNSNames)) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.DNSNames, v5.Leaf.DNSNames) - } - if v5.Leaf.EmailAddresses != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.EmailAddresses = make([]string, len(v5.Leaf.EmailAddresses)) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.EmailAddresses, v5.Leaf.EmailAddresses) - } - if v5.Leaf.IPAddresses != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.IPAddresses = make([]net.IP, len(v5.Leaf.IPAddresses)) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.IPAddresses, v5.Leaf.IPAddresses) - for i10 := range v5.Leaf.IPAddresses { - if v5.Leaf.IPAddresses[i10] != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.IPAddresses[i10] = make([]byte, len(v5.Leaf.IPAddresses[i10])) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.IPAddresses[i10], v5.Leaf.IPAddresses[i10]) - } - } - } - if v5.Leaf.URIs != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.URIs = make([]*url.URL, len(v5.Leaf.URIs)) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.URIs, v5.Leaf.URIs) - for i10 := range v5.Leaf.URIs { - if v5.Leaf.URIs[i10] != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.URIs[i10] = new(url.URL) - *cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.URIs[i10] = *v5.Leaf.URIs[i10] - if v5.Leaf.URIs[i10].User != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.URIs[i10].User = new(url.Userinfo) - *cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.URIs[i10].User = *v5.Leaf.URIs[i10].User - } - } - } - } - if v5.Leaf.PermittedDNSDomains != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.PermittedDNSDomains = make([]string, len(v5.Leaf.PermittedDNSDomains)) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.PermittedDNSDomains, v5.Leaf.PermittedDNSDomains) - } - if v5.Leaf.ExcludedDNSDomains != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.ExcludedDNSDomains = make([]string, len(v5.Leaf.ExcludedDNSDomains)) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.ExcludedDNSDomains, v5.Leaf.ExcludedDNSDomains) - } - if v5.Leaf.PermittedIPRanges != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.PermittedIPRanges = make([]*net.IPNet, len(v5.Leaf.PermittedIPRanges)) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.PermittedIPRanges, v5.Leaf.PermittedIPRanges) - for i10 := range v5.Leaf.PermittedIPRanges { - if v5.Leaf.PermittedIPRanges[i10] != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.PermittedIPRanges[i10] = new(net.IPNet) - *cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.PermittedIPRanges[i10] = *v5.Leaf.PermittedIPRanges[i10] - if v5.Leaf.PermittedIPRanges[i10].IP != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.PermittedIPRanges[i10].IP = make([]byte, len(v5.Leaf.PermittedIPRanges[i10].IP)) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.PermittedIPRanges[i10].IP, v5.Leaf.PermittedIPRanges[i10].IP) - } - if v5.Leaf.PermittedIPRanges[i10].Mask != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.PermittedIPRanges[i10].Mask = make([]byte, len(v5.Leaf.PermittedIPRanges[i10].Mask)) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.PermittedIPRanges[i10].Mask, v5.Leaf.PermittedIPRanges[i10].Mask) - } - } - } - } - if v5.Leaf.ExcludedIPRanges != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.ExcludedIPRanges = make([]*net.IPNet, len(v5.Leaf.ExcludedIPRanges)) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.ExcludedIPRanges, v5.Leaf.ExcludedIPRanges) - for i10 := range v5.Leaf.ExcludedIPRanges { - if v5.Leaf.ExcludedIPRanges[i10] != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.ExcludedIPRanges[i10] = new(net.IPNet) - *cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.ExcludedIPRanges[i10] = *v5.Leaf.ExcludedIPRanges[i10] - if v5.Leaf.ExcludedIPRanges[i10].IP != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.ExcludedIPRanges[i10].IP = make([]byte, len(v5.Leaf.ExcludedIPRanges[i10].IP)) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.ExcludedIPRanges[i10].IP, v5.Leaf.ExcludedIPRanges[i10].IP) - } - if v5.Leaf.ExcludedIPRanges[i10].Mask != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.ExcludedIPRanges[i10].Mask = make([]byte, len(v5.Leaf.ExcludedIPRanges[i10].Mask)) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.ExcludedIPRanges[i10].Mask, v5.Leaf.ExcludedIPRanges[i10].Mask) - } - } - } - } - if v5.Leaf.PermittedEmailAddresses != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.PermittedEmailAddresses = make([]string, len(v5.Leaf.PermittedEmailAddresses)) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.PermittedEmailAddresses, v5.Leaf.PermittedEmailAddresses) - } - if v5.Leaf.ExcludedEmailAddresses != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.ExcludedEmailAddresses = make([]string, len(v5.Leaf.ExcludedEmailAddresses)) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.ExcludedEmailAddresses, v5.Leaf.ExcludedEmailAddresses) - } - if v5.Leaf.PermittedURIDomains != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.PermittedURIDomains = make([]string, len(v5.Leaf.PermittedURIDomains)) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.PermittedURIDomains, v5.Leaf.PermittedURIDomains) - } - if v5.Leaf.ExcludedURIDomains != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.ExcludedURIDomains = make([]string, len(v5.Leaf.ExcludedURIDomains)) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.ExcludedURIDomains, v5.Leaf.ExcludedURIDomains) - } - if v5.Leaf.CRLDistributionPoints != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.CRLDistributionPoints = make([]string, len(v5.Leaf.CRLDistributionPoints)) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.CRLDistributionPoints, v5.Leaf.CRLDistributionPoints) - } - if v5.Leaf.PolicyIdentifiers != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.PolicyIdentifiers = make([]asn1.ObjectIdentifier, len(v5.Leaf.PolicyIdentifiers)) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.PolicyIdentifiers, v5.Leaf.PolicyIdentifiers) - for i10 := range v5.Leaf.PolicyIdentifiers { - if v5.Leaf.PolicyIdentifiers[i10] != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.PolicyIdentifiers[i10] = make([]int, len(v5.Leaf.PolicyIdentifiers[i10])) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.PolicyIdentifiers[i10], v5.Leaf.PolicyIdentifiers[i10]) - } - } - } - if v5.Leaf.Policies != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.Policies = make([]x509.OID, len(v5.Leaf.Policies)) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.Policies, v5.Leaf.Policies) - } - if v5.Leaf.PolicyMappings != nil { - cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.PolicyMappings = make([]x509.PolicyMapping, len(v5.Leaf.PolicyMappings)) - copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.PolicyMappings, v5.Leaf.PolicyMappings) - } - } - } - cp.Cloud.TLSConfig.NameToCertificate[k5] = cp_Cloud_TLSConfig_NameToCertificate_v5 - } - } - if o.Cloud.TLSConfig.RootCAs != nil { - cp.Cloud.TLSConfig.RootCAs = new(x509.CertPool) - *cp.Cloud.TLSConfig.RootCAs = *o.Cloud.TLSConfig.RootCAs - } - if o.Cloud.TLSConfig.NextProtos != nil { - cp.Cloud.TLSConfig.NextProtos = make([]string, len(o.Cloud.TLSConfig.NextProtos)) - copy(cp.Cloud.TLSConfig.NextProtos, o.Cloud.TLSConfig.NextProtos) - } - if o.Cloud.TLSConfig.ClientCAs != nil { - cp.Cloud.TLSConfig.ClientCAs = new(x509.CertPool) - *cp.Cloud.TLSConfig.ClientCAs = *o.Cloud.TLSConfig.ClientCAs - } - if o.Cloud.TLSConfig.CipherSuites != nil { - cp.Cloud.TLSConfig.CipherSuites = make([]uint16, len(o.Cloud.TLSConfig.CipherSuites)) - copy(cp.Cloud.TLSConfig.CipherSuites, o.Cloud.TLSConfig.CipherSuites) - } - if o.Cloud.TLSConfig.CurvePreferences != nil { - cp.Cloud.TLSConfig.CurvePreferences = make([]tls.CurveID, len(o.Cloud.TLSConfig.CurvePreferences)) - copy(cp.Cloud.TLSConfig.CurvePreferences, o.Cloud.TLSConfig.CurvePreferences) - } - if o.Cloud.TLSConfig.EncryptedClientHelloConfigList != nil { - cp.Cloud.TLSConfig.EncryptedClientHelloConfigList = make([]byte, len(o.Cloud.TLSConfig.EncryptedClientHelloConfigList)) - copy(cp.Cloud.TLSConfig.EncryptedClientHelloConfigList, o.Cloud.TLSConfig.EncryptedClientHelloConfigList) - } - if o.Cloud.TLSConfig.EncryptedClientHelloKeys != nil { - cp.Cloud.TLSConfig.EncryptedClientHelloKeys = make([]tls.EncryptedClientHelloKey, len(o.Cloud.TLSConfig.EncryptedClientHelloKeys)) - copy(cp.Cloud.TLSConfig.EncryptedClientHelloKeys, o.Cloud.TLSConfig.EncryptedClientHelloKeys) - for i5 := range o.Cloud.TLSConfig.EncryptedClientHelloKeys { - if o.Cloud.TLSConfig.EncryptedClientHelloKeys[i5].Config != nil { - cp.Cloud.TLSConfig.EncryptedClientHelloKeys[i5].Config = make([]byte, len(o.Cloud.TLSConfig.EncryptedClientHelloKeys[i5].Config)) - copy(cp.Cloud.TLSConfig.EncryptedClientHelloKeys[i5].Config, o.Cloud.TLSConfig.EncryptedClientHelloKeys[i5].Config) - } - if o.Cloud.TLSConfig.EncryptedClientHelloKeys[i5].PrivateKey != nil { - cp.Cloud.TLSConfig.EncryptedClientHelloKeys[i5].PrivateKey = make([]byte, len(o.Cloud.TLSConfig.EncryptedClientHelloKeys[i5].PrivateKey)) - copy(cp.Cloud.TLSConfig.EncryptedClientHelloKeys[i5].PrivateKey, o.Cloud.TLSConfig.EncryptedClientHelloKeys[i5].PrivateKey) - } - } - } - } if o.DNSServiceTTL != nil { cp.DNSServiceTTL = make(map[string]time.Duration, len(o.DNSServiceTTL)) for k2, v2 := range o.DNSServiceTTL { diff --git a/agent/config/runtime.go b/agent/config/runtime.go index 97de5bff0fb8..69512c32271b 100644 --- a/agent/config/runtime.go +++ b/agent/config/runtime.go @@ -17,7 +17,6 @@ import ( "github.com/hashicorp/consul/agent/cache" "github.com/hashicorp/consul/agent/consul" consulrate "github.com/hashicorp/consul/agent/consul/rate" - hcpconfig "github.com/hashicorp/consul/agent/hcp/config" "github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/agent/token" "github.com/hashicorp/consul/api" @@ -162,11 +161,6 @@ type RuntimeConfig struct { // hcl: autopilot { upgrade_version_tag = string } AutopilotUpgradeVersionTag string - // Cloud contains configuration for agents to connect to HCP. - // - // hcl: cloud { ... } - Cloud hcpconfig.CloudConfig - // DNSAllowStale is used to enable lookups with stale // data. This gives horizontal read scalability since // any Consul server can service the query instead of @@ -1586,7 +1580,6 @@ type UIConfig struct { MetricsProviderOptionsJSON string MetricsProxy UIMetricsProxy DashboardURLTemplates map[string]string - HCPEnabled bool } type UIMetricsProxy struct { @@ -1809,14 +1802,6 @@ func (c *RuntimeConfig) Sanitized() map[string]interface{} { return sanitize("rt", reflect.ValueOf(c)).Interface().(map[string]interface{}) } -// IsCloudEnabled returns true if a cloud.resource_id is set and the server mode is enabled -func (c *RuntimeConfig) IsCloudEnabled() bool { - if c == nil { - return false - } - return c.ServerMode && c.Cloud.ResourceID != "" -} - // isSecret determines whether a field name represents a field which // may contain a secret. func isSecret(name string) bool { diff --git a/agent/config/runtime_test.go b/agent/config/runtime_test.go index 35004cdd586e..d6139af0f1d1 100644 --- a/agent/config/runtime_test.go +++ b/agent/config/runtime_test.go @@ -30,7 +30,6 @@ import ( "github.com/hashicorp/consul/agent/checks" "github.com/hashicorp/consul/agent/consul" consulrate "github.com/hashicorp/consul/agent/consul/rate" - hcpconfig "github.com/hashicorp/consul/agent/hcp/config" "github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/agent/token" "github.com/hashicorp/consul/lib" @@ -622,7 +621,6 @@ func TestLoad_IntegrationWithFlags(t *testing.T) { rt.NodeName = "a" rt.TLS.NodeName = "a" rt.DataDir = dataDir - rt.Cloud.NodeName = "a" }, }) run(t, testCase{ @@ -634,7 +632,6 @@ func TestLoad_IntegrationWithFlags(t *testing.T) { expected: func(rt *RuntimeConfig) { rt.NodeID = "a" rt.DataDir = dataDir - rt.Cloud.NodeID = "a" }, }) run(t, testCase{ @@ -2324,77 +2321,6 @@ func TestLoad_IntegrationWithFlags(t *testing.T) { rt.HTTPUseCache = false }, }) - run(t, testCase{ - desc: "cloud resource id from env", - args: []string{ - `-server`, - `-data-dir=` + dataDir, - }, - setup: func() { - os.Setenv("HCP_RESOURCE_ID", "env-id") - }, - cleanup: func() { - os.Unsetenv("HCP_RESOURCE_ID") - }, - expected: func(rt *RuntimeConfig) { - rt.DataDir = dataDir - rt.Cloud = hcpconfig.CloudConfig{ - ResourceID: "env-id", - NodeName: "thehostname", - NodeID: "", - } - - // server things - rt.ServerMode = true - rt.Telemetry.EnableHostMetrics = true - rt.TLS.ServerMode = true - rt.LeaveOnTerm = false - rt.SkipLeaveOnInt = true - rt.RPCConfig.EnableStreaming = true - rt.GRPCTLSPort = 8503 - rt.GRPCTLSAddrs = []net.Addr{defaultGrpcTlsAddr} - }, - }) - run(t, testCase{ - desc: "cloud resource id from file", - args: []string{ - `-server`, - `-data-dir=` + dataDir, - }, - setup: func() { - os.Setenv("HCP_RESOURCE_ID", "env-id") - }, - cleanup: func() { - os.Unsetenv("HCP_RESOURCE_ID") - }, - json: []string{`{ - "cloud": { - "resource_id": "file-id" - } - }`}, - hcl: []string{` - cloud = { - resource_id = "file-id" - } - `}, - expected: func(rt *RuntimeConfig) { - rt.DataDir = dataDir - rt.Cloud = hcpconfig.CloudConfig{ - ResourceID: "env-id", - NodeName: "thehostname", - } - - // server things - rt.ServerMode = true - rt.Telemetry.EnableHostMetrics = true - rt.TLS.ServerMode = true - rt.LeaveOnTerm = false - rt.SkipLeaveOnInt = true - rt.RPCConfig.EnableStreaming = true - rt.GRPCTLSPort = 8503 - rt.GRPCTLSAddrs = []net.Addr{defaultGrpcTlsAddr} - }, - }) run(t, testCase{ desc: "sidecar_service can't have ID", args: []string{ @@ -6677,56 +6603,46 @@ func TestLoad_FullConfig(t *testing.T) { ConnectVirtualIPCIDRv4: "240.0.0.0/4", ConnectVirtualIPCIDRv6: "2000::/3", ConnectMeshGatewayWANFederationEnabled: false, - Cloud: hcpconfig.CloudConfig{ - ResourceID: "N43DsscE", - ClientID: "6WvsDZCP", - ClientSecret: "lCSMHOpB", - Hostname: "DH4bh7aC", - AuthURL: "332nCdR2", - ScadaAddress: "aoeusth232", - NodeID: types.NodeID("AsUIlw99"), - NodeName: "otlLxGaI", - }, - DNSAddrs: []net.Addr{tcpAddr("93.95.95.81:7001"), udpAddr("93.95.95.81:7001")}, - DNSARecordLimit: 29907, - DNSAllowStale: true, - DNSDisableCompression: true, - DNSDomain: "7W1xXSqd", - DNSAltDomain: "1789hsd", - DNSEnableTruncate: true, - DNSMaxStale: 29685 * time.Second, - DNSNodeTTL: 7084 * time.Second, - DNSOnlyPassing: true, - DNSPort: 7001, - DNSRecursorStrategy: "sequential", - DNSRecursorTimeout: 4427 * time.Second, - DNSRecursors: []string{"63.38.39.58", "92.49.18.18"}, - DNSSOA: RuntimeSOAConfig{Refresh: 3600, Retry: 600, Expire: 86400, Minttl: 0}, - DNSServiceTTL: map[string]time.Duration{"*": 32030 * time.Second}, - DNSUDPAnswerLimit: 29909, - DNSNodeMetaTXT: true, - DNSUseCache: true, - DNSCacheMaxAge: 5 * time.Minute, - DataDir: dataDir, - Datacenter: "rzo029wg", - DefaultQueryTime: 16743 * time.Second, - DisableAnonymousSignature: true, - DisableCoordinates: true, - DisableHostNodeID: true, - DisableHTTPUnprintableCharFilter: true, - DisableKeyringFile: true, - DisableRemoteExec: true, - DisableUpdateCheck: true, - DiscardCheckOutput: true, - DiscoveryMaxStale: 5 * time.Second, - EnableAgentTLSForChecks: true, - EnableCentralServiceConfig: false, - EnableDebug: true, - DisableKVKeyValidation: false, - EnableRemoteScriptChecks: true, - EnableLocalScriptChecks: true, - EncryptKey: "A4wELWqH", - Experiments: []string{"foo"}, + DNSAddrs: []net.Addr{tcpAddr("93.95.95.81:7001"), udpAddr("93.95.95.81:7001")}, + DNSARecordLimit: 29907, + DNSAllowStale: true, + DNSDisableCompression: true, + DNSDomain: "7W1xXSqd", + DNSAltDomain: "1789hsd", + DNSEnableTruncate: true, + DNSMaxStale: 29685 * time.Second, + DNSNodeTTL: 7084 * time.Second, + DNSOnlyPassing: true, + DNSPort: 7001, + DNSRecursorStrategy: "sequential", + DNSRecursorTimeout: 4427 * time.Second, + DNSRecursors: []string{"63.38.39.58", "92.49.18.18"}, + DNSSOA: RuntimeSOAConfig{Refresh: 3600, Retry: 600, Expire: 86400, Minttl: 0}, + DNSServiceTTL: map[string]time.Duration{"*": 32030 * time.Second}, + DNSUDPAnswerLimit: 29909, + DNSNodeMetaTXT: true, + DNSUseCache: true, + DNSCacheMaxAge: 5 * time.Minute, + DataDir: dataDir, + Datacenter: "rzo029wg", + DefaultQueryTime: 16743 * time.Second, + DisableAnonymousSignature: true, + DisableCoordinates: true, + DisableHostNodeID: true, + DisableHTTPUnprintableCharFilter: true, + DisableKeyringFile: true, + DisableRemoteExec: true, + DisableUpdateCheck: true, + DiscardCheckOutput: true, + DiscoveryMaxStale: 5 * time.Second, + EnableAgentTLSForChecks: true, + EnableCentralServiceConfig: false, + EnableDebug: true, + DisableKVKeyValidation: false, + EnableRemoteScriptChecks: true, + EnableLocalScriptChecks: true, + EncryptKey: "A4wELWqH", + Experiments: []string{"foo"}, StaticRuntimeConfig: StaticRuntimeConfig{ EncryptVerifyIncoming: true, EncryptVerifyOutgoing: true, @@ -7536,11 +7452,6 @@ func TestRuntimeConfig_Sanitize(t *testing.T) { EntryFetchMaxBurst: 42, EntryFetchRate: 0.334, }, - Cloud: hcpconfig.CloudConfig{ - ResourceID: "cluster1", - ClientID: "id", - ClientSecret: "secret", - }, ConsulCoordinateUpdatePeriod: 15 * time.Second, RaftProtocol: 3, RetryJoinLAN: []string{ diff --git a/agent/config/testdata/TestRuntimeConfig_Sanitize.golden b/agent/config/testdata/TestRuntimeConfig_Sanitize.golden index b767ec60ef5b..ef58c0af83e9 100644 --- a/agent/config/testdata/TestRuntimeConfig_Sanitize.golden +++ b/agent/config/testdata/TestRuntimeConfig_Sanitize.golden @@ -129,18 +129,6 @@ } ], "ClientAddrs": [], - "Cloud": { - "AuthURL": "", - "ClientID": "id", - "ClientSecret": "hidden", - "Hostname": "", - "ManagementToken": "hidden", - "NodeID": "", - "NodeName": "", - "ResourceID": "cluster1", - "ScadaAddress": "", - "TLSConfig": null - }, "ConfigEntryBootstrap": [], "ConnectCAConfig": {}, "ConnectCAProvider": "", @@ -504,7 +492,6 @@ "DashboardURLTemplates": {}, "Dir": "", "Enabled": false, - "HCPEnabled": false, "MetricsProvider": "", "MetricsProviderFiles": [], "MetricsProviderOptionsJSON": "", diff --git a/agent/consul/config.go b/agent/consul/config.go index e50d664c02e1..f9a131226fdb 100644 --- a/agent/consul/config.go +++ b/agent/consul/config.go @@ -18,7 +18,6 @@ import ( "github.com/hashicorp/consul/agent/checks" consulrate "github.com/hashicorp/consul/agent/consul/rate" "github.com/hashicorp/consul/agent/consul/reporting" - hcpconfig "github.com/hashicorp/consul/agent/hcp/config" "github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/internal/gossip/libserf" "github.com/hashicorp/consul/tlsutil" @@ -460,8 +459,6 @@ type Config struct { Locality *structs.Locality - Cloud hcpconfig.CloudConfig - Reporting Reporting // Embedded Consul Enterprise specific configuration diff --git a/agent/consul/leader.go b/agent/consul/leader.go index 4b47fe30b055..15eae91396f8 100644 --- a/agent/consul/leader.go +++ b/agent/consul/leader.go @@ -433,14 +433,6 @@ func (s *Server) initializeACLs(ctx context.Context) error { } } - // Check for configured management token from HCP. It MUST NOT override the user-provided initial management token. - if hcpManagement := s.config.Cloud.ManagementToken; len(hcpManagement) > 0 { - err := s.initializeManagementToken("HCP Management Token", hcpManagement) - if err != nil { - return fmt.Errorf("failed to initialize HCP management token: %w", err) - } - } - // Insert the anonymous token if it does not exist. if err := s.insertAnonymousToken(); err != nil { return err diff --git a/agent/consul/leader_test.go b/agent/consul/leader_test.go index 16c21e18d445..ce003b0b4b1b 100644 --- a/agent/consul/leader_test.go +++ b/agent/consul/leader_test.go @@ -396,7 +396,6 @@ func TestLeader_ACL_Initialization(t *testing.T) { tests := []struct { name string initialManagement string - hcpManagement string // canBootstrap tracks whether the ACL system can be bootstrapped // after the leader initializes ACLs. Bootstrapping is the act @@ -406,25 +405,11 @@ func TestLeader_ACL_Initialization(t *testing.T) { { name: "bootstrap from initial management", initialManagement: "c9ad785a-420d-470d-9b4d-6d9f084bfa87", - hcpManagement: "", - canBootstrap: false, - }, - { - name: "bootstrap from hcp management", - initialManagement: "", - hcpManagement: "924bc0e1-a41b-4f3a-b5e8-0899502fc50e", - canBootstrap: false, - }, - { - name: "bootstrap with both", - initialManagement: "c9ad785a-420d-470d-9b4d-6d9f084bfa87", - hcpManagement: "924bc0e1-a41b-4f3a-b5e8-0899502fc50e", canBootstrap: false, }, { name: "did not bootstrap", initialManagement: "", - hcpManagement: "", canBootstrap: true, }, } @@ -436,7 +421,6 @@ func TestLeader_ACL_Initialization(t *testing.T) { c.PrimaryDatacenter = "dc1" c.ACLsEnabled = true c.ACLInitialManagementToken = tt.initialManagement - c.Cloud.ManagementToken = tt.hcpManagement } _, s1 := testServerWithConfig(t, conf) testrpc.WaitForTestAgent(t, s1.RPC, "dc1") @@ -455,13 +439,6 @@ func TestLeader_ACL_Initialization(t *testing.T) { require.Equal(t, tt.initialManagement, initialManagement.SecretID) } - if tt.hcpManagement != "" { - _, hcpManagement, err := s1.fsm.State().ACLTokenGetBySecret(nil, tt.hcpManagement, nil) - require.NoError(t, err) - require.NotNil(t, hcpManagement) - require.Equal(t, tt.hcpManagement, hcpManagement.SecretID) - } - canBootstrap, _, err := s1.fsm.State().CanBootstrapACLToken() require.NoError(t, err) require.Equal(t, tt.canBootstrap, canBootstrap) diff --git a/agent/consul/options.go b/agent/consul/options.go index 8c9fe05f4873..b75e8730df83 100644 --- a/agent/consul/options.go +++ b/agent/consul/options.go @@ -11,7 +11,6 @@ import ( "github.com/hashicorp/consul/agent/consul/stream" "github.com/hashicorp/consul/agent/grpc-external/limiter" - "github.com/hashicorp/consul/agent/hcp" "github.com/hashicorp/consul/agent/leafcert" "github.com/hashicorp/consul/agent/pool" "github.com/hashicorp/consul/agent/router" @@ -40,9 +39,6 @@ type Deps struct { // NewRequestRecorderFunc provides a middleware.RequestRecorder for the server to use; it cannot be nil NewRequestRecorderFunc func(logger hclog.Logger, isLeader func() bool, localDC string) *middleware.RequestRecorder - // HCP contains the dependencies required when integrating with the HashiCorp Cloud Platform - HCP hcp.Deps - Experiments []string EnterpriseDeps diff --git a/agent/consul/server.go b/agent/consul/server.go index 283c080d5ea3..2439c3b35d59 100644 --- a/agent/consul/server.go +++ b/agent/consul/server.go @@ -5,7 +5,6 @@ package consul import ( "context" - "crypto/x509" "errors" "fmt" "io" @@ -39,7 +38,6 @@ import ( "github.com/hashicorp/consul/acl" "github.com/hashicorp/consul/agent/blockingquery" - "github.com/hashicorp/consul/agent/connect" "github.com/hashicorp/consul/agent/consul/authmethod" "github.com/hashicorp/consul/agent/consul/authmethod/ssoauth" "github.com/hashicorp/consul/agent/consul/fsm" @@ -52,8 +50,6 @@ import ( "github.com/hashicorp/consul/agent/consul/wanfed" "github.com/hashicorp/consul/agent/consul/xdscapacity" "github.com/hashicorp/consul/agent/grpc-external/services/peerstream" - "github.com/hashicorp/consul/agent/hcp" - hcpclient "github.com/hashicorp/consul/agent/hcp/client" logdrop "github.com/hashicorp/consul/agent/log-drop" "github.com/hashicorp/consul/agent/metadata" "github.com/hashicorp/consul/agent/pool" @@ -76,7 +72,6 @@ import ( "github.com/hashicorp/consul/proto-public/pbresource" "github.com/hashicorp/consul/tlsutil" "github.com/hashicorp/consul/types" - cslversion "github.com/hashicorp/consul/version" ) // NOTE The "consul.client.rpc" and "consul.client.rpc.exceeded" counters are defined in consul/client.go @@ -432,9 +427,6 @@ type Server struct { // server is able to handle. xdsCapacityController *xdscapacity.Controller - // hcpManager handles pushing server status updates to the HashiCorp Cloud Platform when enabled - hcpManager *hcp.HCPManager - // embedded struct to hold all the enterprise specific data EnterpriseServer @@ -549,35 +541,6 @@ func NewServer(config *Config, flat Deps, externalGRPCServer *grpc.Server, StorageBackend: s.raftStorageBackend, }) - if s.config.Cloud.IsConfigured() { - s.hcpManager = hcp.NewManager(hcp.ManagerConfig{ - CloudConfig: flat.HCP.Config, - StatusFn: s.hcpServerStatus(flat), - Logger: logger.Named("hcp_manager"), - SCADAProvider: flat.HCP.Provider, - TelemetryProvider: flat.HCP.TelemetryProvider, - ManagementTokenUpserterFn: func(name, secretId string) error { - // Check the state of the server before attempting to upsert the token. Otherwise, - // the upsert will fail and log errors that do not require action from the user. - if s.config.ACLsEnabled && s.IsLeader() && s.InPrimaryDatacenter() { - // Idea for improvement: Upsert a token with a well-known accessorId here instead - // of a randomly generated one. This would prevent any possible insertion collision between - // this and the insertion that happens during the ACL initialization process (initializeACLs function) - return s.upsertManagementToken(name, secretId) - } - return nil - }, - ManagementTokenDeleterFn: func(secretId string) error { - // Check the state of the server before attempting to delete the token.Otherwise, - // the delete will fail and log errors that do not require action from the user. - if s.config.ACLsEnabled && s.IsLeader() && s.InPrimaryDatacenter() { - return s.deleteManagementToken(secretId) - } - return nil - }, - }) - } - var recorder *middleware.RequestRecorder if flat.NewRequestRecorderFunc != nil { recorder = flat.NewRequestRecorderFunc(serverLogger, s.IsLeader, s.config.Datacenter) @@ -1924,10 +1887,6 @@ func (s *Server) trackLeaderChanges() { s.raftStorageBackend.LeaderChanged() s.controllerManager.SetRaftLeader(s.IsLeader()) - if s.config.Cloud.IsConfigured() { - // Trigger sending an update to HCP status - s.hcpManager.SendUpdate() - } case <-s.shutdownCh: s.raft.DeregisterObserver(observer) return @@ -1935,49 +1894,6 @@ func (s *Server) trackLeaderChanges() { } } -// hcpServerStatus is the callback used by the HCP manager to emit status updates to the HashiCorp Cloud Platform when -// enabled. -func (s *Server) hcpServerStatus(deps Deps) hcp.StatusCallback { - return func(ctx context.Context) (status hcpclient.ServerStatus, err error) { - status.Name = s.config.NodeName - status.ID = string(s.config.NodeID) - status.Version = cslversion.GetHumanVersion() - status.LanAddress = s.config.RPCAdvertise.IP.String() - status.GossipPort = s.config.SerfLANConfig.MemberlistConfig.AdvertisePort - status.RPCPort = s.config.RPCAddr.Port - status.Datacenter = s.config.Datacenter - - err = addServerTLSInfo(&status, s.tlsConfigurator) - if err != nil { - return status, fmt.Errorf("error adding server tls info: %w", err) - } - - status.Raft.IsLeader = s.raft.State() == raft.Leader - _, leaderID := s.raft.LeaderWithID() - status.Raft.KnownLeader = leaderID != "" - status.Raft.AppliedIndex = s.raft.AppliedIndex() - if !status.Raft.IsLeader { - status.Raft.TimeSinceLastContact = time.Since(s.raft.LastContact()) - } - - apState := s.autopilot.GetState() - status.Autopilot.Healthy = apState.Healthy - status.Autopilot.FailureTolerance = apState.FailureTolerance - status.Autopilot.NumServers = len(apState.Servers) - status.Autopilot.NumVoters = len(apState.Voters) - status.Autopilot.MinQuorum = int(s.getAutopilotConfigOrDefault().MinQuorum) - - status.ScadaStatus = "unknown" - if deps.HCP.Provider != nil { - status.ScadaStatus = deps.HCP.Provider.SessionStatus() - } - - status.ACL.Enabled = s.config.ACLsEnabled - - return status, nil - } -} - func (s *Server) ResourceServiceClient() pbresource.ResourceServiceClient { return pbresource.NewResourceServiceClient(s.secureSafeGRPCChan) } @@ -2038,83 +1954,6 @@ func convertConsulConfigToRateLimitHandlerConfig(limitsConfig RequestLimits, mul return hc } -// addServerTLSInfo adds the server's TLS information if available to the status -func addServerTLSInfo(status *hcpclient.ServerStatus, tlsConfigurator tlsutil.ConfiguratorIface) error { - tlsCert := tlsConfigurator.Cert() - if tlsCert == nil { - return nil - } - - leaf := tlsCert.Leaf - var err error - if leaf == nil { - // Parse the leaf cert - if len(tlsCert.Certificate) == 0 { - return fmt.Errorf("expected a leaf certificate but there was none") - } - leaf, err = x509.ParseCertificate(tlsCert.Certificate[0]) - if err != nil { - // Shouldn't be possible - return fmt.Errorf("error parsing leaf cert: %w", err) - } - } - - tlsInfo := hcpclient.ServerTLSInfo{ - Enabled: true, - CertIssuer: leaf.Issuer.CommonName, - CertName: leaf.Subject.CommonName, - CertSerial: leaf.SerialNumber.String(), - CertExpiry: leaf.NotAfter, - VerifyIncoming: tlsConfigurator.VerifyIncomingRPC(), - VerifyOutgoing: tlsConfigurator.Base().InternalRPC.VerifyOutgoing, - VerifyServerHostname: tlsConfigurator.VerifyServerHostname(), - } - - // Collect metadata for all CA certs used for internal RPC - metadata := make([]hcpclient.CertificateMetadata, 0) - for _, pemStr := range tlsConfigurator.ManualCAPems() { - cert, err := connect.ParseCert(pemStr) - if err != nil { - return fmt.Errorf("error parsing manual ca pem: %w", err) - } - - metadatum := hcpclient.CertificateMetadata{ - CertExpiry: cert.NotAfter, - CertName: cert.Subject.CommonName, - CertSerial: cert.SerialNumber.String(), - } - metadata = append(metadata, metadatum) - } - for ix, certBytes := range tlsCert.Certificate { - if ix == 0 { - // Skip the leaf cert at index 0. Only collect intermediates - continue - } - - cert, err := x509.ParseCertificate(certBytes) - if err != nil { - return fmt.Errorf("error parsing tls cert index %d: %w", ix, err) - } - - metadatum := hcpclient.CertificateMetadata{ - CertExpiry: cert.NotAfter, - CertName: cert.Subject.CommonName, - CertSerial: cert.SerialNumber.String(), - } - metadata = append(metadata, metadatum) - } - tlsInfo.CertificateAuthorities = metadata - - status.ServerTLSMetadata.InternalRPC = tlsInfo - - // TODO: remove status.TLS in preference for server.ServerTLSMetadata.InternalRPC - // when deprecation path is ready - // https://hashicorp.atlassian.net/browse/CC-7015 - status.TLS = tlsInfo - - return nil -} - // peersInfoContent is used to help operators understand what happened to the // peers.json file. This is written to a file called peers.info in the same // location. diff --git a/agent/consul/server_test.go b/agent/consul/server_test.go index 1d5b6fa5c638..b8cb3d333ef4 100644 --- a/agent/consul/server_test.go +++ b/agent/consul/server_test.go @@ -5,7 +5,6 @@ package consul import ( "context" - "crypto/tls" "crypto/x509" "fmt" "net" @@ -36,8 +35,6 @@ import ( rpcRate "github.com/hashicorp/consul/agent/consul/rate" external "github.com/hashicorp/consul/agent/grpc-external" grpcmiddleware "github.com/hashicorp/consul/agent/grpc-middleware" - hcpclient "github.com/hashicorp/consul/agent/hcp/client" - hcpconfig "github.com/hashicorp/consul/agent/hcp/config" "github.com/hashicorp/consul/agent/leafcert" "github.com/hashicorp/consul/agent/metadata" "github.com/hashicorp/consul/agent/rpc/middleware" @@ -2169,209 +2166,6 @@ func TestServer_Peering_LeadershipCheck(t *testing.T) { require.NotEqual(t, s2.config.RPCAddr.String(), peeringLeaderAddr) } -func TestServer_hcpManager(t *testing.T) { - _, conf1 := testServerConfig(t) - - // Configure the server for the StatusFn - conf1.BootstrapExpect = 1 - conf1.RPCAdvertise = &net.TCPAddr{IP: []byte{127, 0, 0, 2}, Port: conf1.RPCAddr.Port} - conf1.Cloud.ClientID = "test-client-id" - conf1.Cloud.ResourceID = "test-resource-id" - conf1.Cloud.ClientSecret = "test-client-secret" - hcp1 := hcpclient.NewMockClient(t) - hcp1.EXPECT().PushServerStatus(mock.Anything, mock.MatchedBy(func(status *hcpclient.ServerStatus) bool { - return status.ID == string(conf1.NodeID) - })).Run(func(ctx context.Context, status *hcpclient.ServerStatus) { - require.Equal(t, status.LanAddress, "127.0.0.2") - }).Call.Return(nil) - - // Configure the server for the ManagementTokenUpserterFn - conf1.ACLsEnabled = true - - deps1 := newDefaultDeps(t, conf1) - s1, err := newServerWithDeps(t, conf1, deps1) - if err != nil { - t.Fatalf("err: %v", err) - } - defer s1.Shutdown() - require.NotNil(t, s1.hcpManager) - waitForLeaderEstablishment(t, s1) - - // Update the HCP manager and start it - token, err := uuid.GenerateUUID() - require.NoError(t, err) - s1.hcpManager.UpdateConfig(hcp1, hcpconfig.CloudConfig{ - ManagementToken: token, - }) - err = s1.hcpManager.Start(context.Background()) - require.NoError(t, err) - - // Validate that the server status pushed as expected - hcp1.AssertExpectations(t) - - // Validate that the HCP token has been created as expected - retry.Run(t, func(r *retry.R) { - _, createdToken, err := s1.fsm.State().ACLTokenGetBySecret(nil, token, nil) - require.NoError(r, err) - require.NotNil(r, createdToken) - }) - - // Stop the HCP manager - err = s1.hcpManager.Stop() - require.NoError(t, err) - - // Validate that the HCP token has been deleted as expected - retry.Run(t, func(r *retry.R) { - _, createdToken, err := s1.fsm.State().ACLTokenGetBySecret(nil, token, nil) - require.NoError(r, err) - require.Nil(r, createdToken) - }) -} - -func TestServer_addServerTLSInfo(t *testing.T) { - testCases := map[string]struct { - errMsg string - setupConfigurator func(*testing.T) tlsutil.ConfiguratorIface - checkStatus func(*testing.T, hcpclient.ServerStatus) - }{ - "Success": { - setupConfigurator: func(t *testing.T) tlsutil.ConfiguratorIface { - tlsConfig := tlsutil.Config{ - InternalRPC: tlsutil.ProtocolConfig{ - CAFile: "../../test/ca/root.cer", - CertFile: "../../test/key/ourdomain_with_intermediate.cer", - KeyFile: "../../test/key/ourdomain.key", - VerifyIncoming: true, - VerifyOutgoing: true, - VerifyServerHostname: true, - }, - } - - tlsConfigurator, err := tlsutil.NewConfigurator(tlsConfig, hclog.NewNullLogger()) - require.NoError(t, err) - return tlsConfigurator - }, - checkStatus: func(t *testing.T, s hcpclient.ServerStatus) { - expected := hcpclient.ServerTLSInfo{ - Enabled: true, - CertIssuer: "test.internal", - CertName: "testco.internal", - CertSerial: "40", - CertExpiry: time.Date(2123, time.October, 9, 17, 20, 16, 0, time.UTC), - VerifyIncoming: true, - VerifyOutgoing: true, - VerifyServerHostname: true, - CertificateAuthorities: []hcpclient.CertificateMetadata{ - { // manual ca pem - CertExpiry: time.Date(2033, time.October, 30, 15, 50, 29, 0, time.UTC), - CertName: "test.internal", - CertSerial: "191297809789001034260919865367524695178070761520", - }, - { // certificate intermediate - CertExpiry: time.Date(2033, time.October, 30, 15, 50, 29, 0, time.UTC), - CertName: "test.internal", - CertSerial: "191297809789001034260919865367524695178070761520", - }, - }, - } - - require.Equal(t, expected, s.ServerTLSMetadata.InternalRPC) - - // TODO: remove check for status.TLS once deprecation is ready - // https://hashicorp.atlassian.net/browse/CC-7015 - require.Equal(t, expected, s.TLS) - }, - }, - "Nil Cert": { - setupConfigurator: func(t *testing.T) tlsutil.ConfiguratorIface { - tlsConfigurator, err := tlsutil.NewConfigurator(tlsutil.Config{}, - hclog.NewNullLogger()) - require.NoError(t, err) - return tlsConfigurator - }, - checkStatus: func(t *testing.T, s hcpclient.ServerStatus) { - require.Empty(t, s.TLS) - require.Empty(t, s.ServerTLSMetadata.InternalRPC) - }, - }, - "Fail: No leaf": { - errMsg: "expected a leaf certificate", - setupConfigurator: func(t *testing.T) tlsutil.ConfiguratorIface { - return tlsutil.MockConfigurator{ - TlsCert: &tls.Certificate{}, - } - }, - }, - "Fail: Parse leaf cert": { - errMsg: "error parsing leaf cert", - setupConfigurator: func(t *testing.T) tlsutil.ConfiguratorIface { - return tlsutil.MockConfigurator{ - TlsCert: &tls.Certificate{ - Certificate: [][]byte{{}}, - }, - } - }, - }, - "Fail: Parse manual ca pems": { - errMsg: "error parsing manual ca pem", - setupConfigurator: func(t *testing.T) tlsutil.ConfiguratorIface { - tlsConfig := tlsutil.Config{ - InternalRPC: tlsutil.ProtocolConfig{ - CertFile: "../../test/key/ourdomain.cer", - KeyFile: "../../test/key/ourdomain.key", - }, - } - tlsConfigurator, err := tlsutil.NewConfigurator(tlsConfig, hclog.NewNullLogger()) - require.NoError(t, err) - - return tlsutil.MockConfigurator{ - TlsCert: tlsConfigurator.Cert(), - ManualCAPemsArr: []string{"invalid-format"}, - } - }, - }, - "Fail: Parse tls cert intermediate": { - errMsg: "error parsing tls cert", - setupConfigurator: func(t *testing.T) tlsutil.ConfiguratorIface { - tlsConfig := tlsutil.Config{ - InternalRPC: tlsutil.ProtocolConfig{ - CertFile: "../../test/key/ourdomain.cer", - KeyFile: "../../test/key/ourdomain.key", - }, - } - tlsConfigurator, err := tlsutil.NewConfigurator(tlsConfig, hclog.NewNullLogger()) - require.NoError(t, err) - cert := tlsConfigurator.Cert().Certificate - cert = append(cert, []byte{}) - return tlsutil.MockConfigurator{ - TlsCert: &tls.Certificate{ - Certificate: cert, - }, - } - }, - }, - } - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - require.NotNil(t, tc.setupConfigurator) - tlsConfigurator := tc.setupConfigurator(t) - - status := hcpclient.ServerStatus{} - err := addServerTLSInfo(&status, tlsConfigurator) - - if len(tc.errMsg) > 0 { - require.Error(t, err) - require.Contains(t, err.Error(), tc.errMsg) - require.Empty(t, status) - } else { - require.NoError(t, err) - require.NotNil(t, tc.checkStatus) - tc.checkStatus(t, status) - } - }) - } -} - func TestServer_ControllerDependencies(t *testing.T) { // The original goal of this test was to track controller/resource type dependencies // as they change over time. However, the test is difficult to maintain and provides diff --git a/agent/hcp/bootstrap/bootstrap.go b/agent/hcp/bootstrap/bootstrap.go deleted file mode 100644 index fd2a34250508..000000000000 --- a/agent/hcp/bootstrap/bootstrap.go +++ /dev/null @@ -1,481 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -// Package bootstrap handles bootstrapping an agent's config from HCP. -package bootstrap - -import ( - "bufio" - "context" - "crypto/tls" - "crypto/x509" - "encoding/json" - "encoding/pem" - "errors" - "fmt" - "os" - "path/filepath" - "strings" - "time" - - "github.com/hashicorp/go-hclog" - "github.com/hashicorp/go-uuid" - - "github.com/hashicorp/consul/agent/connect" - "github.com/hashicorp/consul/agent/hcp/bootstrap/constants" - hcpclient "github.com/hashicorp/consul/agent/hcp/client" - "github.com/hashicorp/consul/lib" - "github.com/hashicorp/consul/lib/retry" -) - -const ( - CAFileName = "server-tls-cas.pem" - CertFileName = "server-tls-cert.pem" - ConfigFileName = "server-config.json" - KeyFileName = "server-tls-key.pem" - TokenFileName = "hcp-management-token" - SuccessFileName = "successful-bootstrap" -) - -// UI is a shim to allow the agent command to pass in it's mitchelh/cli.UI so we -// can output useful messages to the user during bootstrapping. For example if -// we have to retry several times to bootstrap we don't want the agent to just -// stall with no output which is the case if we just returned all intermediate -// warnings or errors. -type UI interface { - Output(string) - Warn(string) - Info(string) - Error(string) -} - -// RawBootstrapConfig contains the Consul config as a raw JSON string and the management token -// which either was retrieved from persisted files or from the bootstrap endpoint -type RawBootstrapConfig struct { - ConfigJSON string - ManagementToken string -} - -// FetchBootstrapConfig will fetch bootstrap configuration from remote servers and persist it to disk. -// It will retry until successful or a terminal error condition is found (e.g. permission denied). -func FetchBootstrapConfig(ctx context.Context, client hcpclient.Client, dataDir string, ui UI) (*RawBootstrapConfig, error) { - w := retry.Waiter{ - MinWait: 1 * time.Second, - MaxWait: 5 * time.Minute, - Jitter: retry.NewJitter(50), - } - - for { - // Note we don't want to shadow `ctx` here since we need that for the Wait - // below. - reqCtx, cancel := context.WithTimeout(ctx, 5*time.Second) - defer cancel() - - cfg, err := fetchBootstrapConfig(reqCtx, client, dataDir) - if err != nil { - if errors.Is(err, hcpclient.ErrUnauthorized) || errors.Is(err, hcpclient.ErrForbidden) { - // Don't retry on terminal errors - return nil, err - } - ui.Error(fmt.Sprintf("Error: failed to fetch bootstrap config from HCP, will retry in %s: %s", - w.NextWait().Round(time.Second), err)) - if err := w.Wait(ctx); err != nil { - return nil, err - } - // Finished waiting, restart loop - continue - } - return cfg, nil - } -} - -// fetchBootstrapConfig will fetch the bootstrap configuration from remote servers and persist it to disk. -func fetchBootstrapConfig(ctx context.Context, client hcpclient.Client, dataDir string) (*RawBootstrapConfig, error) { - reqCtx, cancel := context.WithTimeout(ctx, 5*time.Second) - defer cancel() - - resp, err := client.FetchBootstrap(reqCtx) - if err != nil { - return nil, fmt.Errorf("failed to fetch bootstrap config from HCP: %w", err) - } - - bsCfg := resp - devMode := dataDir == "" - cfgJSON, err := persistAndProcessConfig(dataDir, devMode, bsCfg) - if err != nil { - return nil, fmt.Errorf("failed to persist config for existing cluster: %w", err) - } - - return &RawBootstrapConfig{ - ConfigJSON: cfgJSON, - ManagementToken: bsCfg.ManagementToken, - }, nil -} - -// persistAndProcessConfig is called when we receive data from CCM. -// We validate and persist everything that was received, then also update -// the JSON config as needed. -func persistAndProcessConfig(dataDir string, devMode bool, bsCfg *hcpclient.BootstrapConfig) (string, error) { - if devMode { - // Agent in dev mode, we still need somewhere to persist the certs - // temporarily though to be able to start up at all since we don't support - // inline certs right now. Use temp dir - tmp, err := os.MkdirTemp(os.TempDir(), "consul-dev-") - if err != nil { - return "", fmt.Errorf("failed to create temp dir for certificates: %w", err) - } - dataDir = tmp - } - - // Create subdir if it's not already there. - dir := filepath.Join(dataDir, constants.SubDir) - if err := lib.EnsurePath(dir, true); err != nil { - return "", fmt.Errorf("failed to ensure directory %q: %w", dir, err) - } - - // Parse just to a map for now as we only have to inject to a specific place - // and parsing whole Config struct is complicated... - var cfg map[string]any - - if err := json.Unmarshal([]byte(bsCfg.ConsulConfig), &cfg); err != nil { - return "", fmt.Errorf("failed to unmarshal bootstrap config: %w", err) - } - - // Avoid ever setting an initial_management token from HCP now that we can - // separately bootstrap an HCP management token with a distinct accessor ID. - // - // CCM will continue to return an initial_management token because previous versions of Consul - // cannot bootstrap an HCP management token distinct from the initial management token. - // This block can be deleted once CCM supports tailoring bootstrap config responses - // based on the version of Consul that requested it. - acls, aclsOK := cfg["acl"].(map[string]any) - if aclsOK { - tokens, tokensOK := acls["tokens"].(map[string]interface{}) - if tokensOK { - delete(tokens, "initial_management") - } - } - - var cfgJSON string - if bsCfg.TLSCert != "" { - if err := ValidateTLSCerts(bsCfg.TLSCert, bsCfg.TLSCertKey, bsCfg.TLSCAs); err != nil { - return "", fmt.Errorf("invalid certificates: %w", err) - } - - // Persist the TLS cert files from the response since we need to refer to them - // as disk files either way. - if err := persistTLSCerts(dir, bsCfg.TLSCert, bsCfg.TLSCertKey, bsCfg.TLSCAs); err != nil { - return "", fmt.Errorf("failed to persist TLS certificates to dir %q: %w", dataDir, err) - } - - // Store paths to the persisted TLS cert files. - cfg["ca_file"] = filepath.Join(dir, CAFileName) - cfg["cert_file"] = filepath.Join(dir, CertFileName) - cfg["key_file"] = filepath.Join(dir, KeyFileName) - - // Convert the bootstrap config map back into a string - cfgJSONBytes, err := json.Marshal(cfg) - if err != nil { - return "", err - } - cfgJSON = string(cfgJSONBytes) - } - - if !devMode { - // Persist the final config we need to add so that it is available locally after a restart. - // Assuming the configured data dir wasn't a tmp dir to start with. - if err := persistBootstrapConfig(dir, cfgJSON); err != nil { - return "", fmt.Errorf("failed to persist bootstrap config: %w", err) - } - - // HCP only returns the management token if it requires Consul to - // initialize it - if bsCfg.ManagementToken != "" { - if err := validateManagementToken(bsCfg.ManagementToken); err != nil { - return "", fmt.Errorf("invalid management token: %w", err) - } - if err := persistManagementToken(dir, bsCfg.ManagementToken); err != nil { - return "", fmt.Errorf("failed to persist HCP management token: %w", err) - } - } - - if err := persistSuccessMarker(dir); err != nil { - return "", fmt.Errorf("failed to persist success marker: %w", err) - } - } - return cfgJSON, nil -} - -func persistSuccessMarker(dir string) error { - name := filepath.Join(dir, SuccessFileName) - return os.WriteFile(name, []byte(""), 0600) - -} - -func persistTLSCerts(dir string, serverCert, serverKey string, caCerts []string) error { - if serverCert == "" || serverKey == "" { - return fmt.Errorf("unexpected bootstrap response from HCP: missing TLS information") - } - - // Write out CA cert(s). We write them all to one file because Go's x509 - // machinery will read as many certs as it finds from each PEM file provided - // and add them separaetly to the CertPool for validation - f, err := os.OpenFile(filepath.Join(dir, CAFileName), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600) - if err != nil { - return err - } - bf := bufio.NewWriter(f) - for _, caPEM := range caCerts { - bf.WriteString(caPEM + "\n") - } - if err := bf.Flush(); err != nil { - return err - } - if err := f.Close(); err != nil { - return err - } - - if err := os.WriteFile(filepath.Join(dir, CertFileName), []byte(serverCert), 0600); err != nil { - return err - } - - if err := os.WriteFile(filepath.Join(dir, KeyFileName), []byte(serverKey), 0600); err != nil { - return err - } - - return nil -} - -// Basic validation to ensure a UUID was loaded and assumes the token is non-empty -func validateManagementToken(token string) error { - // note: we assume that the token is not an empty string - if _, err := uuid.ParseUUID(token); err != nil { - return errors.New("management token is not a valid UUID") - } - return nil -} - -func persistManagementToken(dir, token string) error { - name := filepath.Join(dir, TokenFileName) - return os.WriteFile(name, []byte(token), 0600) -} - -func persistBootstrapConfig(dir, cfgJSON string) error { - // Persist the important bits we got from bootstrapping. The TLS certs are - // already persisted, just need to persist the config we are going to add. - name := filepath.Join(dir, ConfigFileName) - return os.WriteFile(name, []byte(cfgJSON), 0600) -} - -func LoadPersistedBootstrapConfig(dataDir string, ui UI) (*RawBootstrapConfig, bool) { - if dataDir == "" { - // There's no files to load when in dev mode. - return nil, false - } - - dir := filepath.Join(dataDir, constants.SubDir) - - _, err := os.Stat(filepath.Join(dir, SuccessFileName)) - if os.IsNotExist(err) { - // Haven't bootstrapped from HCP. - return nil, false - } - if err != nil { - ui.Warn("failed to check for config on disk, re-fetching from HCP: " + err.Error()) - return nil, false - } - - if err := checkCerts(dir); err != nil { - ui.Warn("failed to validate certs on disk, re-fetching from HCP: " + err.Error()) - return nil, false - } - - configJSON, err := loadBootstrapConfigJSON(dataDir) - if err != nil { - ui.Warn("failed to load bootstrap config from disk, re-fetching from HCP: " + err.Error()) - return nil, false - } - - mgmtToken, err := loadManagementToken(dir) - if err != nil { - ui.Warn("failed to load HCP management token from disk, re-fetching from HCP: " + err.Error()) - return nil, false - } - - return &RawBootstrapConfig{ - ConfigJSON: configJSON, - ManagementToken: mgmtToken, - }, true -} - -func loadBootstrapConfigJSON(dataDir string) (string, error) { - filename := filepath.Join(dataDir, constants.SubDir, ConfigFileName) - - _, err := os.Stat(filename) - if os.IsNotExist(err) { - return "", nil - } - if err != nil { - return "", fmt.Errorf("failed to check for bootstrap config: %w", err) - } - - jsonBs, err := os.ReadFile(filename) - if err != nil { - return "", fmt.Errorf("failed to read local bootstrap config file: %s", err) - } - return strings.TrimSpace(string(jsonBs)), nil -} - -func loadManagementToken(dir string) (string, error) { - name := filepath.Join(dir, TokenFileName) - bytes, err := os.ReadFile(name) - if os.IsNotExist(err) { - return "", errors.New("configuration files on disk are incomplete, missing: " + name) - } - if err != nil { - return "", fmt.Errorf("failed to read: %w", err) - } - - token := string(bytes) - if err := validateManagementToken(token); err != nil { - return "", fmt.Errorf("invalid management token: %w", err) - } - - return token, nil -} - -func checkCerts(dir string) error { - files := []string{ - filepath.Join(dir, CAFileName), - filepath.Join(dir, CertFileName), - filepath.Join(dir, KeyFileName), - } - - missing := make([]string, 0) - for _, file := range files { - _, err := os.Stat(file) - if os.IsNotExist(err) { - missing = append(missing, file) - continue - } - if err != nil { - return err - } - } - - // If all the TLS files are missing, assume this is intentional. - // Existing clusters do not receive any TLS certs. - if len(missing) == len(files) { - return nil - } - - // If only some of the files are missing, something went wrong. - if len(missing) > 0 { - return fmt.Errorf("configuration files on disk are incomplete, missing: %v", missing) - } - - cert, key, caCerts, err := LoadCerts(dir) - if err != nil { - return fmt.Errorf("failed to load certs from disk: %w", err) - } - - if err = ValidateTLSCerts(cert, key, caCerts); err != nil { - return fmt.Errorf("invalid certs on disk: %w", err) - } - return nil -} - -func LoadCerts(dir string) (cert, key string, caCerts []string, err error) { - certPEMBlock, err := os.ReadFile(filepath.Join(dir, CertFileName)) - if err != nil { - return "", "", nil, err - } - keyPEMBlock, err := os.ReadFile(filepath.Join(dir, KeyFileName)) - if err != nil { - return "", "", nil, err - } - - caPEMs, err := os.ReadFile(filepath.Join(dir, CAFileName)) - if err != nil { - return "", "", nil, err - } - caCerts, err = splitCACerts(caPEMs) - if err != nil { - return "", "", nil, fmt.Errorf("failed to parse CA certs: %w", err) - } - - return string(certPEMBlock), string(keyPEMBlock), caCerts, nil -} - -// splitCACerts takes a list of concatenated PEM blocks and splits -// them back up into strings. This is used because CACerts are written -// into a single file, but validated individually. -func splitCACerts(caPEMs []byte) ([]string, error) { - var out []string - - for { - nextBlock, remaining := pem.Decode(caPEMs) - if nextBlock == nil { - break - } - if nextBlock.Type != "CERTIFICATE" { - return nil, fmt.Errorf("PEM-block should be CERTIFICATE type") - } - - // Collect up to the start of the remaining bytes. - // We don't grab nextBlock.Bytes because it's not PEM encoded. - out = append(out, string(caPEMs[:len(caPEMs)-len(remaining)])) - caPEMs = remaining - } - - if len(out) == 0 { - return nil, errors.New("invalid CA certificate") - } - return out, nil -} - -// ValidateTLSCerts checks that the CA cert, server cert, and key on disk are structurally valid. -// -// OPTIMIZE: This could be improved by returning an error if certs are expired or close to expiration. -// However, that requires issuing new certs on bootstrap requests, since returning an error -// would trigger a re-fetch from HCP. -func ValidateTLSCerts(cert, key string, caCerts []string) error { - leaf, err := tls.X509KeyPair([]byte(cert), []byte(key)) - if err != nil { - return errors.New("invalid server certificate or key") - } - _, err = x509.ParseCertificate(leaf.Certificate[0]) - if err != nil { - return errors.New("invalid server certificate") - } - - for _, caCert := range caCerts { - _, err = connect.ParseCert(caCert) - if err != nil { - return errors.New("invalid CA certificate") - } - } - return nil -} - -// LoadManagementToken returns the management token, either by loading it from the persisted -// token config file or by fetching it from HCP if the token file does not exist. -func LoadManagementToken(ctx context.Context, logger hclog.Logger, client hcpclient.Client, dataDir string) (string, error) { - hcpCfgDir := filepath.Join(dataDir, constants.SubDir) - token, err := loadManagementToken(hcpCfgDir) - - if err != nil { - logger.Debug("failed to load management token from local disk, fetching configuration from HCP", "error", err) - var err error - cfg, err := fetchBootstrapConfig(ctx, client, dataDir) - if err != nil { - return "", err - } - logger.Debug("configuration fetched from HCP and saved on local disk") - token = cfg.ManagementToken - } else { - logger.Trace("loaded HCP configuration from local disk") - } - - return token, nil -} diff --git a/agent/hcp/bootstrap/bootstrap_test.go b/agent/hcp/bootstrap/bootstrap_test.go deleted file mode 100644 index 3f2f84da87d1..000000000000 --- a/agent/hcp/bootstrap/bootstrap_test.go +++ /dev/null @@ -1,321 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package bootstrap - -import ( - "context" - "errors" - "os" - "path/filepath" - "testing" - "time" - - "github.com/mitchellh/cli" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - - "github.com/hashicorp/go-hclog" - "github.com/hashicorp/go-uuid" - - "github.com/hashicorp/consul/agent/hcp/bootstrap/constants" - hcpclient "github.com/hashicorp/consul/agent/hcp/client" - "github.com/hashicorp/consul/lib" - "github.com/hashicorp/consul/sdk/testutil" - "github.com/hashicorp/consul/tlsutil" -) - -func Test_loadPersistedBootstrapConfig(t *testing.T) { - type expect struct { - loaded bool - warning string - } - type testCase struct { - existingCluster bool - disableManagementToken bool - mutateFn func(t *testing.T, dir string) - expect expect - } - - run := func(t *testing.T, tc testCase) { - dataDir := testutil.TempDir(t, "load-bootstrap-cfg") - - dir := filepath.Join(dataDir, constants.SubDir) - - // Do some common setup as if we received config from HCP and persisted it to disk. - require.NoError(t, lib.EnsurePath(dir, true)) - require.NoError(t, persistSuccessMarker(dir)) - - if !tc.existingCluster { - caCert, caKey, err := tlsutil.GenerateCA(tlsutil.CAOpts{}) - require.NoError(t, err) - - serverCert, serverKey, err := testLeaf(caCert, caKey) - require.NoError(t, err) - require.NoError(t, persistTLSCerts(dir, serverCert, serverKey, []string{caCert})) - - cfgJSON := `{"bootstrap_expect": 8}` - require.NoError(t, persistBootstrapConfig(dir, cfgJSON)) - } - - var token string - if !tc.disableManagementToken { - var err error - token, err = uuid.GenerateUUID() - require.NoError(t, err) - require.NoError(t, persistManagementToken(dir, token)) - } - - // Optionally mutate the persisted data to trigger errors while loading. - if tc.mutateFn != nil { - tc.mutateFn(t, dir) - } - - ui := cli.NewMockUi() - cfg, loaded := LoadPersistedBootstrapConfig(dataDir, ui) - require.Equal(t, tc.expect.loaded, loaded, ui.ErrorWriter.String()) - if loaded { - require.Equal(t, token, cfg.ManagementToken) - require.Empty(t, ui.ErrorWriter.String()) - } else { - require.Nil(t, cfg) - require.Contains(t, ui.ErrorWriter.String(), tc.expect.warning) - } - } - - tt := map[string]testCase{ - "existing cluster with valid files": { - existingCluster: true, - // Don't mutate, files from setup are valid. - mutateFn: nil, - expect: expect{ - loaded: true, - warning: "", - }, - }, - "existing cluster no token": { - existingCluster: true, - disableManagementToken: true, - expect: expect{ - loaded: false, - }, - }, - "existing cluster no files": { - existingCluster: true, - mutateFn: func(t *testing.T, dir string) { - // Remove all files - require.NoError(t, os.RemoveAll(dir)) - }, - expect: expect{ - loaded: false, - // No warnings since we assume we need to fetch config from HCP for the first time. - warning: "", - }, - }, - "new cluster with valid files": { - // Don't mutate, files from setup are valid. - mutateFn: nil, - expect: expect{ - loaded: true, - warning: "", - }, - }, - "new cluster with no token": { - disableManagementToken: true, - expect: expect{ - loaded: false, - }, - }, - "new cluster some files": { - mutateFn: func(t *testing.T, dir string) { - // Remove one of the required files - require.NoError(t, os.Remove(filepath.Join(dir, CertFileName))) - }, - expect: expect{ - loaded: false, - warning: "configuration files on disk are incomplete", - }, - }, - "new cluster no files": { - mutateFn: func(t *testing.T, dir string) { - // Remove all files - require.NoError(t, os.RemoveAll(dir)) - }, - expect: expect{ - loaded: false, - // No warnings since we assume we need to fetch config from HCP for the first time. - warning: "", - }, - }, - "new cluster invalid cert": { - mutateFn: func(t *testing.T, dir string) { - name := filepath.Join(dir, CertFileName) - require.NoError(t, os.WriteFile(name, []byte("not-a-cert"), 0600)) - }, - expect: expect{ - loaded: false, - warning: "invalid server certificate", - }, - }, - "new cluster invalid CA": { - mutateFn: func(t *testing.T, dir string) { - name := filepath.Join(dir, CAFileName) - require.NoError(t, os.WriteFile(name, []byte("not-a-ca-cert"), 0600)) - }, - expect: expect{ - loaded: false, - warning: "invalid CA certificate", - }, - }, - "existing cluster invalid token": { - existingCluster: true, - mutateFn: func(t *testing.T, dir string) { - name := filepath.Join(dir, TokenFileName) - require.NoError(t, os.WriteFile(name, []byte("not-a-uuid"), 0600)) - }, - expect: expect{ - loaded: false, - warning: "is not a valid UUID", - }, - }, - } - - for name, tc := range tt { - t.Run(name, func(t *testing.T) { - run(t, tc) - }) - } -} - -func TestFetchBootstrapConfig(t *testing.T) { - type testCase struct { - expectFetchErr error - expectRetry bool - } - - run := func(t *testing.T, tc testCase) { - ui := cli.NewMockUi() - dataDir := testutil.TempDir(t, "fetch-bootstrap-cfg") - clientM := hcpclient.NewMockClient(t) - - if tc.expectFetchErr != nil && tc.expectRetry { - clientM.On("FetchBootstrap", mock.Anything). - Return(nil, tc.expectFetchErr) - } else if tc.expectFetchErr != nil && !tc.expectRetry { - clientM.On("FetchBootstrap", mock.Anything). - Return(nil, tc.expectFetchErr).Once() - } else { - validToken, err := uuid.GenerateUUID() - require.NoError(t, err) - clientM.EXPECT().FetchBootstrap(mock.Anything).Return(&hcpclient.BootstrapConfig{ - ManagementToken: validToken, - ConsulConfig: "{}", - }, nil).Once() - } - - ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) - defer cancel() - cfg, err := FetchBootstrapConfig(ctx, clientM, dataDir, ui) - - if tc.expectFetchErr == nil { - require.NoError(t, err) - require.NotNil(t, cfg) - return - } - - require.Error(t, err) - require.Nil(t, cfg) - if tc.expectRetry { - require.ErrorIs(t, err, context.DeadlineExceeded) - } else { - require.ErrorIs(t, err, tc.expectFetchErr) - } - } - - tt := map[string]testCase{ - "success": {}, - "unauthorized": { - expectFetchErr: hcpclient.ErrUnauthorized, - }, - "forbidden": { - expectFetchErr: hcpclient.ErrForbidden, - }, - "retryable fetch error": { - expectFetchErr: errors.New("error"), - expectRetry: true, - }, - } - - for name, tc := range tt { - t.Run(name, func(t *testing.T) { - run(t, tc) - }) - } -} - -func TestLoadManagementToken(t *testing.T) { - type testCase struct { - skipHCPConfigDir bool - skipTokenFile bool - tokenFileContent string - skipBootstrap bool - } - - validToken, err := uuid.GenerateUUID() - require.NoError(t, err) - - run := func(t *testing.T, tc testCase) { - dataDir := testutil.TempDir(t, "load-management-token") - - hcpCfgDir := filepath.Join(dataDir, constants.SubDir) - if !tc.skipHCPConfigDir { - err := os.Mkdir(hcpCfgDir, 0755) - require.NoError(t, err) - } - - tokenFilePath := filepath.Join(hcpCfgDir, TokenFileName) - if !tc.skipTokenFile { - err := os.WriteFile(tokenFilePath, []byte(tc.tokenFileContent), 0600) - require.NoError(t, err) - } - - clientM := hcpclient.NewMockClient(t) - if !tc.skipBootstrap { - clientM.EXPECT().FetchBootstrap(mock.Anything).Return(&hcpclient.BootstrapConfig{ - ManagementToken: validToken, - ConsulConfig: "{}", - }, nil).Once() - } - - token, err := LoadManagementToken(context.Background(), hclog.NewNullLogger(), clientM, dataDir) - require.NoError(t, err) - require.Equal(t, validToken, token) - - bytes, err := os.ReadFile(tokenFilePath) - require.NoError(t, err) - require.Equal(t, validToken, string(bytes)) - } - - tt := map[string]testCase{ - "token configured": { - skipBootstrap: true, - tokenFileContent: validToken, - }, - "no token configured": { - skipTokenFile: true, - }, - "invalid token configured": { - tokenFileContent: "invalid", - }, - "no hcp-config directory": { - skipHCPConfigDir: true, - skipTokenFile: true, - }, - } - - for name, tc := range tt { - t.Run(name, func(t *testing.T) { - run(t, tc) - }) - } -} diff --git a/agent/hcp/bootstrap/config-loader/loader.go b/agent/hcp/bootstrap/config-loader/loader.go deleted file mode 100644 index 05e8d1910225..000000000000 --- a/agent/hcp/bootstrap/config-loader/loader.go +++ /dev/null @@ -1,179 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -// Package loader handles loading the bootstrap agent config fetched from HCP into -// the agent's config. It must be a separate package from other HCP components -// because it has a dependency on agent/config while other components need to be -// imported and run within the server process in agent/consul and that would create -// a dependency cycle. -package loader - -import ( - "context" - "fmt" - "path/filepath" - - "github.com/hashicorp/consul/agent/config" - "github.com/hashicorp/consul/agent/hcp/bootstrap" - "github.com/hashicorp/consul/agent/hcp/bootstrap/constants" - hcpclient "github.com/hashicorp/consul/agent/hcp/client" -) - -type ConfigLoader func(source config.Source) (config.LoadResult, error) - -// LoadConfig will attempt to load previously-fetched config from disk and fall back to -// fetch from HCP servers if the local data is incomplete. -// It must be passed a (CLI) UI implementation so it can deliver progress -// updates to the user, for example if it is waiting to retry for a long period. -func LoadConfig(ctx context.Context, client hcpclient.Client, dataDir string, loader ConfigLoader, ui bootstrap.UI) (ConfigLoader, error) { - ui.Output("Loading configuration from HCP") - - // See if we have existing config on disk - // - // OPTIMIZE: We could probably be more intelligent about config loading. - // The currently implemented approach is: - // 1. Attempt to load data from disk - // 2. If that fails or the data is incomplete, block indefinitely fetching remote config. - // - // What if instead we had the following flow: - // 1. Attempt to fetch config from HCP. - // 2. If that fails, fall back to data on disk from last fetch. - // 3. If that fails, go into blocking loop to fetch remote config. - // - // This should allow us to more gracefully transition cases like when - // an existing cluster is linked, but then wants to receive TLS materials - // at a later time. Currently, if we observe the existing-cluster marker we - // don't attempt to fetch any additional configuration from HCP. - - cfg, ok := bootstrap.LoadPersistedBootstrapConfig(dataDir, ui) - if ok { - // Persisted bootstrap config exists, but needs to be validated - err := validatePersistedConfig(dataDir) - if err != nil { - ok = false - } - } - if !ok { - ui.Info("Fetching configuration from HCP servers") - - var err error - cfg, err = bootstrap.FetchBootstrapConfig(ctx, client, dataDir, ui) - if err != nil { - return nil, fmt.Errorf("failed to bootstrap from HCP: %w", err) - } - ui.Info("Configuration fetched from HCP and saved on local disk") - - } else { - ui.Info("Loaded HCP configuration from local disk") - - } - - // Create a new loader func to return - newLoader := bootstrapConfigLoader(loader, cfg) - return newLoader, nil -} - -func AddAclPolicyAccessControlHeader(baseLoader ConfigLoader) ConfigLoader { - return func(source config.Source) (config.LoadResult, error) { - res, err := baseLoader(source) - if err != nil { - return res, err - } - - rc := res.RuntimeConfig - - // HTTP response headers are modified for the HCP UI to work. - if rc.HTTPResponseHeaders == nil { - rc.HTTPResponseHeaders = make(map[string]string) - } - prevValue, ok := rc.HTTPResponseHeaders[accessControlHeaderName] - if !ok { - rc.HTTPResponseHeaders[accessControlHeaderName] = accessControlHeaderValue - } else { - rc.HTTPResponseHeaders[accessControlHeaderName] = prevValue + "," + accessControlHeaderValue - } - - return res, nil - } -} - -// bootstrapConfigLoader is a ConfigLoader for passing bootstrap JSON config received from HCP -// to the config.builder. ConfigLoaders are functions used to build an agent's RuntimeConfig -// from various sources like files and flags. This config is contained in the config.LoadResult. -// -// The flow to include bootstrap config from HCP as a loader's data source is as follows: -// -// 1. A base ConfigLoader function (baseLoader) is created on agent start, and it sets the input -// source argument as the DefaultConfig. -// -// 2. When a server agent can be configured by HCP that baseLoader is wrapped in this bootstrapConfigLoader. -// -// 3. The bootstrapConfigLoader calls that base loader with the bootstrap JSON config as the -// default source. This data will be merged with other valid sources in the config.builder. -// -// 4. The result of the call to baseLoader() below contains the resulting RuntimeConfig, and we do some -// additional modifications to attach data that doesn't get populated during the build in the config pkg. -// -// Note that since the ConfigJSON is stored as the baseLoader's DefaultConfig, its data is the first -// to be merged by the config.builder and could be overwritten by user-provided values in config files or -// CLI flags. However, values set to RuntimeConfig after the baseLoader call are final. -func bootstrapConfigLoader(baseLoader ConfigLoader, cfg *bootstrap.RawBootstrapConfig) ConfigLoader { - return func(source config.Source) (config.LoadResult, error) { - // Don't allow any further attempts to provide a DefaultSource. This should - // only ever be needed later in client agent AutoConfig code but that should - // be mutually exclusive from this bootstrapping mechanism since this is - // only for servers. If we ever try to change that, this clear failure - // should alert future developers that the assumptions are changing rather - // than quietly not applying the config they expect! - if source != nil { - return config.LoadResult{}, - fmt.Errorf("non-nil config source provided to a loader after HCP bootstrap already provided a DefaultSource") - } - - // Otherwise, just call to the loader we were passed with our own additional - // JSON as the source. - // - // OPTIMIZE: We could check/log whether any fields set by the remote config were overwritten by a user-provided flag. - res, err := baseLoader(config.FileSource{ - Name: "HCP Bootstrap", - Format: "json", - Data: cfg.ConfigJSON, - }) - if err != nil { - return res, fmt.Errorf("failed to load HCP Bootstrap config: %w", err) - } - - finalizeRuntimeConfig(res.RuntimeConfig, cfg) - return res, nil - } -} - -const ( - accessControlHeaderName = "Access-Control-Expose-Headers" - accessControlHeaderValue = "x-consul-default-acl-policy" -) - -// finalizeRuntimeConfig will set additional HCP-specific values that are not -// handled by the config.builder. -func finalizeRuntimeConfig(rc *config.RuntimeConfig, cfg *bootstrap.RawBootstrapConfig) { - rc.Cloud.ManagementToken = cfg.ManagementToken -} - -// validatePersistedConfig attempts to load persisted config to check for errors and basic validity. -// Errors here will raise issues like referencing unsupported config fields. -func validatePersistedConfig(dataDir string) error { - filename := filepath.Join(dataDir, constants.SubDir, bootstrap.ConfigFileName) - _, err := config.Load(config.LoadOpts{ - ConfigFiles: []string{filename}, - HCL: []string{ - "server = true", - `bind_addr = "127.0.0.1"`, - fmt.Sprintf("data_dir = %q", dataDir), - }, - ConfigFormat: "json", - }) - if err != nil { - return fmt.Errorf("failed to parse local bootstrap config: %w", err) - } - return nil -} diff --git a/agent/hcp/bootstrap/config-loader/loader_test.go b/agent/hcp/bootstrap/config-loader/loader_test.go deleted file mode 100644 index 8171c6c30f41..000000000000 --- a/agent/hcp/bootstrap/config-loader/loader_test.go +++ /dev/null @@ -1,391 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package loader - -import ( - "context" - "crypto/tls" - "crypto/x509" - "fmt" - "net/http/httptest" - "os" - "path/filepath" - "testing" - - "github.com/mitchellh/cli" - "github.com/stretchr/testify/require" - - "github.com/hashicorp/consul/agent/config" - "github.com/hashicorp/consul/agent/hcp" - "github.com/hashicorp/consul/agent/hcp/bootstrap" - "github.com/hashicorp/consul/agent/hcp/bootstrap/constants" - hcpclient "github.com/hashicorp/consul/agent/hcp/client" - "github.com/hashicorp/consul/lib" -) - -func TestBootstrapConfigLoader(t *testing.T) { - baseLoader := func(source config.Source) (config.LoadResult, error) { - return config.Load(config.LoadOpts{ - DefaultConfig: source, - HCL: []string{ - `server = true`, - `bind_addr = "127.0.0.1"`, - `data_dir = "/tmp/consul-data"`, - }, - }) - } - - bootstrapLoader := func(source config.Source) (config.LoadResult, error) { - return bootstrapConfigLoader(baseLoader, &bootstrap.RawBootstrapConfig{ - ConfigJSON: `{"bootstrap_expect": 8}`, - ManagementToken: "test-token", - })(source) - } - - result, err := bootstrapLoader(nil) - require.NoError(t, err) - - // bootstrap_expect and management token are injected from bootstrap config received from HCP. - require.Equal(t, 8, result.RuntimeConfig.BootstrapExpect) - require.Equal(t, "test-token", result.RuntimeConfig.Cloud.ManagementToken) -} - -func Test_finalizeRuntimeConfig(t *testing.T) { - type testCase struct { - rc *config.RuntimeConfig - cfg *bootstrap.RawBootstrapConfig - verifyFn func(t *testing.T, rc *config.RuntimeConfig) - } - run := func(t *testing.T, tc testCase) { - finalizeRuntimeConfig(tc.rc, tc.cfg) - tc.verifyFn(t, tc.rc) - } - - tt := map[string]testCase{ - "set management token": { - rc: &config.RuntimeConfig{}, - cfg: &bootstrap.RawBootstrapConfig{ - ManagementToken: "test-token", - }, - verifyFn: func(t *testing.T, rc *config.RuntimeConfig) { - require.Equal(t, "test-token", rc.Cloud.ManagementToken) - }, - }, - } - - for name, tc := range tt { - t.Run(name, func(t *testing.T) { - run(t, tc) - }) - } -} - -func Test_AddAclPolicyAccessControlHeader(t *testing.T) { - type testCase struct { - baseLoader ConfigLoader - verifyFn func(t *testing.T, rc *config.RuntimeConfig) - } - run := func(t *testing.T, tc testCase) { - loader := AddAclPolicyAccessControlHeader(tc.baseLoader) - result, err := loader(nil) - require.NoError(t, err) - tc.verifyFn(t, result.RuntimeConfig) - } - - tt := map[string]testCase{ - "append to header if present": { - baseLoader: func(source config.Source) (config.LoadResult, error) { - return config.Load(config.LoadOpts{ - DefaultConfig: config.DefaultSource(), - HCL: []string{ - `server = true`, - `bind_addr = "127.0.0.1"`, - `data_dir = "/tmp/consul-data"`, - fmt.Sprintf(`http_config = { response_headers = { %s = "test" } }`, accessControlHeaderName), - }, - }) - }, - verifyFn: func(t *testing.T, rc *config.RuntimeConfig) { - require.Equal(t, "test,x-consul-default-acl-policy", rc.HTTPResponseHeaders[accessControlHeaderName]) - }, - }, - "set header if not present": { - baseLoader: func(source config.Source) (config.LoadResult, error) { - return config.Load(config.LoadOpts{ - DefaultConfig: config.DefaultSource(), - HCL: []string{ - `server = true`, - `bind_addr = "127.0.0.1"`, - `data_dir = "/tmp/consul-data"`, - }, - }) - }, - verifyFn: func(t *testing.T, rc *config.RuntimeConfig) { - require.Equal(t, "x-consul-default-acl-policy", rc.HTTPResponseHeaders[accessControlHeaderName]) - }, - }, - } - - for name, tc := range tt { - t.Run(name, func(t *testing.T) { - run(t, tc) - }) - } -} - -func boolPtr(value bool) *bool { - return &value -} - -func TestLoadConfig_Persistence(t *testing.T) { - type testCase struct { - // resourceID is the HCP resource ID. If set, a server is considered to be cloud-enabled. - resourceID string - - // devMode indicates whether the loader should not have a data directory. - devMode bool - - // verifyFn issues case-specific assertions. - verifyFn func(t *testing.T, rc *config.RuntimeConfig) - } - - run := func(t *testing.T, tc testCase) { - dir, err := os.MkdirTemp(os.TempDir(), "bootstrap-test-") - require.NoError(t, err) - t.Cleanup(func() { os.RemoveAll(dir) }) - - s := hcp.NewMockHCPServer() - s.AddEndpoint(bootstrap.TestEndpoint()) - - // Use an HTTPS server since that's what the HCP SDK expects for auth. - srv := httptest.NewTLSServer(s) - defer srv.Close() - - caCert, err := x509.ParseCertificate(srv.TLS.Certificates[0].Certificate[0]) - require.NoError(t, err) - - pool := x509.NewCertPool() - pool.AddCert(caCert) - clientTLS := &tls.Config{RootCAs: pool} - - baseOpts := config.LoadOpts{ - HCL: []string{ - `server = true`, - `bind_addr = "127.0.0.1"`, - fmt.Sprintf(`http_config = { response_headers = { %s = "Content-Encoding" } }`, accessControlHeaderName), - fmt.Sprintf(`cloud { client_id="test" client_secret="test" hostname=%q auth_url=%q resource_id=%q }`, - srv.Listener.Addr().String(), srv.URL, tc.resourceID), - }, - } - if tc.devMode { - baseOpts.DevMode = boolPtr(true) - } else { - baseOpts.HCL = append(baseOpts.HCL, fmt.Sprintf(`data_dir = %q`, dir)) - } - - baseLoader := func(source config.Source) (config.LoadResult, error) { - baseOpts.DefaultConfig = source - return config.Load(baseOpts) - } - - ui := cli.NewMockUi() - - // Load initial config to check whether bootstrapping from HCP is enabled. - initial, err := baseLoader(nil) - require.NoError(t, err) - - // Override the client TLS config so that the test server can be trusted. - initial.RuntimeConfig.Cloud.WithTLSConfig(clientTLS) - client, err := hcpclient.NewClient(initial.RuntimeConfig.Cloud) - require.NoError(t, err) - - loader, err := LoadConfig(context.Background(), client, initial.RuntimeConfig.DataDir, baseLoader, ui) - require.NoError(t, err) - - // Load the agent config with the potentially wrapped loader. - fromRemote, err := loader(nil) - require.NoError(t, err) - - // HCP-enabled cases should fetch from HCP on the first run of LoadConfig. - require.Contains(t, ui.OutputWriter.String(), "Fetching configuration from HCP") - - // Run case-specific verification. - tc.verifyFn(t, fromRemote.RuntimeConfig) - - require.Empty(t, fromRemote.RuntimeConfig.ACLInitialManagementToken, - "initial_management token should have been sanitized") - - if tc.devMode { - // Re-running the bootstrap func below isn't relevant to dev mode - // since they don't have a data directory to load data from. - return - } - - // Run LoadConfig again to exercise the logic of loading config from disk. - loader, err = LoadConfig(context.Background(), client, initial.RuntimeConfig.DataDir, baseLoader, ui) - require.NoError(t, err) - - fromDisk, err := loader(nil) - require.NoError(t, err) - - // HCP-enabled cases should fetch from disk on the second run. - require.Contains(t, ui.OutputWriter.String(), "Loaded HCP configuration from local disk") - - // Config loaded from disk should be the same as the one that was initially fetched from the HCP servers. - require.Equal(t, fromRemote.RuntimeConfig, fromDisk.RuntimeConfig) - } - - tt := map[string]testCase{ - "dev mode": { - devMode: true, - - resourceID: "organization/0b9de9a3-8403-4ca6-aba8-fca752f42100/" + - "project/0b9de9a3-8403-4ca6-aba8-fca752f42100/" + - "consul.cluster/new-cluster-id", - - verifyFn: func(t *testing.T, rc *config.RuntimeConfig) { - require.Empty(t, rc.DataDir) - - // Dev mode should have persisted certs since they can't be inlined. - require.NotEmpty(t, rc.TLS.HTTPS.CertFile) - require.NotEmpty(t, rc.TLS.HTTPS.KeyFile) - require.NotEmpty(t, rc.TLS.HTTPS.CAFile) - - // Find the temporary directory they got stored in. - dir := filepath.Dir(rc.TLS.HTTPS.CertFile) - - // Ensure we only stored the TLS materials. - entries, err := os.ReadDir(dir) - require.NoError(t, err) - require.Len(t, entries, 3) - - haveFiles := make([]string, 3) - for i, entry := range entries { - haveFiles[i] = entry.Name() - } - - wantFiles := []string{bootstrap.CAFileName, bootstrap.CertFileName, bootstrap.KeyFileName} - require.ElementsMatch(t, wantFiles, haveFiles) - }, - }, - "new cluster": { - resourceID: "organization/0b9de9a3-8403-4ca6-aba8-fca752f42100/" + - "project/0b9de9a3-8403-4ca6-aba8-fca752f42100/" + - "consul.cluster/new-cluster-id", - - // New clusters should have received and persisted the whole suite of config. - verifyFn: func(t *testing.T, rc *config.RuntimeConfig) { - dir := filepath.Join(rc.DataDir, constants.SubDir) - - entries, err := os.ReadDir(dir) - require.NoError(t, err) - require.Len(t, entries, 6) - - files := []string{ - filepath.Join(dir, bootstrap.ConfigFileName), - filepath.Join(dir, bootstrap.CAFileName), - filepath.Join(dir, bootstrap.CertFileName), - filepath.Join(dir, bootstrap.KeyFileName), - filepath.Join(dir, bootstrap.TokenFileName), - filepath.Join(dir, bootstrap.SuccessFileName), - } - for _, name := range files { - _, err := os.Stat(name) - require.NoError(t, err) - } - - require.Equal(t, filepath.Join(dir, bootstrap.CertFileName), rc.TLS.HTTPS.CertFile) - require.Equal(t, filepath.Join(dir, bootstrap.KeyFileName), rc.TLS.HTTPS.KeyFile) - require.Equal(t, filepath.Join(dir, bootstrap.CAFileName), rc.TLS.HTTPS.CAFile) - - cert, key, caCerts, err := bootstrap.LoadCerts(dir) - require.NoError(t, err) - - require.NoError(t, bootstrap.ValidateTLSCerts(cert, key, caCerts)) - }, - }, - "existing cluster": { - resourceID: "organization/0b9de9a3-8403-4ca6-aba8-fca752f42100/" + - "project/0b9de9a3-8403-4ca6-aba8-fca752f42100/" + - "consul.cluster/" + bootstrap.TestExistingClusterID, - - // Existing clusters should have only received and persisted the management token. - verifyFn: func(t *testing.T, rc *config.RuntimeConfig) { - dir := filepath.Join(rc.DataDir, constants.SubDir) - - entries, err := os.ReadDir(dir) - require.NoError(t, err) - require.Len(t, entries, 3) - - files := []string{ - filepath.Join(dir, bootstrap.TokenFileName), - filepath.Join(dir, bootstrap.SuccessFileName), - filepath.Join(dir, bootstrap.ConfigFileName), - } - for _, name := range files { - _, err := os.Stat(name) - require.NoError(t, err) - } - }, - }, - } - - for name, tc := range tt { - t.Run(name, func(t *testing.T) { - run(t, tc) - }) - } -} - -func TestValidatePersistedConfig(t *testing.T) { - type testCase struct { - configContents string - expectErr string - } - - run := func(t *testing.T, tc testCase) { - dataDir, err := os.MkdirTemp(os.TempDir(), "load-bootstrap-test-") - require.NoError(t, err) - t.Cleanup(func() { os.RemoveAll(dataDir) }) - - dir := filepath.Join(dataDir, constants.SubDir) - require.NoError(t, lib.EnsurePath(dir, true)) - - if tc.configContents != "" { - name := filepath.Join(dir, bootstrap.ConfigFileName) - require.NoError(t, os.WriteFile(name, []byte(tc.configContents), 0600)) - } - - err = validatePersistedConfig(dataDir) - if tc.expectErr != "" { - require.Error(t, err) - require.Contains(t, err.Error(), tc.expectErr) - } else { - require.NoError(t, err) - } - } - - tt := map[string]testCase{ - "valid": { - configContents: `{"bootstrap_expect": 1, "cloud": {"resource_id": "id"}}`, - }, - "invalid config key": { - configContents: `{"not_a_consul_agent_config_field": "zap"}`, - expectErr: "invalid config key not_a_consul_agent_config_field", - }, - "invalid format": { - configContents: `{"not_json" = "invalid"}`, - expectErr: "invalid character '=' after object key", - }, - "missing configuration file": { - expectErr: "no such file or directory", - }, - } - - for name, tc := range tt { - t.Run(name, func(t *testing.T) { - run(t, tc) - }) - } -} diff --git a/agent/hcp/bootstrap/constants/constants.go b/agent/hcp/bootstrap/constants/constants.go deleted file mode 100644 index 1f39bf4712da..000000000000 --- a/agent/hcp/bootstrap/constants/constants.go +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -// Package constants declares some constants for use in the HCP bootstrapping -// process. It is in its own package with no other dependencies in order -// to avoid a dependency cycle. -package constants - -const SubDir = "hcp-config" diff --git a/agent/hcp/bootstrap/testing.go b/agent/hcp/bootstrap/testing.go deleted file mode 100644 index f073d1718344..000000000000 --- a/agent/hcp/bootstrap/testing.go +++ /dev/null @@ -1,184 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package bootstrap - -import ( - "crypto/rand" - "crypto/x509" - "encoding/base64" - "encoding/json" - "fmt" - "net" - "net/http" - "strings" - - "github.com/hashicorp/consul/agent/hcp" - "github.com/hashicorp/consul/tlsutil" - "github.com/hashicorp/go-uuid" - gnmmod "github.com/hashicorp/hcp-sdk-go/clients/cloud-global-network-manager-service/preview/2022-02-15/models" - "github.com/hashicorp/hcp-sdk-go/resource" -) - -// TestEndpoint returns an hcp.TestEndpoint to be used in an hcp.MockHCPServer. -func TestEndpoint() hcp.TestEndpoint { - // Memoize data so it's consistent for the life of the test server - data := make(map[string]gnmmod.HashicorpCloudGlobalNetworkManager20220215AgentBootstrapResponse) - - return hcp.TestEndpoint{ - Methods: []string{"GET"}, - PathSuffix: "agent/bootstrap_config", - Handler: func(r *http.Request, cluster resource.Resource) (interface{}, error) { - return handleBootstrap(data, cluster) - }, - } -} - -func handleBootstrap(data map[string]gnmmod.HashicorpCloudGlobalNetworkManager20220215AgentBootstrapResponse, cluster resource.Resource) (interface{}, error) { - resp, ok := data[cluster.ID] - if !ok { - // Create new response - r, err := generateClusterData(cluster) - if err != nil { - return nil, err - } - data[cluster.ID] = r - resp = r - } - return resp, nil -} - -const TestExistingClusterID = "133114e7-9745-41ce-b1c9-9644a20d2952" - -func testLeaf(caCert, caKey string) (serverCert, serverKey string, err error) { - signer, err := tlsutil.ParseSigner(caKey) - if err != nil { - return "", "", err - } - - serverCert, serverKey, err = tlsutil.GenerateCert(tlsutil.CertOpts{ - Signer: signer, - CA: caCert, - Name: "server.dc1.consul", - Days: 30, - DNSNames: []string{"server.dc1.consul", "localhost"}, - IPAddresses: append([]net.IP{}, net.ParseIP("127.0.0.1")), - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, - }) - if err != nil { - return "", "", err - } - return serverCert, serverKey, nil -} - -func generateClusterData(cluster resource.Resource) (gnmmod.HashicorpCloudGlobalNetworkManager20220215AgentBootstrapResponse, error) { - resp := gnmmod.HashicorpCloudGlobalNetworkManager20220215AgentBootstrapResponse{ - Cluster: &gnmmod.HashicorpCloudGlobalNetworkManager20220215Cluster{}, - Bootstrap: &gnmmod.HashicorpCloudGlobalNetworkManager20220215ClusterBootstrap{ - ServerTLS: &gnmmod.HashicorpCloudGlobalNetworkManager20220215ServerTLS{}, - }, - } - if cluster.ID == TestExistingClusterID { - token, err := uuid.GenerateUUID() - if err != nil { - return resp, err - } - resp.Bootstrap.ConsulConfig = "{}" - resp.Bootstrap.ManagementToken = token - return resp, nil - } - - caCert, caKey, err := tlsutil.GenerateCA(tlsutil.CAOpts{}) - if err != nil { - return resp, err - } - serverCert, serverKey, err := testLeaf(caCert, caKey) - if err != nil { - return resp, err - } - - resp.Bootstrap.ServerTLS.CertificateAuthorities = append(resp.Bootstrap.ServerTLS.CertificateAuthorities, caCert) - resp.Bootstrap.ServerTLS.Cert = serverCert - resp.Bootstrap.ServerTLS.PrivateKey = serverKey - - // Generate Config. We don't use the read config.Config struct because it - // doesn't have `omitempty` which makes the output gross. We only want a tiny - // subset, so we use a map that ends up with the same structure for now. - - // Gossip key - gossipKeyBs := make([]byte, 32) - _, err = rand.Reader.Read(gossipKeyBs) - if err != nil { - return resp, err - } - - retryJoinArgs := map[string]string{ - "provider": "hcp", - "resource_id": cluster.String(), - "client_id": "test_id", - "client_secret": "test_secret", - } - - cfg := map[string]interface{}{ - "encrypt": base64.StdEncoding.EncodeToString(gossipKeyBs), - "encrypt_verify_incoming": true, - "encrypt_verify_outgoing": true, - - // TLS settings (certs will be added by client since we can't put them inline) - "verify_incoming": true, - "verify_outgoing": true, - "verify_server_hostname": true, - "auto_encrypt": map[string]interface{}{ - "allow_tls": true, - }, - - // Enable HTTPS port, disable HTTP - "ports": map[string]interface{}{ - "https": 8501, - "http": -1, - "grpc_tls": 8503, - }, - - // RAFT Peers - "bootstrap_expect": 1, - "retry_join": []string{ - mapArgsString(retryJoinArgs), - }, - } - - // ACLs - token, err := uuid.GenerateUUID() - if err != nil { - return resp, err - } - resp.Bootstrap.ManagementToken = token - - cfg["acl"] = map[string]interface{}{ - "tokens": map[string]interface{}{ - // Also setup the server's own agent token to be the management token so it has - // permission to register itself. - "agent": token, - "initial_management": token, - }, - "default_policy": "deny", - "enabled": true, - "enable_token_persistence": true, - } - - // Encode and return a JSON string in the response - jsonBs, err := json.Marshal(cfg) - if err != nil { - return resp, err - } - resp.Bootstrap.ConsulConfig = string(jsonBs) - - return resp, nil -} - -func mapArgsString(m map[string]string) string { - args := make([]string, len(m)) - for k, v := range m { - args = append(args, fmt.Sprintf("%s=%s", k, v)) - } - return strings.Join(args, " ") -} diff --git a/agent/hcp/client/client.go b/agent/hcp/client/client.go deleted file mode 100644 index 1ba630c33f36..000000000000 --- a/agent/hcp/client/client.go +++ /dev/null @@ -1,392 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package client - -import ( - "context" - "errors" - "fmt" - "net" - "net/http" - "net/url" - "strconv" - "time" - - httptransport "github.com/go-openapi/runtime/client" - "github.com/go-openapi/strfmt" - "golang.org/x/oauth2" - - hcptelemetry "github.com/hashicorp/hcp-sdk-go/clients/cloud-consul-telemetry-gateway/preview/2023-04-14/client/consul_telemetry_service" - hcpgnm "github.com/hashicorp/hcp-sdk-go/clients/cloud-global-network-manager-service/preview/2022-02-15/client/global_network_manager_service" - gnmmod "github.com/hashicorp/hcp-sdk-go/clients/cloud-global-network-manager-service/preview/2022-02-15/models" - "github.com/hashicorp/hcp-sdk-go/httpclient" - "github.com/hashicorp/hcp-sdk-go/resource" - - "github.com/hashicorp/consul/agent/hcp/config" - "github.com/hashicorp/consul/version" -) - -// metricsGatewayPath is the default path for metrics export request on the Telemetry Gateway. -const metricsGatewayPath = "/v1/metrics" - -// Client interface exposes HCP operations that can be invoked by Consul -// -//go:generate mockery --name Client --with-expecter --inpackage -type Client interface { - FetchBootstrap(ctx context.Context) (*BootstrapConfig, error) - FetchTelemetryConfig(ctx context.Context) (*TelemetryConfig, error) - GetObservabilitySecret(ctx context.Context) (clientID, clientSecret string, err error) - PushServerStatus(ctx context.Context, status *ServerStatus) error - DiscoverServers(ctx context.Context) ([]string, error) - GetCluster(ctx context.Context) (*Cluster, error) -} - -type BootstrapConfig struct { - Name string - BootstrapExpect int - GossipKey string - TLSCert string - TLSCertKey string - TLSCAs []string - ConsulConfig string - ManagementToken string -} - -type Cluster struct { - Name string - HCPPortalURL string - AccessLevel *gnmmod.HashicorpCloudGlobalNetworkManager20220215ClusterConsulAccessLevel -} - -type hcpClient struct { - hc *httptransport.Runtime - cfg config.CloudConfig - gnm hcpgnm.ClientService - tgw hcptelemetry.ClientService - resource resource.Resource -} - -func NewClient(cfg config.CloudConfig) (Client, error) { - client := &hcpClient{ - cfg: cfg, - } - - var err error - client.resource, err = resource.FromString(cfg.ResourceID) - if err != nil { - return nil, err - } - - client.hc, err = httpClient(cfg) - if err != nil { - return nil, err - } - - client.gnm = hcpgnm.New(client.hc, nil) - client.tgw = hcptelemetry.New(client.hc, nil) - - return client, nil -} - -func httpClient(c config.CloudConfig) (*httptransport.Runtime, error) { - cfg, err := c.HCPConfig() - if err != nil { - return nil, err - } - - return httpclient.New(httpclient.Config{ - HCPConfig: cfg, - SourceChannel: "consul " + version.GetHumanVersion(), - }) -} - -// FetchTelemetryConfig obtains telemetry configuration from the Telemetry Gateway. -func (c *hcpClient) FetchTelemetryConfig(ctx context.Context) (*TelemetryConfig, error) { - params := hcptelemetry.NewAgentTelemetryConfigParamsWithContext(ctx). - WithLocationOrganizationID(c.resource.Organization). - WithLocationProjectID(c.resource.Project). - WithClusterID(c.resource.ID) - - resp, err := c.tgw.AgentTelemetryConfig(params, nil) - if err != nil { - return nil, fmt.Errorf("failed to fetch from HCP: %w", err) - } - - if err := validateAgentTelemetryConfigPayload(resp); err != nil { - return nil, fmt.Errorf("invalid response payload: %w", err) - } - - return convertAgentTelemetryResponse(ctx, resp, c.cfg) -} - -func (c *hcpClient) FetchBootstrap(ctx context.Context) (*BootstrapConfig, error) { - version := version.GetHumanVersion() - params := hcpgnm.NewAgentBootstrapConfigParamsWithContext(ctx). - WithID(c.resource.ID). - WithLocationOrganizationID(c.resource.Organization). - WithLocationProjectID(c.resource.Project). - WithConsulVersion(&version) - - resp, err := c.gnm.AgentBootstrapConfig(params, nil) - if err != nil { - return nil, decodeError(err) - } - return bootstrapConfigFromHCP(resp.Payload), nil -} - -func bootstrapConfigFromHCP(res *gnmmod.HashicorpCloudGlobalNetworkManager20220215AgentBootstrapResponse) *BootstrapConfig { - var serverTLS gnmmod.HashicorpCloudGlobalNetworkManager20220215ServerTLS - if res.Bootstrap.ServerTLS != nil { - serverTLS = *res.Bootstrap.ServerTLS - } - - return &BootstrapConfig{ - Name: res.Bootstrap.ID, - BootstrapExpect: int(res.Bootstrap.BootstrapExpect), - GossipKey: res.Bootstrap.GossipKey, - TLSCert: serverTLS.Cert, - TLSCertKey: serverTLS.PrivateKey, - TLSCAs: serverTLS.CertificateAuthorities, - ConsulConfig: res.Bootstrap.ConsulConfig, - ManagementToken: res.Bootstrap.ManagementToken, - } -} - -func (c *hcpClient) PushServerStatus(ctx context.Context, s *ServerStatus) error { - params := hcpgnm.NewAgentPushServerStateParamsWithContext(ctx). - WithID(c.resource.ID). - WithLocationOrganizationID(c.resource.Organization). - WithLocationProjectID(c.resource.Project) - - params.SetBody(hcpgnm.AgentPushServerStateBody{ - ServerState: serverStatusToHCP(s), - }) - - _, err := c.gnm.AgentPushServerState(params, nil) - return err -} - -// ServerStatus is used to collect server status information in order to push -// to HCP. Fields should mirror HashicorpCloudGlobalNetworkManager20220215ServerState -type ServerStatus struct { - ID string - Name string - Version string - LanAddress string - GossipPort int - RPCPort int - Datacenter string - - Autopilot ServerAutopilot - Raft ServerRaft - ACL ServerACLInfo - ServerTLSMetadata ServerTLSMetadata - - // TODO: TLS will be deprecated in favor of ServerTLSInfo in GNM. Handle - // removal in a subsequent PR - // https://hashicorp.atlassian.net/browse/CC-7015 - TLS ServerTLSInfo - - ScadaStatus string -} - -type ServerAutopilot struct { - FailureTolerance int - Healthy bool - MinQuorum int - NumServers int - NumVoters int -} - -type ServerRaft struct { - IsLeader bool - KnownLeader bool - AppliedIndex uint64 - TimeSinceLastContact time.Duration -} - -type ServerACLInfo struct { - Enabled bool -} - -// ServerTLSInfo mirrors HashicorpCloudGlobalNetworkManager20220215TLSInfo -type ServerTLSInfo struct { - Enabled bool - CertExpiry time.Time - CertIssuer string - CertName string - CertSerial string - CertificateAuthorities []CertificateMetadata - VerifyIncoming bool - VerifyOutgoing bool - VerifyServerHostname bool -} - -// ServerTLSMetadata mirrors HashicorpCloudGlobalNetworkManager20220215ServerTLSMetadata -type ServerTLSMetadata struct { - InternalRPC ServerTLSInfo -} - -// CertificateMetadata mirrors HashicorpCloudGlobalNetworkManager20220215CertificateMetadata -type CertificateMetadata struct { - CertExpiry time.Time - CertName string - CertSerial string -} - -func serverStatusToHCP(s *ServerStatus) *gnmmod.HashicorpCloudGlobalNetworkManager20220215ServerState { - if s == nil { - return nil - } - - // Convert CA metadata - caCerts := make([]*gnmmod.HashicorpCloudGlobalNetworkManager20220215CertificateMetadata, - len(s.ServerTLSMetadata.InternalRPC.CertificateAuthorities)) - for ix, ca := range s.ServerTLSMetadata.InternalRPC.CertificateAuthorities { - caCerts[ix] = &gnmmod.HashicorpCloudGlobalNetworkManager20220215CertificateMetadata{ - CertExpiry: strfmt.DateTime(ca.CertExpiry), - CertName: ca.CertName, - CertSerial: ca.CertSerial, - } - } - - return &gnmmod.HashicorpCloudGlobalNetworkManager20220215ServerState{ - Autopilot: &gnmmod.HashicorpCloudGlobalNetworkManager20220215AutoPilotInfo{ - FailureTolerance: int32(s.Autopilot.FailureTolerance), - Healthy: s.Autopilot.Healthy, - MinQuorum: int32(s.Autopilot.MinQuorum), - NumServers: int32(s.Autopilot.NumServers), - NumVoters: int32(s.Autopilot.NumVoters), - }, - GossipPort: int32(s.GossipPort), - ID: s.ID, - LanAddress: s.LanAddress, - Name: s.Name, - Raft: &gnmmod.HashicorpCloudGlobalNetworkManager20220215RaftInfo{ - AppliedIndex: strconv.FormatUint(s.Raft.AppliedIndex, 10), - IsLeader: s.Raft.IsLeader, - KnownLeader: s.Raft.KnownLeader, - TimeSinceLastContact: s.Raft.TimeSinceLastContact.String(), - }, - RPCPort: int32(s.RPCPort), - TLS: &gnmmod.HashicorpCloudGlobalNetworkManager20220215TLSInfo{ - // TODO: remove TLS in preference for ServerTLSMetadata.InternalRPC - // when deprecation path is ready - // https://hashicorp.atlassian.net/browse/CC-7015 - CertExpiry: strfmt.DateTime(s.TLS.CertExpiry), - CertName: s.TLS.CertName, - CertSerial: s.TLS.CertSerial, - Enabled: s.TLS.Enabled, - VerifyIncoming: s.TLS.VerifyIncoming, - VerifyOutgoing: s.TLS.VerifyOutgoing, - VerifyServerHostname: s.TLS.VerifyServerHostname, - }, - ServerTLS: &gnmmod.HashicorpCloudGlobalNetworkManager20220215ServerTLSMetadata{ - InternalRPC: &gnmmod.HashicorpCloudGlobalNetworkManager20220215TLSInfo{ - CertExpiry: strfmt.DateTime(s.ServerTLSMetadata.InternalRPC.CertExpiry), - CertIssuer: s.ServerTLSMetadata.InternalRPC.CertIssuer, - CertName: s.ServerTLSMetadata.InternalRPC.CertName, - CertSerial: s.ServerTLSMetadata.InternalRPC.CertSerial, - Enabled: s.ServerTLSMetadata.InternalRPC.Enabled, - VerifyIncoming: s.ServerTLSMetadata.InternalRPC.VerifyIncoming, - VerifyOutgoing: s.ServerTLSMetadata.InternalRPC.VerifyOutgoing, - VerifyServerHostname: s.ServerTLSMetadata.InternalRPC.VerifyServerHostname, - CertificateAuthorities: caCerts, - }, - }, - Version: s.Version, - ScadaStatus: s.ScadaStatus, - ACL: &gnmmod.HashicorpCloudGlobalNetworkManager20220215ACLInfo{ - Enabled: s.ACL.Enabled, - }, - Datacenter: s.Datacenter, - } -} - -func (c *hcpClient) DiscoverServers(ctx context.Context) ([]string, error) { - params := hcpgnm.NewAgentDiscoverParamsWithContext(ctx). - WithID(c.resource.ID). - WithLocationOrganizationID(c.resource.Organization). - WithLocationProjectID(c.resource.Project) - - resp, err := c.gnm.AgentDiscover(params, nil) - if err != nil { - return nil, err - } - var servers []string - for _, srv := range resp.Payload.Servers { - if srv != nil { - servers = append(servers, net.JoinHostPort(srv.LanAddress, strconv.Itoa(int(srv.GossipPort)))) - } - } - - return servers, nil -} - -func (c *hcpClient) GetCluster(ctx context.Context) (*Cluster, error) { - params := hcpgnm.NewGetClusterParamsWithContext(ctx). - WithID(c.resource.ID). - WithLocationOrganizationID(c.resource.Organization). - WithLocationProjectID(c.resource.Project) - - resp, err := c.gnm.GetCluster(params, nil) - if err != nil { - return nil, decodeError(err) - } - - return clusterFromHCP(resp.Payload), nil -} - -func clusterFromHCP(payload *gnmmod.HashicorpCloudGlobalNetworkManager20220215GetClusterResponse) *Cluster { - return &Cluster{ - Name: payload.Cluster.ID, - AccessLevel: payload.Cluster.ConsulAccessLevel, - HCPPortalURL: payload.Cluster.HcpPortalURL, - } -} - -func decodeError(err error) error { - // Determine the code from the type of error - var code int - switch e := err.(type) { - case *url.Error: - oauthErr, ok := errors.Unwrap(e.Err).(*oauth2.RetrieveError) - if ok { - code = oauthErr.Response.StatusCode - } - case *hcpgnm.AgentBootstrapConfigDefault: - code = e.Code() - case *hcpgnm.GetClusterDefault: - code = e.Code() - } - - // Return specific error for codes if relevant - switch code { - case http.StatusUnauthorized: - return ErrUnauthorized - case http.StatusForbidden: - return ErrForbidden - } - - return err -} - -func (c *hcpClient) GetObservabilitySecret(ctx context.Context) (string, string, error) { - params := hcpgnm.NewGetObservabilitySecretParamsWithContext(ctx). - WithID(c.resource.ID). - WithLocationOrganizationID(c.resource.Organization). - WithLocationProjectID(c.resource.Project) - - resp, err := c.gnm.GetObservabilitySecret(params, nil) - if err != nil { - return "", "", err - } - - if len(resp.GetPayload().Keys) == 0 { - return "", "", fmt.Errorf("no observability keys returned for cluster") - } - - key := resp.GetPayload().Keys[len(resp.GetPayload().Keys)-1] - return key.ClientID, key.ClientSecret, nil -} diff --git a/agent/hcp/client/client_test.go b/agent/hcp/client/client_test.go deleted file mode 100644 index c9c5bc63f2dd..000000000000 --- a/agent/hcp/client/client_test.go +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package client - -import ( - "context" - "fmt" - "net/url" - "regexp" - "testing" - "time" - - "github.com/go-openapi/runtime" - hcptelemetry "github.com/hashicorp/hcp-sdk-go/clients/cloud-consul-telemetry-gateway/preview/2023-04-14/client/consul_telemetry_service" - "github.com/hashicorp/hcp-sdk-go/clients/cloud-consul-telemetry-gateway/preview/2023-04-14/models" - "github.com/stretchr/testify/require" -) - -type mockTGW struct { - mockResponse *hcptelemetry.AgentTelemetryConfigOK - mockError error -} - -func (m *mockTGW) AgentTelemetryConfig(params *hcptelemetry.AgentTelemetryConfigParams, authInfo runtime.ClientAuthInfoWriter, opts ...hcptelemetry.ClientOption) (*hcptelemetry.AgentTelemetryConfigOK, error) { - return m.mockResponse, m.mockError -} -func (m *mockTGW) GetLabelValues(params *hcptelemetry.GetLabelValuesParams, authInfo runtime.ClientAuthInfoWriter, opts ...hcptelemetry.ClientOption) (*hcptelemetry.GetLabelValuesOK, error) { - return hcptelemetry.NewGetLabelValuesOK(), nil -} -func (m *mockTGW) QueryRangeBatch(params *hcptelemetry.QueryRangeBatchParams, authInfo runtime.ClientAuthInfoWriter, opts ...hcptelemetry.ClientOption) (*hcptelemetry.QueryRangeBatchOK, error) { - return hcptelemetry.NewQueryRangeBatchOK(), nil -} -func (m *mockTGW) SetTransport(transport runtime.ClientTransport) {} -func (m *mockTGW) GetServiceTopology(params *hcptelemetry.GetServiceTopologyParams, authInfo runtime.ClientAuthInfoWriter, opts ...hcptelemetry.ClientOption) (*hcptelemetry.GetServiceTopologyOK, error) { - return hcptelemetry.NewGetServiceTopologyOK(), nil -} - -type expectedTelemetryCfg struct { - endpoint string - labels map[string]string - filters string - refreshInterval time.Duration -} - -func TestFetchTelemetryConfig(t *testing.T) { - t.Parallel() - for name, tc := range map[string]struct { - mockResponse *hcptelemetry.AgentTelemetryConfigOK - mockError error - wantErr string - expected *expectedTelemetryCfg - }{ - "errorsWithFetchFailure": { - mockError: fmt.Errorf("failed to fetch from HCP"), - mockResponse: nil, - wantErr: "failed to fetch from HCP", - }, - "errorsWithInvalidPayload": { - mockResponse: &hcptelemetry.AgentTelemetryConfigOK{ - Payload: &models.HashicorpCloudConsulTelemetry20230414AgentTelemetryConfigResponse{}, - }, - mockError: nil, - wantErr: "invalid response payload", - }, - "success:": { - mockResponse: &hcptelemetry.AgentTelemetryConfigOK{ - Payload: &models.HashicorpCloudConsulTelemetry20230414AgentTelemetryConfigResponse{ - RefreshConfig: &models.HashicorpCloudConsulTelemetry20230414RefreshConfig{ - RefreshInterval: "1s", - }, - TelemetryConfig: &models.HashicorpCloudConsulTelemetry20230414TelemetryConfig{ - Endpoint: "https://test.com", - Labels: map[string]string{"test": "123"}, - Metrics: &models.HashicorpCloudConsulTelemetry20230414TelemetryMetricsConfig{ - IncludeList: []string{"consul", "test"}, - }, - }, - }, - }, - expected: &expectedTelemetryCfg{ - endpoint: "https://test.com/v1/metrics", - labels: map[string]string{"test": "123"}, - filters: "consul|test", - refreshInterval: 1 * time.Second, - }, - }, - } { - tc := tc - t.Run(name, func(t *testing.T) { - t.Parallel() - c := &hcpClient{ - tgw: &mockTGW{ - mockError: tc.mockError, - mockResponse: tc.mockResponse, - }, - } - - telemetryCfg, err := c.FetchTelemetryConfig(context.Background()) - - if tc.wantErr != "" { - require.Error(t, err) - require.Contains(t, err.Error(), tc.wantErr) - require.Nil(t, telemetryCfg) - return - } - - urlEndpoint, err := url.Parse(tc.expected.endpoint) - require.NoError(t, err) - - regexFilters, err := regexp.Compile(tc.expected.filters) - require.NoError(t, err) - - expectedCfg := &TelemetryConfig{ - MetricsConfig: &MetricsConfig{ - Endpoint: urlEndpoint, - Filters: regexFilters, - Labels: tc.expected.labels, - }, - RefreshConfig: &RefreshConfig{ - RefreshInterval: tc.expected.refreshInterval, - }, - } - - require.NoError(t, err) - require.Equal(t, expectedCfg, telemetryCfg) - }) - } -} diff --git a/agent/hcp/client/errors.go b/agent/hcp/client/errors.go deleted file mode 100644 index 5f0716979247..000000000000 --- a/agent/hcp/client/errors.go +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package client - -import "errors" - -var ( - ErrUnauthorized = errors.New("unauthorized") - ErrForbidden = errors.New("forbidden") -) diff --git a/agent/hcp/client/http_client.go b/agent/hcp/client/http_client.go deleted file mode 100644 index 4854f8c022cd..000000000000 --- a/agent/hcp/client/http_client.go +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package client - -import ( - "crypto/tls" - "net/http" - "time" - - "github.com/hashicorp/go-cleanhttp" - "github.com/hashicorp/go-hclog" - "github.com/hashicorp/go-retryablehttp" - "golang.org/x/oauth2" -) - -const ( - // HTTP Client config - defaultStreamTimeout = 15 * time.Second - - // Retry config - // TODO: Eventually, we'd like to configure these values dynamically. - defaultRetryWaitMin = 1 * time.Second - defaultRetryWaitMax = 15 * time.Second - // defaultRetryMax is set to 0 to turn off retry functionality, until dynamic configuration is possible. - // This is to circumvent any spikes in load that may cause or exacerbate server-side issues for now. - defaultRetryMax = 0 -) - -// NewHTTPClient configures the retryable HTTP client. -func NewHTTPClient(tlsCfg *tls.Config, source oauth2.TokenSource) *retryablehttp.Client { - tlsTransport := cleanhttp.DefaultPooledTransport() - tlsTransport.TLSClientConfig = tlsCfg - - var transport http.RoundTripper = &oauth2.Transport{ - Base: tlsTransport, - Source: source, - } - - client := &http.Client{ - Transport: transport, - Timeout: defaultStreamTimeout, - } - - retryClient := &retryablehttp.Client{ - HTTPClient: client, - // We already log failed requests elsewhere, we pass a null logger here to avoid redundant logs. - Logger: hclog.NewNullLogger(), - RetryWaitMin: defaultRetryWaitMin, - RetryWaitMax: defaultRetryWaitMax, - RetryMax: defaultRetryMax, - CheckRetry: retryablehttp.DefaultRetryPolicy, - Backoff: retryablehttp.DefaultBackoff, - } - - return retryClient -} diff --git a/agent/hcp/client/http_client_test.go b/agent/hcp/client/http_client_test.go deleted file mode 100644 index b8971040892a..000000000000 --- a/agent/hcp/client/http_client_test.go +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package client - -import ( - "net/http" - "net/http/httptest" - "testing" - - "github.com/hashicorp/consul/agent/hcp/config" - "github.com/stretchr/testify/require" -) - -func TestNewHTTPClient(t *testing.T) { - mockCfg := config.MockCloudCfg{} - mockHCPCfg, err := mockCfg.HCPConfig() - require.NoError(t, err) - - client := NewHTTPClient(mockHCPCfg.APITLSConfig(), mockHCPCfg) - require.NotNil(t, client) - - var req *http.Request - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - req = r - })) - _, err = client.Get(srv.URL) - require.NoError(t, err) - require.Equal(t, "Bearer test-token", req.Header.Get("Authorization")) -} diff --git a/agent/hcp/client/metrics_client.go b/agent/hcp/client/metrics_client.go deleted file mode 100644 index 47dd7d52800f..000000000000 --- a/agent/hcp/client/metrics_client.go +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package client - -import ( - "bytes" - "context" - "errors" - "fmt" - "io" - "net/http" - - "github.com/hashicorp/go-retryablehttp" - colmetricpb "go.opentelemetry.io/proto/otlp/collector/metrics/v1" - metricpb "go.opentelemetry.io/proto/otlp/metrics/v1" - "google.golang.org/protobuf/proto" - - "github.com/hashicorp/consul/agent/hcp/telemetry" -) - -const ( - // defaultErrRespBodyLength refers to the max character length of the body on a failure to export metrics. - // anything beyond we will truncate. - defaultErrRespBodyLength = 100 -) - -// MetricsClientProvider provides the retryable HTTP client and headers to use for exporting metrics -// by the metrics client. -type MetricsClientProvider interface { - GetHTTPClient() *retryablehttp.Client - GetHeader() http.Header -} - -// otlpClient is an implementation of MetricsClient with a retryable http client for retries and to honor throttle. -// It also holds default HTTP headers to add to export requests. -type otlpClient struct { - provider MetricsClientProvider -} - -// NewMetricsClient returns a configured MetricsClient. -// The current implementation uses otlpClient to provide retry functionality. -func NewMetricsClient(ctx context.Context, provider MetricsClientProvider) telemetry.MetricsClient { - return &otlpClient{ - provider: provider, - } -} - -// ExportMetrics is the single method exposed by MetricsClient to export OTLP metrics to the desired HCP endpoint. -// The endpoint is configurable as the endpoint can change during periodic refresh of CCM telemetry config. -// By configuring the endpoint here, we can re-use the same client and override the endpoint when making a request. -func (o *otlpClient) ExportMetrics(ctx context.Context, protoMetrics *metricpb.ResourceMetrics, endpoint string) error { - client := o.provider.GetHTTPClient() - if client == nil { - return errors.New("http client not configured") - } - - pbRequest := &colmetricpb.ExportMetricsServiceRequest{ - ResourceMetrics: []*metricpb.ResourceMetrics{protoMetrics}, - } - - body, err := proto.Marshal(pbRequest) - if err != nil { - return fmt.Errorf("failed to marshal the request: %w", err) - } - - req, err := retryablehttp.NewRequest(http.MethodPost, endpoint, bytes.NewBuffer(body)) - if err != nil { - return fmt.Errorf("failed to create request: %w", err) - } - req.Header = o.provider.GetHeader() - - resp, err := client.Do(req.WithContext(ctx)) - if err != nil { - return fmt.Errorf("failed to post metrics: %w", err) - } - defer resp.Body.Close() - - var respData bytes.Buffer - if _, err := io.Copy(&respData, resp.Body); err != nil { - return fmt.Errorf("failed to read body: %w", err) - } - - if resp.StatusCode != http.StatusOK { - truncatedBody := truncate(respData.String(), defaultErrRespBodyLength) - return fmt.Errorf("failed to export metrics: code %d: %s", resp.StatusCode, truncatedBody) - } - - return nil -} - -func truncate(text string, width uint) string { - if len(text) <= int(width) { - return text - } - r := []rune(text) - trunc := r[:width] - return string(trunc) + "..." -} diff --git a/agent/hcp/client/metrics_client_test.go b/agent/hcp/client/metrics_client_test.go deleted file mode 100644 index cea6efca3750..000000000000 --- a/agent/hcp/client/metrics_client_test.go +++ /dev/null @@ -1,155 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package client - -import ( - "context" - "math/rand" - "net/http" - "net/http/httptest" - "testing" - - "github.com/stretchr/testify/require" - colpb "go.opentelemetry.io/proto/otlp/collector/metrics/v1" - metricpb "go.opentelemetry.io/proto/otlp/metrics/v1" - "google.golang.org/protobuf/proto" - - "github.com/hashicorp/go-retryablehttp" -) - -type mockClientProvider struct { - client *retryablehttp.Client - header *http.Header -} - -func (m *mockClientProvider) GetHTTPClient() *retryablehttp.Client { return m.client } -func (m *mockClientProvider) GetHeader() http.Header { return m.header.Clone() } - -func newMockClientProvider() *mockClientProvider { - header := make(http.Header) - header.Set("content-type", "application/x-protobuf") - - client := retryablehttp.NewClient() - - return &mockClientProvider{ - header: &header, - client: client, - } -} - -var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZäöüÄÖÜ世界") - -func randStringRunes(n int) string { - b := make([]rune, n) - for i := range b { - b[i] = letterRunes[rand.Intn(len(letterRunes))] - } - return string(b) -} - -func TestExportMetrics(t *testing.T) { - for name, test := range map[string]struct { - wantErr string - status int - largeBodyError bool - mutateProvider func(*mockClientProvider) - }{ - "success": { - status: http.StatusOK, - }, - "failsWithNonRetryableError": { - status: http.StatusBadRequest, - wantErr: "failed to export metrics: code 400", - }, - "failsWithNonRetryableErrorWithLongError": { - status: http.StatusBadRequest, - wantErr: "failed to export metrics: code 400", - largeBodyError: true, - }, - "failsWithClientNotConfigured": { - mutateProvider: func(m *mockClientProvider) { - m.client = nil - }, - wantErr: "http client not configured", - }, - } { - t.Run(name, func(t *testing.T) { - randomBody := randStringRunes(1000) - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, r.Header.Get("content-type"), "application/x-protobuf") - - body := colpb.ExportMetricsServiceResponse{} - bytes, err := proto.Marshal(&body) - - require.NoError(t, err) - - w.Header().Set("Content-Type", "application/x-protobuf") - w.WriteHeader(test.status) - if test.largeBodyError { - w.Write([]byte(randomBody)) - } else { - w.Write(bytes) - } - - })) - defer srv.Close() - - provider := newMockClientProvider() - if test.mutateProvider != nil { - test.mutateProvider(provider) - } - client := NewMetricsClient(context.Background(), provider) - - ctx := context.Background() - metrics := &metricpb.ResourceMetrics{} - err := client.ExportMetrics(ctx, metrics, srv.URL) - - if test.wantErr != "" { - require.Error(t, err) - require.Contains(t, err.Error(), test.wantErr) - if test.largeBodyError { - truncatedBody := truncate(randomBody, defaultErrRespBodyLength) - require.Contains(t, err.Error(), truncatedBody) - } - return - } - - require.NoError(t, err) - }) - } -} - -func TestTruncate(t *testing.T) { - for name, tc := range map[string]struct { - body string - expectedSize int - }{ - "ZeroSize": { - body: "", - expectedSize: 0, - }, - "LessThanSize": { - body: "foobar", - expectedSize: 6, - }, - "defaultSize": { - body: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis vel tincidunt nunc, sed tristique risu", - expectedSize: 100, - }, - "greaterThanSize": { - body: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis vel tincidunt nunc, sed tristique risus", - expectedSize: 103, - }, - "greaterThanSizeWithUnicode": { - body: randStringRunes(1000), - expectedSize: 103, - }, - } { - t.Run(name, func(t *testing.T) { - truncatedBody := truncate(tc.body, defaultErrRespBodyLength) - truncatedRunes := []rune(truncatedBody) - require.Equal(t, len(truncatedRunes), tc.expectedSize) - }) - } -} diff --git a/agent/hcp/client/mock_Client.go b/agent/hcp/client/mock_Client.go deleted file mode 100644 index 8e5437c22ddf..000000000000 --- a/agent/hcp/client/mock_Client.go +++ /dev/null @@ -1,378 +0,0 @@ -// Code generated by mockery v2.39.2. DO NOT EDIT. - -package client - -import ( - context "context" - - mock "github.com/stretchr/testify/mock" -) - -// MockClient is an autogenerated mock type for the Client type -type MockClient struct { - mock.Mock -} - -type MockClient_Expecter struct { - mock *mock.Mock -} - -func (_m *MockClient) EXPECT() *MockClient_Expecter { - return &MockClient_Expecter{mock: &_m.Mock} -} - -// DiscoverServers provides a mock function with given fields: ctx -func (_m *MockClient) DiscoverServers(ctx context.Context) ([]string, error) { - ret := _m.Called(ctx) - - if len(ret) == 0 { - panic("no return value specified for DiscoverServers") - } - - var r0 []string - var r1 error - if rf, ok := ret.Get(0).(func(context.Context) ([]string, error)); ok { - return rf(ctx) - } - if rf, ok := ret.Get(0).(func(context.Context) []string); ok { - r0 = rf(ctx) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]string) - } - } - - if rf, ok := ret.Get(1).(func(context.Context) error); ok { - r1 = rf(ctx) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// MockClient_DiscoverServers_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DiscoverServers' -type MockClient_DiscoverServers_Call struct { - *mock.Call -} - -// DiscoverServers is a helper method to define mock.On call -// - ctx context.Context -func (_e *MockClient_Expecter) DiscoverServers(ctx interface{}) *MockClient_DiscoverServers_Call { - return &MockClient_DiscoverServers_Call{Call: _e.mock.On("DiscoverServers", ctx)} -} - -func (_c *MockClient_DiscoverServers_Call) Run(run func(ctx context.Context)) *MockClient_DiscoverServers_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context)) - }) - return _c -} - -func (_c *MockClient_DiscoverServers_Call) Return(_a0 []string, _a1 error) *MockClient_DiscoverServers_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *MockClient_DiscoverServers_Call) RunAndReturn(run func(context.Context) ([]string, error)) *MockClient_DiscoverServers_Call { - _c.Call.Return(run) - return _c -} - -// FetchBootstrap provides a mock function with given fields: ctx -func (_m *MockClient) FetchBootstrap(ctx context.Context) (*BootstrapConfig, error) { - ret := _m.Called(ctx) - - if len(ret) == 0 { - panic("no return value specified for FetchBootstrap") - } - - var r0 *BootstrapConfig - var r1 error - if rf, ok := ret.Get(0).(func(context.Context) (*BootstrapConfig, error)); ok { - return rf(ctx) - } - if rf, ok := ret.Get(0).(func(context.Context) *BootstrapConfig); ok { - r0 = rf(ctx) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*BootstrapConfig) - } - } - - if rf, ok := ret.Get(1).(func(context.Context) error); ok { - r1 = rf(ctx) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// MockClient_FetchBootstrap_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FetchBootstrap' -type MockClient_FetchBootstrap_Call struct { - *mock.Call -} - -// FetchBootstrap is a helper method to define mock.On call -// - ctx context.Context -func (_e *MockClient_Expecter) FetchBootstrap(ctx interface{}) *MockClient_FetchBootstrap_Call { - return &MockClient_FetchBootstrap_Call{Call: _e.mock.On("FetchBootstrap", ctx)} -} - -func (_c *MockClient_FetchBootstrap_Call) Run(run func(ctx context.Context)) *MockClient_FetchBootstrap_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context)) - }) - return _c -} - -func (_c *MockClient_FetchBootstrap_Call) Return(_a0 *BootstrapConfig, _a1 error) *MockClient_FetchBootstrap_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *MockClient_FetchBootstrap_Call) RunAndReturn(run func(context.Context) (*BootstrapConfig, error)) *MockClient_FetchBootstrap_Call { - _c.Call.Return(run) - return _c -} - -// FetchTelemetryConfig provides a mock function with given fields: ctx -func (_m *MockClient) FetchTelemetryConfig(ctx context.Context) (*TelemetryConfig, error) { - ret := _m.Called(ctx) - - if len(ret) == 0 { - panic("no return value specified for FetchTelemetryConfig") - } - - var r0 *TelemetryConfig - var r1 error - if rf, ok := ret.Get(0).(func(context.Context) (*TelemetryConfig, error)); ok { - return rf(ctx) - } - if rf, ok := ret.Get(0).(func(context.Context) *TelemetryConfig); ok { - r0 = rf(ctx) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*TelemetryConfig) - } - } - - if rf, ok := ret.Get(1).(func(context.Context) error); ok { - r1 = rf(ctx) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// MockClient_FetchTelemetryConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FetchTelemetryConfig' -type MockClient_FetchTelemetryConfig_Call struct { - *mock.Call -} - -// FetchTelemetryConfig is a helper method to define mock.On call -// - ctx context.Context -func (_e *MockClient_Expecter) FetchTelemetryConfig(ctx interface{}) *MockClient_FetchTelemetryConfig_Call { - return &MockClient_FetchTelemetryConfig_Call{Call: _e.mock.On("FetchTelemetryConfig", ctx)} -} - -func (_c *MockClient_FetchTelemetryConfig_Call) Run(run func(ctx context.Context)) *MockClient_FetchTelemetryConfig_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context)) - }) - return _c -} - -func (_c *MockClient_FetchTelemetryConfig_Call) Return(_a0 *TelemetryConfig, _a1 error) *MockClient_FetchTelemetryConfig_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *MockClient_FetchTelemetryConfig_Call) RunAndReturn(run func(context.Context) (*TelemetryConfig, error)) *MockClient_FetchTelemetryConfig_Call { - _c.Call.Return(run) - return _c -} - -// GetCluster provides a mock function with given fields: ctx -func (_m *MockClient) GetCluster(ctx context.Context) (*Cluster, error) { - ret := _m.Called(ctx) - - if len(ret) == 0 { - panic("no return value specified for GetCluster") - } - - var r0 *Cluster - var r1 error - if rf, ok := ret.Get(0).(func(context.Context) (*Cluster, error)); ok { - return rf(ctx) - } - if rf, ok := ret.Get(0).(func(context.Context) *Cluster); ok { - r0 = rf(ctx) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*Cluster) - } - } - - if rf, ok := ret.Get(1).(func(context.Context) error); ok { - r1 = rf(ctx) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// MockClient_GetCluster_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetCluster' -type MockClient_GetCluster_Call struct { - *mock.Call -} - -// GetCluster is a helper method to define mock.On call -// - ctx context.Context -func (_e *MockClient_Expecter) GetCluster(ctx interface{}) *MockClient_GetCluster_Call { - return &MockClient_GetCluster_Call{Call: _e.mock.On("GetCluster", ctx)} -} - -func (_c *MockClient_GetCluster_Call) Run(run func(ctx context.Context)) *MockClient_GetCluster_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context)) - }) - return _c -} - -func (_c *MockClient_GetCluster_Call) Return(_a0 *Cluster, _a1 error) *MockClient_GetCluster_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *MockClient_GetCluster_Call) RunAndReturn(run func(context.Context) (*Cluster, error)) *MockClient_GetCluster_Call { - _c.Call.Return(run) - return _c -} - -// GetObservabilitySecret provides a mock function with given fields: ctx -func (_m *MockClient) GetObservabilitySecret(ctx context.Context) (string, string, error) { - ret := _m.Called(ctx) - - if len(ret) == 0 { - panic("no return value specified for GetObservabilitySecret") - } - - var r0 string - var r1 string - var r2 error - if rf, ok := ret.Get(0).(func(context.Context) (string, string, error)); ok { - return rf(ctx) - } - if rf, ok := ret.Get(0).(func(context.Context) string); ok { - r0 = rf(ctx) - } else { - r0 = ret.Get(0).(string) - } - - if rf, ok := ret.Get(1).(func(context.Context) string); ok { - r1 = rf(ctx) - } else { - r1 = ret.Get(1).(string) - } - - if rf, ok := ret.Get(2).(func(context.Context) error); ok { - r2 = rf(ctx) - } else { - r2 = ret.Error(2) - } - - return r0, r1, r2 -} - -// MockClient_GetObservabilitySecret_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetObservabilitySecret' -type MockClient_GetObservabilitySecret_Call struct { - *mock.Call -} - -// GetObservabilitySecret is a helper method to define mock.On call -// - ctx context.Context -func (_e *MockClient_Expecter) GetObservabilitySecret(ctx interface{}) *MockClient_GetObservabilitySecret_Call { - return &MockClient_GetObservabilitySecret_Call{Call: _e.mock.On("GetObservabilitySecret", ctx)} -} - -func (_c *MockClient_GetObservabilitySecret_Call) Run(run func(ctx context.Context)) *MockClient_GetObservabilitySecret_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context)) - }) - return _c -} - -func (_c *MockClient_GetObservabilitySecret_Call) Return(clientID string, clientSecret string, err error) *MockClient_GetObservabilitySecret_Call { - _c.Call.Return(clientID, clientSecret, err) - return _c -} - -func (_c *MockClient_GetObservabilitySecret_Call) RunAndReturn(run func(context.Context) (string, string, error)) *MockClient_GetObservabilitySecret_Call { - _c.Call.Return(run) - return _c -} - -// PushServerStatus provides a mock function with given fields: ctx, status -func (_m *MockClient) PushServerStatus(ctx context.Context, status *ServerStatus) error { - ret := _m.Called(ctx, status) - - if len(ret) == 0 { - panic("no return value specified for PushServerStatus") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, *ServerStatus) error); ok { - r0 = rf(ctx, status) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// MockClient_PushServerStatus_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PushServerStatus' -type MockClient_PushServerStatus_Call struct { - *mock.Call -} - -// PushServerStatus is a helper method to define mock.On call -// - ctx context.Context -// - status *ServerStatus -func (_e *MockClient_Expecter) PushServerStatus(ctx interface{}, status interface{}) *MockClient_PushServerStatus_Call { - return &MockClient_PushServerStatus_Call{Call: _e.mock.On("PushServerStatus", ctx, status)} -} - -func (_c *MockClient_PushServerStatus_Call) Run(run func(ctx context.Context, status *ServerStatus)) *MockClient_PushServerStatus_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(*ServerStatus)) - }) - return _c -} - -func (_c *MockClient_PushServerStatus_Call) Return(_a0 error) *MockClient_PushServerStatus_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *MockClient_PushServerStatus_Call) RunAndReturn(run func(context.Context, *ServerStatus) error) *MockClient_PushServerStatus_Call { - _c.Call.Return(run) - return _c -} - -// NewMockClient creates a new instance of MockClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewMockClient(t interface { - mock.TestingT - Cleanup(func()) -}) *MockClient { - mock := &MockClient{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/agent/hcp/client/telemetry_config.go b/agent/hcp/client/telemetry_config.go deleted file mode 100644 index 0745f1b7c619..000000000000 --- a/agent/hcp/client/telemetry_config.go +++ /dev/null @@ -1,176 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package client - -import ( - "context" - "errors" - "fmt" - "net/url" - "regexp" - "strings" - "time" - - "github.com/hashicorp/go-hclog" - hcptelemetry "github.com/hashicorp/hcp-sdk-go/clients/cloud-consul-telemetry-gateway/preview/2023-04-14/client/consul_telemetry_service" - - "github.com/hashicorp/consul/agent/hcp/config" -) - -var ( - // defaultMetricFilters is a regex that matches all metric names. - DefaultMetricFilters = regexp.MustCompile(".+") - - // Validation errors for AgentTelemetryConfigOK response. - errMissingPayload = errors.New("missing payload") - errMissingTelemetryConfig = errors.New("missing telemetry config") - errMissingRefreshConfig = errors.New("missing refresh config") - errMissingMetricsConfig = errors.New("missing metrics config") - errInvalidRefreshInterval = errors.New("invalid refresh interval") - errInvalidEndpoint = errors.New("invalid metrics endpoint") - errEmptyEndpoint = errors.New("empty metrics endpoint") -) - -// TelemetryConfig contains configuration for telemetry data forwarded by Consul servers -// to the HCP Telemetry gateway. -type TelemetryConfig struct { - MetricsConfig *MetricsConfig - RefreshConfig *RefreshConfig -} - -// MetricsConfig holds metrics specific configuration within TelemetryConfig. -type MetricsConfig struct { - Labels map[string]string - Filters *regexp.Regexp - Endpoint *url.URL - Disabled bool -} - -// RefreshConfig contains configuration for the periodic fetch of configuration from HCP. -type RefreshConfig struct { - RefreshInterval time.Duration -} - -// validateAgentTelemetryConfigPayload ensures the returned payload from HCP is valid. -func validateAgentTelemetryConfigPayload(resp *hcptelemetry.AgentTelemetryConfigOK) error { - if resp.Payload == nil { - return errMissingPayload - } - - if resp.Payload.TelemetryConfig == nil { - return errMissingTelemetryConfig - } - - if resp.Payload.RefreshConfig == nil { - return errMissingRefreshConfig - } - - if resp.Payload.TelemetryConfig.Metrics == nil { - return errMissingMetricsConfig - } - - return nil -} - -// convertAgentTelemetryResponse converts an AgentTelemetryConfig payload into a TelemetryConfig object. -func convertAgentTelemetryResponse(ctx context.Context, resp *hcptelemetry.AgentTelemetryConfigOK, cfg config.CloudConfig) (*TelemetryConfig, error) { - refreshInterval, err := time.ParseDuration(resp.Payload.RefreshConfig.RefreshInterval) - if err != nil { - return nil, fmt.Errorf("%w: %w", errInvalidRefreshInterval, err) - } - - telemetryConfig := resp.Payload.TelemetryConfig - metricsEndpoint, err := convertMetricEndpoint(telemetryConfig.Endpoint, telemetryConfig.Metrics.Endpoint) - if err != nil { - return nil, err - } - - metricsFilters := convertMetricFilters(ctx, telemetryConfig.Metrics.IncludeList) - metricLabels := convertMetricLabels(telemetryConfig.Labels, cfg) - - return &TelemetryConfig{ - MetricsConfig: &MetricsConfig{ - Endpoint: metricsEndpoint, - Labels: metricLabels, - Filters: metricsFilters, - Disabled: telemetryConfig.Metrics.Disabled, - }, - RefreshConfig: &RefreshConfig{ - RefreshInterval: refreshInterval, - }, - }, nil -} - -// convertMetricEndpoint returns a url for the export of metrics, if a valid endpoint was obtained. -// It returns no error, and no url, if an empty endpoint is retrieved (server not registered with CCM). -// It returns an error, and no url, if a bad endpoint is retrieved. -func convertMetricEndpoint(telemetryEndpoint string, metricsEndpoint string) (*url.URL, error) { - // Telemetry endpoint overriden by metrics specific endpoint, if given. - endpoint := telemetryEndpoint - if metricsEndpoint != "" { - endpoint = metricsEndpoint - } - - if endpoint == "" { - return nil, errEmptyEndpoint - } - - // Endpoint from CTW has no metrics path, so it must be added. - rawUrl := endpoint + metricsGatewayPath - u, err := url.ParseRequestURI(rawUrl) - if err != nil { - return nil, fmt.Errorf("%w: %w", errInvalidEndpoint, err) - } - - return u, nil -} - -// convertMetricFilters returns a valid regex used to filter metrics. -// if invalid filters are given, a defaults regex that allow all metrics is returned. -func convertMetricFilters(ctx context.Context, payloadFilters []string) *regexp.Regexp { - logger := hclog.FromContext(ctx) - validFilters := make([]string, 0, len(payloadFilters)) - for _, filter := range payloadFilters { - _, err := regexp.Compile(filter) - if err != nil { - logger.Error("invalid filter", "error", err) - continue - } - validFilters = append(validFilters, filter) - } - - if len(validFilters) == 0 { - logger.Error("no valid filters") - return DefaultMetricFilters - } - - // Combine the valid regex strings with OR. - finalRegex := strings.Join(validFilters, "|") - composedRegex, err := regexp.Compile(finalRegex) - if err != nil { - logger.Error("failed to compile final regex", "error", err) - return DefaultMetricFilters - } - - return composedRegex -} - -// convertMetricLabels returns a set of string pairs that must be added as attributes to all exported telemetry data. -func convertMetricLabels(payloadLabels map[string]string, cfg config.CloudConfig) map[string]string { - labels := make(map[string]string) - nodeID := string(cfg.NodeID) - if nodeID != "" { - labels["node_id"] = nodeID - } - - if cfg.NodeName != "" { - labels["node_name"] = cfg.NodeName - } - - for k, v := range payloadLabels { - labels[k] = v - } - - return labels -} diff --git a/agent/hcp/client/telemetry_config_test.go b/agent/hcp/client/telemetry_config_test.go deleted file mode 100644 index d43024400779..000000000000 --- a/agent/hcp/client/telemetry_config_test.go +++ /dev/null @@ -1,343 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package client - -import ( - "context" - "net/url" - "regexp" - "testing" - "time" - - "github.com/hashicorp/hcp-sdk-go/clients/cloud-consul-telemetry-gateway/preview/2023-04-14/client/consul_telemetry_service" - "github.com/hashicorp/hcp-sdk-go/clients/cloud-consul-telemetry-gateway/preview/2023-04-14/models" - "github.com/stretchr/testify/require" - - "github.com/hashicorp/consul/agent/hcp/config" - "github.com/hashicorp/consul/types" -) - -func TestValidateAgentTelemetryConfigPayload(t *testing.T) { - t.Parallel() - for name, tc := range map[string]struct { - resp *consul_telemetry_service.AgentTelemetryConfigOK - wantErr error - }{ - "errorsWithNilPayload": { - resp: &consul_telemetry_service.AgentTelemetryConfigOK{}, - wantErr: errMissingPayload, - }, - "errorsWithNilTelemetryConfig": { - resp: &consul_telemetry_service.AgentTelemetryConfigOK{ - Payload: &models.HashicorpCloudConsulTelemetry20230414AgentTelemetryConfigResponse{ - RefreshConfig: &models.HashicorpCloudConsulTelemetry20230414RefreshConfig{}, - }, - }, - wantErr: errMissingTelemetryConfig, - }, - "errorsWithNilRefreshConfig": { - resp: &consul_telemetry_service.AgentTelemetryConfigOK{ - Payload: &models.HashicorpCloudConsulTelemetry20230414AgentTelemetryConfigResponse{ - TelemetryConfig: &models.HashicorpCloudConsulTelemetry20230414TelemetryConfig{}, - }, - }, - wantErr: errMissingRefreshConfig, - }, - "errorsWithNilMetricsConfig": { - resp: &consul_telemetry_service.AgentTelemetryConfigOK{ - Payload: &models.HashicorpCloudConsulTelemetry20230414AgentTelemetryConfigResponse{ - TelemetryConfig: &models.HashicorpCloudConsulTelemetry20230414TelemetryConfig{}, - RefreshConfig: &models.HashicorpCloudConsulTelemetry20230414RefreshConfig{}, - }, - }, - wantErr: errMissingMetricsConfig, - }, - "success": { - resp: &consul_telemetry_service.AgentTelemetryConfigOK{ - Payload: &models.HashicorpCloudConsulTelemetry20230414AgentTelemetryConfigResponse{ - TelemetryConfig: &models.HashicorpCloudConsulTelemetry20230414TelemetryConfig{ - Metrics: &models.HashicorpCloudConsulTelemetry20230414TelemetryMetricsConfig{}, - }, - RefreshConfig: &models.HashicorpCloudConsulTelemetry20230414RefreshConfig{}, - }, - }, - }, - } { - tc := tc - t.Run(name, func(t *testing.T) { - t.Parallel() - err := validateAgentTelemetryConfigPayload(tc.resp) - if tc.wantErr != nil { - require.ErrorIs(t, err, tc.wantErr) - return - } - require.NoError(t, err) - }) - } -} - -func TestConvertAgentTelemetryResponse(t *testing.T) { - validTestURL, err := url.Parse("https://test.com/v1/metrics") - require.NoError(t, err) - - validTestFilters, err := regexp.Compile("test|consul") - require.NoError(t, err) - - for name, tc := range map[string]struct { - resp *consul_telemetry_service.AgentTelemetryConfigOK - expectedTelemetryCfg *TelemetryConfig - wantErr error - }{ - "success": { - resp: &consul_telemetry_service.AgentTelemetryConfigOK{ - Payload: &models.HashicorpCloudConsulTelemetry20230414AgentTelemetryConfigResponse{ - TelemetryConfig: &models.HashicorpCloudConsulTelemetry20230414TelemetryConfig{ - Endpoint: "https://test.com", - Labels: map[string]string{"test": "test"}, - Metrics: &models.HashicorpCloudConsulTelemetry20230414TelemetryMetricsConfig{ - IncludeList: []string{"test", "consul"}, - }, - }, - RefreshConfig: &models.HashicorpCloudConsulTelemetry20230414RefreshConfig{ - RefreshInterval: "2s", - }, - }, - }, - expectedTelemetryCfg: &TelemetryConfig{ - MetricsConfig: &MetricsConfig{ - Endpoint: validTestURL, - Labels: map[string]string{"test": "test"}, - Filters: validTestFilters, - }, - RefreshConfig: &RefreshConfig{ - RefreshInterval: 2 * time.Second, - }, - }, - }, - "successBadFilters": { - resp: &consul_telemetry_service.AgentTelemetryConfigOK{ - Payload: &models.HashicorpCloudConsulTelemetry20230414AgentTelemetryConfigResponse{ - TelemetryConfig: &models.HashicorpCloudConsulTelemetry20230414TelemetryConfig{ - Endpoint: "https://test.com", - Labels: map[string]string{"test": "test"}, - Metrics: &models.HashicorpCloudConsulTelemetry20230414TelemetryMetricsConfig{ - IncludeList: []string{"[", "(*LF)"}, - }, - }, - RefreshConfig: &models.HashicorpCloudConsulTelemetry20230414RefreshConfig{ - RefreshInterval: "2s", - }, - }, - }, - expectedTelemetryCfg: &TelemetryConfig{ - MetricsConfig: &MetricsConfig{ - Endpoint: validTestURL, - Labels: map[string]string{"test": "test"}, - Filters: DefaultMetricFilters, - }, - RefreshConfig: &RefreshConfig{ - RefreshInterval: 2 * time.Second, - }, - }, - }, - "errorsWithInvalidRefreshInterval": { - resp: &consul_telemetry_service.AgentTelemetryConfigOK{ - Payload: &models.HashicorpCloudConsulTelemetry20230414AgentTelemetryConfigResponse{ - TelemetryConfig: &models.HashicorpCloudConsulTelemetry20230414TelemetryConfig{ - Metrics: &models.HashicorpCloudConsulTelemetry20230414TelemetryMetricsConfig{}, - }, - RefreshConfig: &models.HashicorpCloudConsulTelemetry20230414RefreshConfig{ - RefreshInterval: "300ws", - }, - }, - }, - wantErr: errInvalidRefreshInterval, - }, - "errorsWithInvalidEndpoint": { - resp: &consul_telemetry_service.AgentTelemetryConfigOK{ - Payload: &models.HashicorpCloudConsulTelemetry20230414AgentTelemetryConfigResponse{ - TelemetryConfig: &models.HashicorpCloudConsulTelemetry20230414TelemetryConfig{ - Metrics: &models.HashicorpCloudConsulTelemetry20230414TelemetryMetricsConfig{ - Endpoint: " ", - }, - }, - RefreshConfig: &models.HashicorpCloudConsulTelemetry20230414RefreshConfig{ - RefreshInterval: "1s", - }, - }, - }, - wantErr: errInvalidEndpoint, - }, - } { - t.Run(name, func(t *testing.T) { - telemetryCfg, err := convertAgentTelemetryResponse(context.Background(), tc.resp, config.CloudConfig{}) - if tc.wantErr != nil { - require.ErrorIs(t, err, tc.wantErr) - require.Nil(t, telemetryCfg) - return - } - require.NoError(t, err) - require.Equal(t, tc.expectedTelemetryCfg, telemetryCfg) - }) - } -} - -func TestConvertMetricEndpoint(t *testing.T) { - t.Parallel() - for name, tc := range map[string]struct { - endpoint string - override string - expected string - wantErr error - }{ - "success": { - endpoint: "https://test.com", - expected: "https://test.com/v1/metrics", - }, - "successMetricsOverride": { - endpoint: "https://test.com", - override: "https://override.com", - expected: "https://override.com/v1/metrics", - }, - "errorWithEmptyEndpoints": { - endpoint: "", - override: "", - wantErr: errEmptyEndpoint, - }, - "errorWithInvalidURL": { - endpoint: " ", - override: "", - wantErr: errInvalidEndpoint, - }, - } { - tc := tc - t.Run(name, func(t *testing.T) { - t.Parallel() - u, err := convertMetricEndpoint(tc.endpoint, tc.override) - if tc.wantErr != nil { - require.ErrorIs(t, err, tc.wantErr) - require.Empty(t, u) - return - } - - require.NotNil(t, u) - require.NoError(t, err) - require.Equal(t, tc.expected, u.String()) - }) - } - -} - -func TestConvertMetricFilters(t *testing.T) { - t.Parallel() - for name, tc := range map[string]struct { - filters []string - expectedRegexString string - matches []string - wantErr string - wantMatch bool - }{ - "badFilterRegex": { - filters: []string{"(*LF)"}, - expectedRegexString: DefaultMetricFilters.String(), - matches: []string{"consul.raft.peers", "consul.mem.heap_size"}, - wantMatch: true, - }, - "emptyRegex": { - filters: []string{}, - expectedRegexString: DefaultMetricFilters.String(), - matches: []string{"consul.raft.peers", "consul.mem.heap_size"}, - wantMatch: true, - }, - "matchFound": { - filters: []string{"raft.*", "mem.*"}, - expectedRegexString: "raft.*|mem.*", - matches: []string{"consul.raft.peers", "consul.mem.heap_size"}, - wantMatch: true, - }, - "matchNotFound": { - filters: []string{"mem.*"}, - matches: []string{"consul.raft.peers", "consul.txn.apply"}, - expectedRegexString: "mem.*", - wantMatch: false, - }, - } { - tc := tc - t.Run(name, func(t *testing.T) { - t.Parallel() - f := convertMetricFilters(context.Background(), tc.filters) - - require.Equal(t, tc.expectedRegexString, f.String()) - for _, metric := range tc.matches { - m := f.MatchString(metric) - require.Equal(t, tc.wantMatch, m) - } - }) - } -} - -func TestConvertMetricLabels(t *testing.T) { - t.Parallel() - for name, tc := range map[string]struct { - payloadLabels map[string]string - cfg config.CloudConfig - expectedLabels map[string]string - }{ - "Success": { - payloadLabels: map[string]string{ - "ctw_label": "test", - }, - cfg: config.CloudConfig{ - NodeID: types.NodeID("nodeyid"), - NodeName: "nodey", - }, - expectedLabels: map[string]string{ - "ctw_label": "test", - "node_id": "nodeyid", - "node_name": "nodey", - }, - }, - - "NoNodeID": { - payloadLabels: map[string]string{ - "ctw_label": "test", - }, - cfg: config.CloudConfig{ - NodeID: types.NodeID(""), - NodeName: "nodey", - }, - expectedLabels: map[string]string{ - "ctw_label": "test", - "node_name": "nodey", - }, - }, - "NoNodeName": { - payloadLabels: map[string]string{ - "ctw_label": "test", - }, - cfg: config.CloudConfig{ - NodeID: types.NodeID("nodeyid"), - NodeName: "", - }, - expectedLabels: map[string]string{ - "ctw_label": "test", - "node_id": "nodeyid", - }, - }, - "Empty": { - cfg: config.CloudConfig{ - NodeID: "", - NodeName: "", - }, - expectedLabels: map[string]string{}, - }, - } { - tc := tc - t.Run(name, func(t *testing.T) { - t.Parallel() - labels := convertMetricLabels(tc.payloadLabels, tc.cfg) - require.Equal(t, labels, tc.expectedLabels) - }) - } -} diff --git a/agent/hcp/config/config.go b/agent/hcp/config/config.go deleted file mode 100644 index 420af129adda..000000000000 --- a/agent/hcp/config/config.go +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package config - -import ( - "crypto/tls" - - "github.com/hashicorp/consul/types" - hcpcfg "github.com/hashicorp/hcp-sdk-go/config" - "github.com/hashicorp/hcp-sdk-go/resource" -) - -// CloudConfigurer abstracts the cloud config methods needed to connect to HCP -// in an interface for easier testing. -type CloudConfigurer interface { - HCPConfig(opts ...hcpcfg.HCPConfigOption) (hcpcfg.HCPConfig, error) - Resource() (resource.Resource, error) -} - -// CloudConfig defines configuration for connecting to HCP services -type CloudConfig struct { - ResourceID string - ClientID string - ClientSecret string - Hostname string - AuthURL string - ScadaAddress string - - // Management token used by HCP management plane. - // Cannot be set via config files. - ManagementToken string - - // TlsConfig for testing. - TLSConfig *tls.Config - - NodeID types.NodeID - NodeName string -} - -func (c *CloudConfig) WithTLSConfig(cfg *tls.Config) { - c.TLSConfig = cfg -} - -func (c *CloudConfig) Resource() (resource.Resource, error) { - return resource.FromString(c.ResourceID) -} - -// HCPConfig returns a configuration to use with the HCP SDK. It assumes that the environment -// variables for the HCP configuration have already been loaded and set in the CloudConfig. -func (c *CloudConfig) HCPConfig(opts ...hcpcfg.HCPConfigOption) (hcpcfg.HCPConfig, error) { - if c.TLSConfig == nil { - c.TLSConfig = &tls.Config{} - } - if c.ClientID != "" && c.ClientSecret != "" { - opts = append(opts, hcpcfg.WithClientCredentials(c.ClientID, c.ClientSecret)) - } - if c.AuthURL != "" { - opts = append(opts, hcpcfg.WithAuth(c.AuthURL, c.TLSConfig)) - } - if c.Hostname != "" { - opts = append(opts, hcpcfg.WithAPI(c.Hostname, c.TLSConfig)) - } - if c.ScadaAddress != "" { - opts = append(opts, hcpcfg.WithSCADA(c.ScadaAddress, c.TLSConfig)) - } - opts = append(opts, hcpcfg.WithoutBrowserLogin()) - return hcpcfg.NewHCPConfig(opts...) -} - -// IsConfigured returns whether the cloud configuration has been set either -// in the configuration file or via environment variables. -func (c *CloudConfig) IsConfigured() bool { - return c.ResourceID != "" && c.ClientID != "" && c.ClientSecret != "" -} - -// Merge returns a cloud configuration that is the combined the values of -// two configurations. -func Merge(o CloudConfig, n CloudConfig) CloudConfig { - c := o - if n.ResourceID != "" { - c.ResourceID = n.ResourceID - } - - if n.ClientID != "" { - c.ClientID = n.ClientID - } - - if n.ClientSecret != "" { - c.ClientSecret = n.ClientSecret - } - - if n.Hostname != "" { - c.Hostname = n.Hostname - } - - if n.AuthURL != "" { - c.AuthURL = n.AuthURL - } - - if n.ScadaAddress != "" { - c.ScadaAddress = n.ScadaAddress - } - - if n.ManagementToken != "" { - c.ManagementToken = n.ManagementToken - } - - if n.TLSConfig != nil { - c.TLSConfig = n.TLSConfig - } - - if n.NodeID != "" { - c.NodeID = n.NodeID - } - - if n.NodeName != "" { - c.NodeName = n.NodeName - } - - return c -} diff --git a/agent/hcp/config/config_test.go b/agent/hcp/config/config_test.go deleted file mode 100644 index ca07d4d94eaa..000000000000 --- a/agent/hcp/config/config_test.go +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package config - -import ( - "crypto/tls" - "testing" - - "github.com/stretchr/testify/require" -) - -func TestMerge(t *testing.T) { - oldCfg := CloudConfig{ - ResourceID: "old-resource-id", - ClientID: "old-client-id", - ClientSecret: "old-client-secret", - Hostname: "old-hostname", - AuthURL: "old-auth-url", - ScadaAddress: "old-scada-address", - ManagementToken: "old-token", - TLSConfig: &tls.Config{ - ServerName: "old-server-name", - }, - NodeID: "old-node-id", - NodeName: "old-node-name", - } - - newCfg := CloudConfig{ - ResourceID: "new-resource-id", - ClientID: "new-client-id", - ClientSecret: "new-client-secret", - Hostname: "new-hostname", - AuthURL: "new-auth-url", - ScadaAddress: "new-scada-address", - ManagementToken: "new-token", - TLSConfig: &tls.Config{ - ServerName: "new-server-name", - }, - NodeID: "new-node-id", - NodeName: "new-node-name", - } - - for name, tc := range map[string]struct { - newCfg CloudConfig - expectedCfg CloudConfig - }{ - "Empty": { - newCfg: CloudConfig{}, - expectedCfg: oldCfg, - }, - "All": { - newCfg: newCfg, - expectedCfg: newCfg, - }, - "Partial": { - newCfg: CloudConfig{ - ResourceID: newCfg.ResourceID, - ClientID: newCfg.ClientID, - ClientSecret: newCfg.ClientSecret, - ManagementToken: newCfg.ManagementToken, - }, - expectedCfg: CloudConfig{ - ResourceID: newCfg.ResourceID, - ClientID: newCfg.ClientID, - ClientSecret: newCfg.ClientSecret, - ManagementToken: newCfg.ManagementToken, - Hostname: oldCfg.Hostname, - AuthURL: oldCfg.AuthURL, - ScadaAddress: oldCfg.ScadaAddress, - TLSConfig: oldCfg.TLSConfig, - NodeID: oldCfg.NodeID, - NodeName: oldCfg.NodeName, - }, - }, - } { - t.Run(name, func(t *testing.T) { - merged := Merge(oldCfg, tc.newCfg) - require.Equal(t, tc.expectedCfg, merged) - }) - } -} diff --git a/agent/hcp/config/mock_CloudConfig.go b/agent/hcp/config/mock_CloudConfig.go deleted file mode 100644 index e2c6ba0c53be..000000000000 --- a/agent/hcp/config/mock_CloudConfig.go +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package config - -import ( - "crypto/tls" - "net/url" - - hcpcfg "github.com/hashicorp/hcp-sdk-go/config" - "github.com/hashicorp/hcp-sdk-go/profile" - "github.com/hashicorp/hcp-sdk-go/resource" - "golang.org/x/oauth2" -) - -const testResourceID = "organization/test-org/project/test-project/test-type/test-id" - -type mockHCPCfg struct{} - -func (m *mockHCPCfg) Token() (*oauth2.Token, error) { - return &oauth2.Token{ - AccessToken: "test-token", - }, nil -} - -func (m *mockHCPCfg) APITLSConfig() *tls.Config { return nil } -func (m *mockHCPCfg) SCADAAddress() string { return "" } -func (m *mockHCPCfg) SCADATLSConfig() *tls.Config { return &tls.Config{} } -func (m *mockHCPCfg) APIAddress() string { return "" } -func (m *mockHCPCfg) PortalURL() *url.URL { return &url.URL{} } -func (m *mockHCPCfg) Profile() *profile.UserProfile { return nil } -func (m *mockHCPCfg) Logout() error { return nil } - -type MockCloudCfg struct { - ConfigErr error - ResourceErr error -} - -func (m MockCloudCfg) Resource() (resource.Resource, error) { - r := resource.Resource{ - ID: "test-id", - Type: "test-type", - Organization: "test-org", - Project: "test-project", - } - return r, m.ResourceErr -} - -func (m MockCloudCfg) HCPConfig(opts ...hcpcfg.HCPConfigOption) (hcpcfg.HCPConfig, error) { - return &mockHCPCfg{}, m.ConfigErr -} diff --git a/agent/hcp/deps.go b/agent/hcp/deps.go deleted file mode 100644 index 05e65708b6d1..000000000000 --- a/agent/hcp/deps.go +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package hcp - -import ( - "context" - "fmt" - - "github.com/armon/go-metrics" - "github.com/hashicorp/go-hclog" - "go.opentelemetry.io/otel" - - "github.com/hashicorp/consul/agent/hcp/client" - "github.com/hashicorp/consul/agent/hcp/config" - "github.com/hashicorp/consul/agent/hcp/scada" - "github.com/hashicorp/consul/agent/hcp/telemetry" -) - -// Deps contains the interfaces that the rest of Consul core depends on for HCP integration. -type Deps struct { - Config config.CloudConfig - Provider scada.Provider - Sink metrics.ShutdownSink - TelemetryProvider *hcpProviderImpl - DataDir string -} - -func NewDeps(cfg config.CloudConfig, logger hclog.Logger, dataDir string) (Deps, error) { - ctx := context.Background() - ctx = hclog.WithContext(ctx, logger) - - provider, err := scada.New(logger.Named("scada")) - if err != nil { - return Deps{}, fmt.Errorf("failed to init scada: %w", err) - } - - metricsProvider := NewHCPProvider(ctx) - if err != nil { - logger.Error("failed to init HCP metrics provider", "error", err) - return Deps{}, fmt.Errorf("failed to init HCP metrics provider: %w", err) - } - - metricsClient := client.NewMetricsClient(ctx, metricsProvider) - - sink, err := newSink(ctx, metricsClient, metricsProvider) - if err != nil { - // Do not prevent server start if sink init fails, only log error. - logger.Error("failed to init sink", "error", err) - } - - return Deps{ - Config: cfg, - Provider: provider, - Sink: sink, - TelemetryProvider: metricsProvider, - DataDir: dataDir, - }, nil -} - -// newSink initializes an OTELSink which forwards Consul metrics to HCP. -// This step should not block server initialization, errors are returned, only to be logged. -func newSink( - ctx context.Context, - metricsClient telemetry.MetricsClient, - cfgProvider *hcpProviderImpl, -) (metrics.ShutdownSink, error) { - logger := hclog.FromContext(ctx) - - // Set the global OTEL error handler. Without this, on any failure to publish metrics in - // otelExporter.Export, the default OTEL handler logs to stderr without the formatting or group - // that hclog provides. Here we override that global error handler once so logs are - // in the standard format and include "hcp" in the group name like: - // 2024-02-06T22:35:19.072Z [ERROR] agent.hcp: failed to export metrics: failed to export metrics: code 404: 404 page not found - otel.SetErrorHandler(&otelErrorHandler{logger: logger}) - - reader := telemetry.NewOTELReader(metricsClient, cfgProvider) - sinkOpts := &telemetry.OTELSinkOpts{ - Reader: reader, - ConfigProvider: cfgProvider, - } - - sink, err := telemetry.NewOTELSink(ctx, sinkOpts) - if err != nil { - return nil, fmt.Errorf("failed to create OTELSink: %w", err) - } - - logger.Debug("initialized HCP metrics sink") - - return sink, nil -} - -type otelErrorHandler struct { - logger hclog.Logger -} - -func (o *otelErrorHandler) Handle(err error) { - o.logger.Error(err.Error()) -} diff --git a/agent/hcp/deps_test.go b/agent/hcp/deps_test.go deleted file mode 100644 index 84a34dd697c2..000000000000 --- a/agent/hcp/deps_test.go +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package hcp - -import ( - "context" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/hashicorp/consul/agent/hcp/telemetry" -) - -type mockMetricsClient struct { - telemetry.MetricsClient -} - -func TestSink(t *testing.T) { - t.Parallel() - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - s, err := newSink(ctx, mockMetricsClient{}, &hcpProviderImpl{}) - - require.NotNil(t, s) - require.NoError(t, err) -} diff --git a/agent/hcp/discover/discover.go b/agent/hcp/discover/discover.go deleted file mode 100644 index 981400c38b47..000000000000 --- a/agent/hcp/discover/discover.go +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package discover - -import ( - "context" - "fmt" - "log" - "time" - - hcpclient "github.com/hashicorp/consul/agent/hcp/client" - "github.com/hashicorp/consul/agent/hcp/config" -) - -type Provider struct { -} - -var ( - defaultTimeout = 5 * time.Second -) - -type providerConfig struct { - config.CloudConfig - - timeout time.Duration -} - -func (p *Provider) Addrs(args map[string]string, l *log.Logger) ([]string, error) { - cfg, err := parseArgs(args) - if err != nil { - return nil, err - } - - client, err := hcpclient.NewClient(cfg.CloudConfig) - if err != nil { - return nil, err - } - - ctx, cancel := context.WithTimeout(context.Background(), cfg.timeout) - defer cancel() - servers, err := client.DiscoverServers(ctx) - if err != nil { - return nil, err - } - - return servers, nil -} - -func (p *Provider) Help() string { - return "" -} - -func parseArgs(args map[string]string) (cfg providerConfig, err error) { - cfg.timeout = defaultTimeout - - if id, ok := args["resource_id"]; ok { - cfg.ResourceID = id - } else { - err = fmt.Errorf("'resource_id' was not found and is required") - } - - if cid, ok := args["client_id"]; ok { - cfg.ClientID = cid - } - - if csec, ok := args["client_secret"]; ok { - cfg.ClientSecret = csec - } - - if timeoutRaw, ok := args["timeout"]; ok { - timeout, err := time.ParseDuration(timeoutRaw) - if err != nil { - return cfg, err - } - cfg.timeout = timeout - } - return -} diff --git a/agent/hcp/manager.go b/agent/hcp/manager.go deleted file mode 100644 index 8fb1ac67c852..000000000000 --- a/agent/hcp/manager.go +++ /dev/null @@ -1,364 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package hcp - -import ( - "context" - "reflect" - "sync" - "time" - - "github.com/hashicorp/go-hclog" - - hcpclient "github.com/hashicorp/consul/agent/hcp/client" - "github.com/hashicorp/consul/agent/hcp/config" - "github.com/hashicorp/consul/agent/hcp/scada" - "github.com/hashicorp/consul/lib" -) - -var ( - defaultManagerMinInterval = 45 * time.Minute - defaultManagerMaxInterval = 75 * time.Minute -) - -var _ Manager = (*HCPManager)(nil) - -type ManagerConfig struct { - Client hcpclient.Client - CloudConfig config.CloudConfig - SCADAProvider scada.Provider - TelemetryProvider TelemetryProvider - - StatusFn StatusCallback - // Idempotent function to upsert the HCP management token. This will be called periodically in - // the manager's main loop. - ManagementTokenUpserterFn ManagementTokenUpserter - ManagementTokenDeleterFn ManagementTokenDeleter - MinInterval time.Duration - MaxInterval time.Duration - - Logger hclog.Logger -} - -func (cfg *ManagerConfig) enabled() bool { - return cfg.Client != nil && cfg.StatusFn != nil -} - -func (cfg *ManagerConfig) nextHeartbeat() time.Duration { - min := cfg.MinInterval - if min == 0 { - min = defaultManagerMinInterval - } - - max := cfg.MaxInterval - if max == 0 { - max = defaultManagerMaxInterval - } - if max < min { - max = min - } - return min + lib.RandomStagger(max-min) -} - -type StatusCallback func(context.Context) (hcpclient.ServerStatus, error) -type ManagementTokenUpserter func(name, secretId string) error -type ManagementTokenDeleter func(secretId string) error - -//go:generate mockery --name Manager --with-expecter --inpackage -type Manager interface { - Start(context.Context) error - Stop() error - GetCloudConfig() config.CloudConfig - UpdateConfig(hcpclient.Client, config.CloudConfig) -} - -type HCPManager struct { - logger hclog.Logger - - running bool - runLock sync.RWMutex - - cfg ManagerConfig - cfgMu sync.RWMutex - - updateCh chan struct{} - stopCh chan struct{} - - // testUpdateSent is set by unit tests to signal when the manager's status update has triggered - testUpdateSent chan struct{} -} - -// NewManager returns a Manager initialized with the given configuration. -func NewManager(cfg ManagerConfig) *HCPManager { - return &HCPManager{ - logger: cfg.Logger, - cfg: cfg, - - updateCh: make(chan struct{}, 1), - } -} - -// Start executes the logic for connecting to HCP and sending periodic server updates. If the -// manager has been previously started, it will not start again. -func (m *HCPManager) Start(ctx context.Context) error { - // Check if the manager has already started - changed := m.setRunning(true) - if !changed { - m.logger.Trace("HCP manager already started") - return nil - } - - var err error - m.logger.Info("HCP manager starting") - - // Update and start the SCADA provider - err = m.startSCADAProvider() - if err != nil { - m.logger.Error("failed to start scada provider", "error", err) - m.setRunning(false) - return err - } - - // Update and start the telemetry provider to enable the HCP metrics sink - if err := m.startTelemetryProvider(ctx); err != nil { - m.logger.Error("failed to update telemetry config provider", "error", err) - m.setRunning(false) - return err - } - - // immediately send initial update - select { - case <-ctx.Done(): - m.setRunning(false) - return nil - case <-m.stopCh: - return nil - case <-m.updateCh: // empty the update chan if there is a queued update to prevent repeated update in main loop - err = m.sendUpdate() - if err != nil { - m.setRunning(false) - return err - } - default: - err = m.sendUpdate() - if err != nil { - m.setRunning(false) - return err - } - } - - // main loop - go func() { - for { - m.cfgMu.RLock() - cfg := m.cfg - m.cfgMu.RUnlock() - - // Check for configured management token from HCP and upsert it if found - if hcpManagement := cfg.CloudConfig.ManagementToken; len(hcpManagement) > 0 { - if cfg.ManagementTokenUpserterFn != nil { - upsertTokenErr := cfg.ManagementTokenUpserterFn("HCP Management Token", hcpManagement) - if upsertTokenErr != nil { - m.logger.Error("failed to upsert HCP management token", "err", upsertTokenErr) - } - } - } - - nextUpdate := cfg.nextHeartbeat() - if err != nil { - m.logger.Error("failed to send server status to HCP", "err", err, "next_heartbeat", nextUpdate.String()) - } - - select { - case <-ctx.Done(): - m.setRunning(false) - return - - case <-m.stopCh: - return - - case <-m.updateCh: - err = m.sendUpdate() - - case <-time.After(nextUpdate): - err = m.sendUpdate() - } - } - }() - - return err -} - -func (m *HCPManager) startSCADAProvider() error { - provider := m.cfg.SCADAProvider - if provider == nil { - return nil - } - - // Update the SCADA provider configuration with HCP configurations - m.logger.Debug("updating scada provider with HCP configuration") - err := provider.UpdateHCPConfig(m.cfg.CloudConfig) - if err != nil { - m.logger.Error("failed to update scada provider with HCP configuration", "err", err) - return err - } - - // Update the SCADA provider metadata - provider.UpdateMeta(map[string]string{ - "consul_server_id": string(m.cfg.CloudConfig.NodeID), - }) - - // Start the SCADA provider - err = provider.Start() - if err != nil { - return err - } - return nil -} - -func (m *HCPManager) startTelemetryProvider(ctx context.Context) error { - if m.cfg.TelemetryProvider == nil || reflect.ValueOf(m.cfg.TelemetryProvider).IsNil() { - return nil - } - - m.cfg.TelemetryProvider.Start(ctx, &HCPProviderCfg{ - HCPClient: m.cfg.Client, - HCPConfig: &m.cfg.CloudConfig, - }) - - return nil -} - -func (m *HCPManager) GetCloudConfig() config.CloudConfig { - m.cfgMu.RLock() - defer m.cfgMu.RUnlock() - - return m.cfg.CloudConfig -} - -func (m *HCPManager) UpdateConfig(client hcpclient.Client, cloudCfg config.CloudConfig) { - m.cfgMu.Lock() - // Save original values - originalCfg := m.cfg.CloudConfig - originalClient := m.cfg.Client - - // Update with new values - m.cfg.Client = client - m.cfg.CloudConfig = cloudCfg - m.cfgMu.Unlock() - - // Send update if already running and values were updated - if m.isRunning() && (originalClient != client || originalCfg != cloudCfg) { - m.SendUpdate() - } -} - -func (m *HCPManager) SendUpdate() { - m.logger.Debug("HCP triggering status update") - select { - case m.updateCh <- struct{}{}: - // trigger update - default: - // if chan is full then there is already an update triggered that will soon - // be acted on so don't bother blocking. - } -} - -// TODO: we should have retried on failures here with backoff but take into -// account that if a new update is triggered while we are still retrying we -// should not start another retry loop. Something like have a "dirty" flag which -// we mark on first PushUpdate and then a retry timer as well as the interval -// and a "isRetrying" state or something so that we attempt to send update, but -// then fetch fresh info on each attempt to send so if we are already in a retry -// backoff a new push is a no-op. -func (m *HCPManager) sendUpdate() error { - m.cfgMu.RLock() - cfg := m.cfg - m.cfgMu.RUnlock() - - if !cfg.enabled() { - return nil - } - - if m.testUpdateSent != nil { - defer func() { - select { - case m.testUpdateSent <- struct{}{}: - default: - } - }() - } - - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - - s, err := cfg.StatusFn(ctx) - if err != nil { - return err - } - - return cfg.Client.PushServerStatus(ctx, &s) -} - -func (m *HCPManager) isRunning() bool { - m.runLock.RLock() - defer m.runLock.RUnlock() - return m.running -} - -// setRunning sets the running status of the manager to the given value. If the -// given value is the same as the current running status, it returns false. If -// current status is updated to the given status, it returns true. -func (m *HCPManager) setRunning(r bool) bool { - m.runLock.Lock() - defer m.runLock.Unlock() - - if m.running == r { - return false - } - - // Initialize or close the stop channel depending what running status - // we're transitioning to. Channel must be initialized on start since - // a provider can be stopped and started multiple times. - if r { - m.stopCh = make(chan struct{}) - } else { - close(m.stopCh) - } - - m.running = r - return true -} - -// Stop stops the manager's main loop that sends updates -// and stops the SCADA provider and telemetry provider. -func (m *HCPManager) Stop() error { - changed := m.setRunning(false) - if !changed { - m.logger.Trace("HCP manager already stopped") - return nil - } - m.logger.Info("HCP manager stopping") - - m.cfgMu.RLock() - defer m.cfgMu.RUnlock() - - if m.cfg.SCADAProvider != nil { - m.cfg.SCADAProvider.Stop() - } - - if m.cfg.TelemetryProvider != nil && !reflect.ValueOf(m.cfg.TelemetryProvider).IsNil() { - m.cfg.TelemetryProvider.Stop() - } - - if m.cfg.ManagementTokenDeleterFn != nil && m.cfg.CloudConfig.ManagementToken != "" { - err := m.cfg.ManagementTokenDeleterFn(m.cfg.CloudConfig.ManagementToken) - if err != nil { - return err - } - } - - m.logger.Info("HCP manager stopped") - return nil -} diff --git a/agent/hcp/manager_test.go b/agent/hcp/manager_test.go deleted file mode 100644 index 75e1e3d2849d..000000000000 --- a/agent/hcp/manager_test.go +++ /dev/null @@ -1,380 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package hcp - -import ( - "fmt" - "io" - "testing" - "time" - - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - "golang.org/x/net/context" - - "github.com/hashicorp/go-hclog" - - hcpclient "github.com/hashicorp/consul/agent/hcp/client" - "github.com/hashicorp/consul/agent/hcp/config" - "github.com/hashicorp/consul/agent/hcp/scada" - "github.com/hashicorp/consul/sdk/testutil" -) - -func TestManager_Start(t *testing.T) { - client := hcpclient.NewMockClient(t) - statusF := func(ctx context.Context) (hcpclient.ServerStatus, error) { - return hcpclient.ServerStatus{ID: t.Name()}, nil - } - upsertManagementTokenCalled := make(chan struct{}, 1) - upsertManagementTokenF := func(name, secretID string) error { - upsertManagementTokenCalled <- struct{}{} - return nil - } - updateCh := make(chan struct{}, 1) - client.EXPECT().PushServerStatus(mock.Anything, &hcpclient.ServerStatus{ID: t.Name()}).Return(nil).Once() - - cloudCfg := config.CloudConfig{ - ResourceID: "resource-id", - NodeID: "node-1", - ManagementToken: "fake-token", - } - scadaM := scada.NewMockProvider(t) - scadaM.EXPECT().UpdateHCPConfig(cloudCfg).Return(nil).Once() - scadaM.EXPECT().UpdateMeta( - map[string]string{ - "consul_server_id": string(cloudCfg.NodeID), - }, - ).Return().Once() - scadaM.EXPECT().Start().Return(nil) - - telemetryM := NewMockTelemetryProvider(t) - telemetryM.EXPECT().Start( - mock.Anything, &HCPProviderCfg{ - HCPClient: client, - HCPConfig: &cloudCfg, - }, - ).Return(nil).Once() - - mgr := NewManager( - ManagerConfig{ - Logger: hclog.New(&hclog.LoggerOptions{Output: io.Discard}), - StatusFn: statusF, - ManagementTokenUpserterFn: upsertManagementTokenF, - SCADAProvider: scadaM, - TelemetryProvider: telemetryM, - }, - ) - mgr.testUpdateSent = updateCh - ctx, cancel := context.WithCancel(context.Background()) - t.Cleanup(cancel) - - mgr.UpdateConfig(client, cloudCfg) - mgr.Start(ctx) - select { - case <-updateCh: - case <-time.After(time.Second): - require.Fail(t, "manager did not send update in expected time") - } - - select { - case <-upsertManagementTokenCalled: - case <-time.After(time.Second): - require.Fail(t, "manager did not upsert management token in expected time") - } - - // Make sure after manager has stopped no more statuses are pushed. - cancel() - client.AssertExpectations(t) -} - -func TestManager_StartMultipleTimes(t *testing.T) { - client := hcpclient.NewMockClient(t) - statusF := func(ctx context.Context) (hcpclient.ServerStatus, error) { - return hcpclient.ServerStatus{ID: t.Name()}, nil - } - - updateCh := make(chan struct{}, 1) - client.EXPECT().PushServerStatus(mock.Anything, &hcpclient.ServerStatus{ID: t.Name()}).Return(nil).Once() - - cloudCfg := config.CloudConfig{ - ResourceID: "organization/85702e73-8a3d-47dc-291c-379b783c5804/project/8c0547c0-10e8-1ea2-dffe-384bee8da634/hashicorp.consul.global-network-manager.cluster/test", - NodeID: "node-1", - ManagementToken: "fake-token", - } - - mgr := NewManager( - ManagerConfig{ - Logger: hclog.New(&hclog.LoggerOptions{Output: io.Discard}), - StatusFn: statusF, - }, - ) - - mgr.testUpdateSent = updateCh - ctx, cancel := context.WithCancel(context.Background()) - t.Cleanup(cancel) - - // Start the manager twice concurrently, expect only one update - mgr.UpdateConfig(client, cloudCfg) - go mgr.Start(ctx) - go mgr.Start(ctx) - select { - case <-updateCh: - case <-time.After(time.Second): - require.Fail(t, "manager did not send update in expected time") - } - - select { - case <-updateCh: - require.Fail(t, "manager sent an update when not expected") - case <-time.After(time.Second): - } - - // Try start the manager again, still don't expect an update since already running - mgr.Start(ctx) - select { - case <-updateCh: - require.Fail(t, "manager sent an update when not expected") - case <-time.After(time.Second): - } -} - -func TestManager_UpdateConfig(t *testing.T) { - client := hcpclient.NewMockClient(t) - statusF := func(ctx context.Context) (hcpclient.ServerStatus, error) { - return hcpclient.ServerStatus{ID: t.Name()}, nil - } - - updateCh := make(chan struct{}, 1) - - cloudCfg := config.CloudConfig{ - ResourceID: "organization/85702e73-8a3d-47dc-291c-379b783c5804/project/8c0547c0-10e8-1ea2-dffe-384bee8da634/hashicorp.consul.global-network-manager.cluster/test", - NodeID: "node-1", - } - - mgr := NewManager( - ManagerConfig{ - Logger: hclog.New(&hclog.LoggerOptions{Output: io.Discard}), - StatusFn: statusF, - CloudConfig: cloudCfg, - Client: client, - }, - ) - - mgr.testUpdateSent = updateCh - ctx, cancel := context.WithCancel(context.Background()) - t.Cleanup(cancel) - - // Start the manager, expect an initial status update - client.EXPECT().PushServerStatus(mock.Anything, &hcpclient.ServerStatus{ID: t.Name()}).Return(nil).Once() - mgr.Start(ctx) - select { - case <-updateCh: - case <-time.After(time.Second): - require.Fail(t, "manager did not send update in expected time") - } - - // Update the cloud configuration, expect a status update - client.EXPECT().PushServerStatus(mock.Anything, &hcpclient.ServerStatus{ID: t.Name()}).Return(nil).Once() - updatedCfg := cloudCfg - updatedCfg.ManagementToken = "token" - mgr.UpdateConfig(client, updatedCfg) - select { - case <-updateCh: - case <-time.After(time.Second): - require.Fail(t, "manager did not send update in expected time") - } - - // Update the client, expect a status update - updatedClient := hcpclient.NewMockClient(t) - updatedClient.EXPECT().PushServerStatus(mock.Anything, &hcpclient.ServerStatus{ID: t.Name()}).Return(nil).Once() - mgr.UpdateConfig(updatedClient, updatedCfg) - select { - case <-updateCh: - case <-time.After(time.Second): - require.Fail(t, "manager did not send update in expected time") - } - - // Update with the same values, don't expect a status update - mgr.UpdateConfig(updatedClient, updatedCfg) - select { - case <-updateCh: - require.Fail(t, "manager sent an update when not expected") - case <-time.After(time.Second): - } -} - -func TestManager_SendUpdate(t *testing.T) { - client := hcpclient.NewMockClient(t) - statusF := func(ctx context.Context) (hcpclient.ServerStatus, error) { - return hcpclient.ServerStatus{ID: t.Name()}, nil - } - updateCh := make(chan struct{}, 1) - - // Expect two calls, once during run startup and again when SendUpdate is called - client.EXPECT().PushServerStatus(mock.Anything, &hcpclient.ServerStatus{ID: t.Name()}).Return(nil).Twice() - mgr := NewManager( - ManagerConfig{ - Client: client, - Logger: hclog.New(&hclog.LoggerOptions{Output: io.Discard}), - StatusFn: statusF, - }, - ) - mgr.testUpdateSent = updateCh - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - mgr.Start(ctx) - select { - case <-updateCh: - case <-time.After(time.Second): - require.Fail(t, "manager did not send update in expected time") - } - mgr.SendUpdate() - select { - case <-updateCh: - case <-time.After(time.Second): - require.Fail(t, "manager did not send update in expected time") - } - client.AssertExpectations(t) -} - -func TestManager_SendUpdate_Periodic(t *testing.T) { - client := hcpclient.NewMockClient(t) - statusF := func(ctx context.Context) (hcpclient.ServerStatus, error) { - return hcpclient.ServerStatus{ID: t.Name()}, nil - } - updateCh := make(chan struct{}, 1) - - // Expect two calls, once during run startup and again when SendUpdate is called - client.EXPECT().PushServerStatus(mock.Anything, &hcpclient.ServerStatus{ID: t.Name()}).Return(nil).Twice() - mgr := NewManager( - ManagerConfig{ - Client: client, - Logger: hclog.New(&hclog.LoggerOptions{Output: io.Discard}), - StatusFn: statusF, - MaxInterval: time.Second, - MinInterval: 100 * time.Millisecond, - }, - ) - mgr.testUpdateSent = updateCh - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - mgr.Start(ctx) - select { - case <-updateCh: - case <-time.After(time.Second): - require.Fail(t, "manager did not send update in expected time") - } - select { - case <-updateCh: - case <-time.After(time.Second): - require.Fail(t, "manager did not send update in expected time") - } - client.AssertExpectations(t) -} - -func TestManager_Stop(t *testing.T) { - client := hcpclient.NewMockClient(t) - - // Configure status functions called in sendUpdate - statusF := func(ctx context.Context) (hcpclient.ServerStatus, error) { - return hcpclient.ServerStatus{ID: t.Name()}, nil - } - updateCh := make(chan struct{}, 1) - client.EXPECT().PushServerStatus(mock.Anything, &hcpclient.ServerStatus{ID: t.Name()}).Return(nil).Twice() - - // Configure management token creation and cleanup - token := "test-token" - upsertManagementTokenCalled := make(chan struct{}, 1) - upsertManagementTokenF := func(name, secretID string) error { - upsertManagementTokenCalled <- struct{}{} - if secretID != token { - return fmt.Errorf("expected token %q, got %q", token, secretID) - } - return nil - } - deleteManagementTokenCalled := make(chan struct{}, 1) - deleteManagementTokenF := func(secretID string) error { - deleteManagementTokenCalled <- struct{}{} - if secretID != token { - return fmt.Errorf("expected token %q, got %q", token, secretID) - } - return nil - } - - // Configure the SCADA provider - scadaM := scada.NewMockProvider(t) - scadaM.EXPECT().UpdateHCPConfig(mock.Anything).Return(nil).Once() - scadaM.EXPECT().UpdateMeta(mock.Anything).Return().Once() - scadaM.EXPECT().Start().Return(nil).Once() - scadaM.EXPECT().Stop().Return(nil).Once() - - // Configure the telemetry provider - telemetryM := NewMockTelemetryProvider(t) - telemetryM.EXPECT().Start(mock.Anything, mock.Anything).Return(nil).Once() - telemetryM.EXPECT().Stop().Return().Once() - - // Configure manager with all its dependencies - mgr := NewManager( - ManagerConfig{ - Logger: testutil.Logger(t), - StatusFn: statusF, - Client: client, - ManagementTokenUpserterFn: upsertManagementTokenF, - ManagementTokenDeleterFn: deleteManagementTokenF, - SCADAProvider: scadaM, - TelemetryProvider: telemetryM, - CloudConfig: config.CloudConfig{ - ManagementToken: token, - }, - }, - ) - mgr.testUpdateSent = updateCh - ctx, cancel := context.WithCancel(context.Background()) - t.Cleanup(cancel) - - // Start the manager - err := mgr.Start(ctx) - require.NoError(t, err) - select { - case <-updateCh: - case <-time.After(time.Second): - require.Fail(t, "manager did not send update in expected time") - } - select { - case <-upsertManagementTokenCalled: - case <-time.After(time.Second): - require.Fail(t, "manager did not create token in expected time") - } - - // Send an update to ensure the manager is running in its main loop - mgr.SendUpdate() - select { - case <-updateCh: - case <-time.After(time.Second): - require.Fail(t, "manager did not send update in expected time") - } - - // Stop the manager - err = mgr.Stop() - require.NoError(t, err) - - // Validate that the management token delete function is called - select { - case <-deleteManagementTokenCalled: - case <-time.After(time.Millisecond * 100): - require.Fail(t, "manager did not create token in expected time") - } - - // Send an update, expect no update since manager is stopped - mgr.SendUpdate() - select { - case <-updateCh: - require.Fail(t, "manager sent update after stopped") - case <-time.After(time.Second): - } -} diff --git a/agent/hcp/mock_Manager.go b/agent/hcp/mock_Manager.go deleted file mode 100644 index 422d9034d88d..000000000000 --- a/agent/hcp/mock_Manager.go +++ /dev/null @@ -1,209 +0,0 @@ -// Code generated by mockery v2.38.0. DO NOT EDIT. - -package hcp - -import ( - client "github.com/hashicorp/consul/agent/hcp/client" - config "github.com/hashicorp/consul/agent/hcp/config" - - context "context" - - mock "github.com/stretchr/testify/mock" -) - -// MockManager is an autogenerated mock type for the Manager type -type MockManager struct { - mock.Mock -} - -type MockManager_Expecter struct { - mock *mock.Mock -} - -func (_m *MockManager) EXPECT() *MockManager_Expecter { - return &MockManager_Expecter{mock: &_m.Mock} -} - -// GetCloudConfig provides a mock function with given fields: -func (_m *MockManager) GetCloudConfig() config.CloudConfig { - ret := _m.Called() - - if len(ret) == 0 { - panic("no return value specified for GetCloudConfig") - } - - var r0 config.CloudConfig - if rf, ok := ret.Get(0).(func() config.CloudConfig); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(config.CloudConfig) - } - - return r0 -} - -// MockManager_GetCloudConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetCloudConfig' -type MockManager_GetCloudConfig_Call struct { - *mock.Call -} - -// GetCloudConfig is a helper method to define mock.On call -func (_e *MockManager_Expecter) GetCloudConfig() *MockManager_GetCloudConfig_Call { - return &MockManager_GetCloudConfig_Call{Call: _e.mock.On("GetCloudConfig")} -} - -func (_c *MockManager_GetCloudConfig_Call) Run(run func()) *MockManager_GetCloudConfig_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *MockManager_GetCloudConfig_Call) Return(_a0 config.CloudConfig) *MockManager_GetCloudConfig_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *MockManager_GetCloudConfig_Call) RunAndReturn(run func() config.CloudConfig) *MockManager_GetCloudConfig_Call { - _c.Call.Return(run) - return _c -} - -// Start provides a mock function with given fields: _a0 -func (_m *MockManager) Start(_a0 context.Context) error { - ret := _m.Called(_a0) - - if len(ret) == 0 { - panic("no return value specified for Start") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context) error); ok { - r0 = rf(_a0) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// MockManager_Start_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Start' -type MockManager_Start_Call struct { - *mock.Call -} - -// Start is a helper method to define mock.On call -// - _a0 context.Context -func (_e *MockManager_Expecter) Start(_a0 interface{}) *MockManager_Start_Call { - return &MockManager_Start_Call{Call: _e.mock.On("Start", _a0)} -} - -func (_c *MockManager_Start_Call) Run(run func(_a0 context.Context)) *MockManager_Start_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context)) - }) - return _c -} - -func (_c *MockManager_Start_Call) Return(_a0 error) *MockManager_Start_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *MockManager_Start_Call) RunAndReturn(run func(context.Context) error) *MockManager_Start_Call { - _c.Call.Return(run) - return _c -} - -// Stop provides a mock function with given fields: -func (_m *MockManager) Stop() error { - ret := _m.Called() - - if len(ret) == 0 { - panic("no return value specified for Stop") - } - - var r0 error - if rf, ok := ret.Get(0).(func() error); ok { - r0 = rf() - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// MockManager_Stop_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Stop' -type MockManager_Stop_Call struct { - *mock.Call -} - -// Stop is a helper method to define mock.On call -func (_e *MockManager_Expecter) Stop() *MockManager_Stop_Call { - return &MockManager_Stop_Call{Call: _e.mock.On("Stop")} -} - -func (_c *MockManager_Stop_Call) Run(run func()) *MockManager_Stop_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *MockManager_Stop_Call) Return(_a0 error) *MockManager_Stop_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *MockManager_Stop_Call) RunAndReturn(run func() error) *MockManager_Stop_Call { - _c.Call.Return(run) - return _c -} - -// UpdateConfig provides a mock function with given fields: _a0, _a1 -func (_m *MockManager) UpdateConfig(_a0 client.Client, _a1 config.CloudConfig) { - _m.Called(_a0, _a1) -} - -// MockManager_UpdateConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateConfig' -type MockManager_UpdateConfig_Call struct { - *mock.Call -} - -// UpdateConfig is a helper method to define mock.On call -// - _a0 client.Client -// - _a1 config.CloudConfig -func (_e *MockManager_Expecter) UpdateConfig(_a0 interface{}, _a1 interface{}) *MockManager_UpdateConfig_Call { - return &MockManager_UpdateConfig_Call{Call: _e.mock.On("UpdateConfig", _a0, _a1)} -} - -func (_c *MockManager_UpdateConfig_Call) Run(run func(_a0 client.Client, _a1 config.CloudConfig)) *MockManager_UpdateConfig_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(client.Client), args[1].(config.CloudConfig)) - }) - return _c -} - -func (_c *MockManager_UpdateConfig_Call) Return() *MockManager_UpdateConfig_Call { - _c.Call.Return() - return _c -} - -func (_c *MockManager_UpdateConfig_Call) RunAndReturn(run func(client.Client, config.CloudConfig)) *MockManager_UpdateConfig_Call { - _c.Call.Return(run) - return _c -} - -// NewMockManager creates a new instance of MockManager. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewMockManager(t interface { - mock.TestingT - Cleanup(func()) -}) *MockManager { - mock := &MockManager{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/agent/hcp/mock_TelemetryProvider.go b/agent/hcp/mock_TelemetryProvider.go deleted file mode 100644 index f654575f5bae..000000000000 --- a/agent/hcp/mock_TelemetryProvider.go +++ /dev/null @@ -1,115 +0,0 @@ -// Code generated by mockery v2.38.0. DO NOT EDIT. - -package hcp - -import ( - context "context" - - mock "github.com/stretchr/testify/mock" -) - -// MockTelemetryProvider is an autogenerated mock type for the TelemetryProvider type -type MockTelemetryProvider struct { - mock.Mock -} - -type MockTelemetryProvider_Expecter struct { - mock *mock.Mock -} - -func (_m *MockTelemetryProvider) EXPECT() *MockTelemetryProvider_Expecter { - return &MockTelemetryProvider_Expecter{mock: &_m.Mock} -} - -// Start provides a mock function with given fields: ctx, c -func (_m *MockTelemetryProvider) Start(ctx context.Context, c *HCPProviderCfg) error { - ret := _m.Called(ctx, c) - - if len(ret) == 0 { - panic("no return value specified for Start") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, *HCPProviderCfg) error); ok { - r0 = rf(ctx, c) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// MockTelemetryProvider_Start_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Start' -type MockTelemetryProvider_Start_Call struct { - *mock.Call -} - -// Start is a helper method to define mock.On call -// - ctx context.Context -// - c *HCPProviderCfg -func (_e *MockTelemetryProvider_Expecter) Start(ctx interface{}, c interface{}) *MockTelemetryProvider_Start_Call { - return &MockTelemetryProvider_Start_Call{Call: _e.mock.On("Start", ctx, c)} -} - -func (_c *MockTelemetryProvider_Start_Call) Run(run func(ctx context.Context, c *HCPProviderCfg)) *MockTelemetryProvider_Start_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(*HCPProviderCfg)) - }) - return _c -} - -func (_c *MockTelemetryProvider_Start_Call) Return(_a0 error) *MockTelemetryProvider_Start_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *MockTelemetryProvider_Start_Call) RunAndReturn(run func(context.Context, *HCPProviderCfg) error) *MockTelemetryProvider_Start_Call { - _c.Call.Return(run) - return _c -} - -// Stop provides a mock function with given fields: -func (_m *MockTelemetryProvider) Stop() { - _m.Called() -} - -// MockTelemetryProvider_Stop_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Stop' -type MockTelemetryProvider_Stop_Call struct { - *mock.Call -} - -// Stop is a helper method to define mock.On call -func (_e *MockTelemetryProvider_Expecter) Stop() *MockTelemetryProvider_Stop_Call { - return &MockTelemetryProvider_Stop_Call{Call: _e.mock.On("Stop")} -} - -func (_c *MockTelemetryProvider_Stop_Call) Run(run func()) *MockTelemetryProvider_Stop_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *MockTelemetryProvider_Stop_Call) Return() *MockTelemetryProvider_Stop_Call { - _c.Call.Return() - return _c -} - -func (_c *MockTelemetryProvider_Stop_Call) RunAndReturn(run func()) *MockTelemetryProvider_Stop_Call { - _c.Call.Return(run) - return _c -} - -// NewMockTelemetryProvider creates a new instance of MockTelemetryProvider. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewMockTelemetryProvider(t interface { - mock.TestingT - Cleanup(func()) -}) *MockTelemetryProvider { - mock := &MockTelemetryProvider{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/agent/hcp/scada/capabilities.go b/agent/hcp/scada/capabilities.go deleted file mode 100644 index bbb6ea6266dc..000000000000 --- a/agent/hcp/scada/capabilities.go +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package scada - -import "github.com/hashicorp/hcp-scada-provider/capability" - -// CAPCoreAPI is the capability used to securely expose the Consul HTTP API to HCP -var CAPCoreAPI = capability.NewAddr("core_api") diff --git a/agent/hcp/scada/mock_Provider.go b/agent/hcp/scada/mock_Provider.go deleted file mode 100644 index 7e922cb21bc4..000000000000 --- a/agent/hcp/scada/mock_Provider.go +++ /dev/null @@ -1,553 +0,0 @@ -// Code generated by mockery v2.38.0. DO NOT EDIT. - -package scada - -import ( - config "github.com/hashicorp/consul/agent/hcp/config" - mock "github.com/stretchr/testify/mock" - - net "net" - - provider "github.com/hashicorp/hcp-scada-provider" - - time "time" -) - -// MockProvider is an autogenerated mock type for the Provider type -type MockProvider struct { - mock.Mock -} - -type MockProvider_Expecter struct { - mock *mock.Mock -} - -func (_m *MockProvider) EXPECT() *MockProvider_Expecter { - return &MockProvider_Expecter{mock: &_m.Mock} -} - -// AddMeta provides a mock function with given fields: _a0 -func (_m *MockProvider) AddMeta(_a0 ...provider.Meta) { - _va := make([]interface{}, len(_a0)) - for _i := range _a0 { - _va[_i] = _a0[_i] - } - var _ca []interface{} - _ca = append(_ca, _va...) - _m.Called(_ca...) -} - -// MockProvider_AddMeta_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AddMeta' -type MockProvider_AddMeta_Call struct { - *mock.Call -} - -// AddMeta is a helper method to define mock.On call -// - _a0 ...provider.Meta -func (_e *MockProvider_Expecter) AddMeta(_a0 ...interface{}) *MockProvider_AddMeta_Call { - return &MockProvider_AddMeta_Call{Call: _e.mock.On("AddMeta", - append([]interface{}{}, _a0...)...)} -} - -func (_c *MockProvider_AddMeta_Call) Run(run func(_a0 ...provider.Meta)) *MockProvider_AddMeta_Call { - _c.Call.Run(func(args mock.Arguments) { - variadicArgs := make([]provider.Meta, len(args)-0) - for i, a := range args[0:] { - if a != nil { - variadicArgs[i] = a.(provider.Meta) - } - } - run(variadicArgs...) - }) - return _c -} - -func (_c *MockProvider_AddMeta_Call) Return() *MockProvider_AddMeta_Call { - _c.Call.Return() - return _c -} - -func (_c *MockProvider_AddMeta_Call) RunAndReturn(run func(...provider.Meta)) *MockProvider_AddMeta_Call { - _c.Call.Return(run) - return _c -} - -// DeleteMeta provides a mock function with given fields: _a0 -func (_m *MockProvider) DeleteMeta(_a0 ...string) { - _va := make([]interface{}, len(_a0)) - for _i := range _a0 { - _va[_i] = _a0[_i] - } - var _ca []interface{} - _ca = append(_ca, _va...) - _m.Called(_ca...) -} - -// MockProvider_DeleteMeta_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteMeta' -type MockProvider_DeleteMeta_Call struct { - *mock.Call -} - -// DeleteMeta is a helper method to define mock.On call -// - _a0 ...string -func (_e *MockProvider_Expecter) DeleteMeta(_a0 ...interface{}) *MockProvider_DeleteMeta_Call { - return &MockProvider_DeleteMeta_Call{Call: _e.mock.On("DeleteMeta", - append([]interface{}{}, _a0...)...)} -} - -func (_c *MockProvider_DeleteMeta_Call) Run(run func(_a0 ...string)) *MockProvider_DeleteMeta_Call { - _c.Call.Run(func(args mock.Arguments) { - variadicArgs := make([]string, len(args)-0) - for i, a := range args[0:] { - if a != nil { - variadicArgs[i] = a.(string) - } - } - run(variadicArgs...) - }) - return _c -} - -func (_c *MockProvider_DeleteMeta_Call) Return() *MockProvider_DeleteMeta_Call { - _c.Call.Return() - return _c -} - -func (_c *MockProvider_DeleteMeta_Call) RunAndReturn(run func(...string)) *MockProvider_DeleteMeta_Call { - _c.Call.Return(run) - return _c -} - -// GetMeta provides a mock function with given fields: -func (_m *MockProvider) GetMeta() map[string]string { - ret := _m.Called() - - if len(ret) == 0 { - panic("no return value specified for GetMeta") - } - - var r0 map[string]string - if rf, ok := ret.Get(0).(func() map[string]string); ok { - r0 = rf() - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(map[string]string) - } - } - - return r0 -} - -// MockProvider_GetMeta_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetMeta' -type MockProvider_GetMeta_Call struct { - *mock.Call -} - -// GetMeta is a helper method to define mock.On call -func (_e *MockProvider_Expecter) GetMeta() *MockProvider_GetMeta_Call { - return &MockProvider_GetMeta_Call{Call: _e.mock.On("GetMeta")} -} - -func (_c *MockProvider_GetMeta_Call) Run(run func()) *MockProvider_GetMeta_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *MockProvider_GetMeta_Call) Return(_a0 map[string]string) *MockProvider_GetMeta_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *MockProvider_GetMeta_Call) RunAndReturn(run func() map[string]string) *MockProvider_GetMeta_Call { - _c.Call.Return(run) - return _c -} - -// LastError provides a mock function with given fields: -func (_m *MockProvider) LastError() (time.Time, error) { - ret := _m.Called() - - if len(ret) == 0 { - panic("no return value specified for LastError") - } - - var r0 time.Time - var r1 error - if rf, ok := ret.Get(0).(func() (time.Time, error)); ok { - return rf() - } - if rf, ok := ret.Get(0).(func() time.Time); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(time.Time) - } - - if rf, ok := ret.Get(1).(func() error); ok { - r1 = rf() - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// MockProvider_LastError_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'LastError' -type MockProvider_LastError_Call struct { - *mock.Call -} - -// LastError is a helper method to define mock.On call -func (_e *MockProvider_Expecter) LastError() *MockProvider_LastError_Call { - return &MockProvider_LastError_Call{Call: _e.mock.On("LastError")} -} - -func (_c *MockProvider_LastError_Call) Run(run func()) *MockProvider_LastError_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *MockProvider_LastError_Call) Return(_a0 time.Time, _a1 error) *MockProvider_LastError_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *MockProvider_LastError_Call) RunAndReturn(run func() (time.Time, error)) *MockProvider_LastError_Call { - _c.Call.Return(run) - return _c -} - -// Listen provides a mock function with given fields: capability -func (_m *MockProvider) Listen(capability string) (net.Listener, error) { - ret := _m.Called(capability) - - if len(ret) == 0 { - panic("no return value specified for Listen") - } - - var r0 net.Listener - var r1 error - if rf, ok := ret.Get(0).(func(string) (net.Listener, error)); ok { - return rf(capability) - } - if rf, ok := ret.Get(0).(func(string) net.Listener); ok { - r0 = rf(capability) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(net.Listener) - } - } - - if rf, ok := ret.Get(1).(func(string) error); ok { - r1 = rf(capability) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// MockProvider_Listen_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Listen' -type MockProvider_Listen_Call struct { - *mock.Call -} - -// Listen is a helper method to define mock.On call -// - capability string -func (_e *MockProvider_Expecter) Listen(capability interface{}) *MockProvider_Listen_Call { - return &MockProvider_Listen_Call{Call: _e.mock.On("Listen", capability)} -} - -func (_c *MockProvider_Listen_Call) Run(run func(capability string)) *MockProvider_Listen_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) - }) - return _c -} - -func (_c *MockProvider_Listen_Call) Return(_a0 net.Listener, _a1 error) *MockProvider_Listen_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *MockProvider_Listen_Call) RunAndReturn(run func(string) (net.Listener, error)) *MockProvider_Listen_Call { - _c.Call.Return(run) - return _c -} - -// SessionStatus provides a mock function with given fields: -func (_m *MockProvider) SessionStatus() string { - ret := _m.Called() - - if len(ret) == 0 { - panic("no return value specified for SessionStatus") - } - - var r0 string - if rf, ok := ret.Get(0).(func() string); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(string) - } - - return r0 -} - -// MockProvider_SessionStatus_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SessionStatus' -type MockProvider_SessionStatus_Call struct { - *mock.Call -} - -// SessionStatus is a helper method to define mock.On call -func (_e *MockProvider_Expecter) SessionStatus() *MockProvider_SessionStatus_Call { - return &MockProvider_SessionStatus_Call{Call: _e.mock.On("SessionStatus")} -} - -func (_c *MockProvider_SessionStatus_Call) Run(run func()) *MockProvider_SessionStatus_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *MockProvider_SessionStatus_Call) Return(_a0 string) *MockProvider_SessionStatus_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *MockProvider_SessionStatus_Call) RunAndReturn(run func() string) *MockProvider_SessionStatus_Call { - _c.Call.Return(run) - return _c -} - -// Start provides a mock function with given fields: -func (_m *MockProvider) Start() error { - ret := _m.Called() - - if len(ret) == 0 { - panic("no return value specified for Start") - } - - var r0 error - if rf, ok := ret.Get(0).(func() error); ok { - r0 = rf() - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// MockProvider_Start_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Start' -type MockProvider_Start_Call struct { - *mock.Call -} - -// Start is a helper method to define mock.On call -func (_e *MockProvider_Expecter) Start() *MockProvider_Start_Call { - return &MockProvider_Start_Call{Call: _e.mock.On("Start")} -} - -func (_c *MockProvider_Start_Call) Run(run func()) *MockProvider_Start_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *MockProvider_Start_Call) Return(_a0 error) *MockProvider_Start_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *MockProvider_Start_Call) RunAndReturn(run func() error) *MockProvider_Start_Call { - _c.Call.Return(run) - return _c -} - -// Stop provides a mock function with given fields: -func (_m *MockProvider) Stop() error { - ret := _m.Called() - - if len(ret) == 0 { - panic("no return value specified for Stop") - } - - var r0 error - if rf, ok := ret.Get(0).(func() error); ok { - r0 = rf() - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// MockProvider_Stop_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Stop' -type MockProvider_Stop_Call struct { - *mock.Call -} - -// Stop is a helper method to define mock.On call -func (_e *MockProvider_Expecter) Stop() *MockProvider_Stop_Call { - return &MockProvider_Stop_Call{Call: _e.mock.On("Stop")} -} - -func (_c *MockProvider_Stop_Call) Run(run func()) *MockProvider_Stop_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *MockProvider_Stop_Call) Return(_a0 error) *MockProvider_Stop_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *MockProvider_Stop_Call) RunAndReturn(run func() error) *MockProvider_Stop_Call { - _c.Call.Return(run) - return _c -} - -// UpdateConfig provides a mock function with given fields: _a0 -func (_m *MockProvider) UpdateConfig(_a0 *provider.Config) error { - ret := _m.Called(_a0) - - if len(ret) == 0 { - panic("no return value specified for UpdateConfig") - } - - var r0 error - if rf, ok := ret.Get(0).(func(*provider.Config) error); ok { - r0 = rf(_a0) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// MockProvider_UpdateConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateConfig' -type MockProvider_UpdateConfig_Call struct { - *mock.Call -} - -// UpdateConfig is a helper method to define mock.On call -// - _a0 *provider.Config -func (_e *MockProvider_Expecter) UpdateConfig(_a0 interface{}) *MockProvider_UpdateConfig_Call { - return &MockProvider_UpdateConfig_Call{Call: _e.mock.On("UpdateConfig", _a0)} -} - -func (_c *MockProvider_UpdateConfig_Call) Run(run func(_a0 *provider.Config)) *MockProvider_UpdateConfig_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(*provider.Config)) - }) - return _c -} - -func (_c *MockProvider_UpdateConfig_Call) Return(_a0 error) *MockProvider_UpdateConfig_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *MockProvider_UpdateConfig_Call) RunAndReturn(run func(*provider.Config) error) *MockProvider_UpdateConfig_Call { - _c.Call.Return(run) - return _c -} - -// UpdateHCPConfig provides a mock function with given fields: cfg -func (_m *MockProvider) UpdateHCPConfig(cfg config.CloudConfig) error { - ret := _m.Called(cfg) - - if len(ret) == 0 { - panic("no return value specified for UpdateHCPConfig") - } - - var r0 error - if rf, ok := ret.Get(0).(func(config.CloudConfig) error); ok { - r0 = rf(cfg) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// MockProvider_UpdateHCPConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateHCPConfig' -type MockProvider_UpdateHCPConfig_Call struct { - *mock.Call -} - -// UpdateHCPConfig is a helper method to define mock.On call -// - cfg config.CloudConfig -func (_e *MockProvider_Expecter) UpdateHCPConfig(cfg interface{}) *MockProvider_UpdateHCPConfig_Call { - return &MockProvider_UpdateHCPConfig_Call{Call: _e.mock.On("UpdateHCPConfig", cfg)} -} - -func (_c *MockProvider_UpdateHCPConfig_Call) Run(run func(cfg config.CloudConfig)) *MockProvider_UpdateHCPConfig_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(config.CloudConfig)) - }) - return _c -} - -func (_c *MockProvider_UpdateHCPConfig_Call) Return(_a0 error) *MockProvider_UpdateHCPConfig_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *MockProvider_UpdateHCPConfig_Call) RunAndReturn(run func(config.CloudConfig) error) *MockProvider_UpdateHCPConfig_Call { - _c.Call.Return(run) - return _c -} - -// UpdateMeta provides a mock function with given fields: _a0 -func (_m *MockProvider) UpdateMeta(_a0 map[string]string) { - _m.Called(_a0) -} - -// MockProvider_UpdateMeta_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateMeta' -type MockProvider_UpdateMeta_Call struct { - *mock.Call -} - -// UpdateMeta is a helper method to define mock.On call -// - _a0 map[string]string -func (_e *MockProvider_Expecter) UpdateMeta(_a0 interface{}) *MockProvider_UpdateMeta_Call { - return &MockProvider_UpdateMeta_Call{Call: _e.mock.On("UpdateMeta", _a0)} -} - -func (_c *MockProvider_UpdateMeta_Call) Run(run func(_a0 map[string]string)) *MockProvider_UpdateMeta_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(map[string]string)) - }) - return _c -} - -func (_c *MockProvider_UpdateMeta_Call) Return() *MockProvider_UpdateMeta_Call { - _c.Call.Return() - return _c -} - -func (_c *MockProvider_UpdateMeta_Call) RunAndReturn(run func(map[string]string)) *MockProvider_UpdateMeta_Call { - _c.Call.Return(run) - return _c -} - -// NewMockProvider creates a new instance of MockProvider. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewMockProvider(t interface { - mock.TestingT - Cleanup(func()) -}) *MockProvider { - mock := &MockProvider{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/agent/hcp/scada/scada.go b/agent/hcp/scada/scada.go deleted file mode 100644 index c62f45908bcf..000000000000 --- a/agent/hcp/scada/scada.go +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package scada - -import ( - "fmt" - "net" - - "github.com/hashicorp/consul/agent/hcp/config" - "github.com/hashicorp/go-hclog" - libscada "github.com/hashicorp/hcp-scada-provider" - "github.com/hashicorp/hcp-scada-provider/capability" - cloud "github.com/hashicorp/hcp-sdk-go/clients/cloud-shared/v1/models" - hcpcfg "github.com/hashicorp/hcp-sdk-go/config" -) - -// Provider is the interface used in the rest of Consul core when using SCADA, it is aliased here to the same interface -// provided by the hcp-scada-provider library. If the interfaces needs to be extended in the future it can be done so -// with minimal impact on the rest of the codebase. -// -//go:generate mockery --name Provider --with-expecter --inpackage -type Provider interface { - libscada.SCADAProvider - UpdateHCPConfig(cfg config.CloudConfig) error -} - -const ( - scadaConsulServiceKey = "consul" -) - -type scadaProvider struct { - libscada.SCADAProvider - logger hclog.Logger -} - -// New returns an initialized SCADA provider with a zero configuration. -// It can listen but cannot start until UpdateHCPConfig is called with -// a configuration that provides credentials to contact HCP. -func New(logger hclog.Logger) (*scadaProvider, error) { - // Create placeholder resource link - resourceLink := cloud.HashicorpCloudLocationLink{ - Type: "no-op", - ID: "no-op", - Location: &cloud.HashicorpCloudLocationLocation{}, - } - - // Configure with an empty HCP configuration - hcpConfig, err := hcpcfg.NewHCPConfig(hcpcfg.WithoutBrowserLogin()) - if err != nil { - return nil, fmt.Errorf("failed to configure SCADA provider: %w", err) - } - - pvd, err := libscada.New(&libscada.Config{ - Service: scadaConsulServiceKey, - HCPConfig: hcpConfig, - Resource: resourceLink, - Logger: logger, - }) - if err != nil { - return nil, err - } - - return &scadaProvider{pvd, logger}, nil -} - -// UpdateHCPConfig updates the SCADA provider with the given HCP -// configurations. -func (p *scadaProvider) UpdateHCPConfig(cfg config.CloudConfig) error { - resource, err := cfg.Resource() - if err != nil { - return err - } - - hcpCfg, err := cfg.HCPConfig() - if err != nil { - return err - } - - err = p.UpdateConfig(&libscada.Config{ - Service: scadaConsulServiceKey, - HCPConfig: hcpCfg, - Resource: *resource.Link(), - Logger: p.logger, - }) - if err != nil { - return err - } - - return nil -} - -// IsCapability takes a net.Addr and returns true if it is a SCADA capability.Addr -func IsCapability(a net.Addr) bool { - _, ok := a.(*capability.Addr) - return ok -} diff --git a/agent/hcp/scada/scada_test.go b/agent/hcp/scada/scada_test.go deleted file mode 100644 index 0cebed1b93a4..000000000000 --- a/agent/hcp/scada/scada_test.go +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package scada - -import ( - "testing" - - "github.com/hashicorp/consul/agent/hcp/config" - "github.com/hashicorp/go-hclog" - "github.com/stretchr/testify/require" -) - -func TestUpdateHCPConfig(t *testing.T) { - for name, tc := range map[string]struct { - cfg config.CloudConfig - expectedErr string - }{ - "Success": { - cfg: config.CloudConfig{ - ResourceID: "organization/85702e73-8a3d-47dc-291c-379b783c5804/project/8c0547c0-10e8-1ea2-dffe-384bee8da634/hashicorp.consul.global-network-manager.cluster/test", - ClientID: "test", - ClientSecret: "test", - }, - }, - "Empty": { - cfg: config.CloudConfig{}, - expectedErr: "could not parse resource: unexpected number of tokens 1", - }, - "InvalidResource": { - cfg: config.CloudConfig{ - ResourceID: "invalid", - }, - expectedErr: "could not parse resource: unexpected number of tokens 1", - }, - } { - t.Run(name, func(t *testing.T) { - // Create a provider - p, err := New(hclog.NewNullLogger()) - require.NoError(t, err) - - // Update the provider - err = p.UpdateHCPConfig(tc.cfg) - if tc.expectedErr != "" { - require.Error(t, err) - require.Contains(t, err.Error(), tc.expectedErr) - return - } - require.NoError(t, err) - }) - } -} diff --git a/agent/hcp/telemetry/custom_metrics.go b/agent/hcp/telemetry/custom_metrics.go deleted file mode 100644 index 39df765b9204..000000000000 --- a/agent/hcp/telemetry/custom_metrics.go +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package telemetry - -// Keys for custom Go Metrics metrics emitted only for the OTEL -// export (exporter.go) and transform (transform.go) failures and successes. -// These enable us to monitor OTEL operations. -var ( - internalMetricTransformFailure []string = []string{"hcp", "otel", "transform", "failure"} - - internalMetricExportSuccess []string = []string{"hcp", "otel", "exporter", "export", "success"} - internalMetricExportFailure []string = []string{"hcp", "otel", "exporter", "export", "failure"} - - internalMetricExporterShutdown []string = []string{"hcp", "otel", "exporter", "shutdown"} - internalMetricExporterForceFlush []string = []string{"hcp", "otel", "exporter", "force_flush"} -) diff --git a/agent/hcp/telemetry/doc.go b/agent/hcp/telemetry/doc.go deleted file mode 100644 index d982a37c0f3e..000000000000 --- a/agent/hcp/telemetry/doc.go +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -// Package telemetry implements functionality to collect, aggregate, convert and export -// telemetry data in OpenTelemetry Protocol (OTLP) format. -// -// The entrypoint is the OpenTelemetry (OTEL) go-metrics sink which: -// - Receives metric data. -// - Aggregates metric data using the OTEL Go Metrics SDK. -// - Exports metric data using a configurable OTEL exporter. -// -// The package also provides an OTEL exporter implementation to be used within the sink, which: -// - Transforms metric data from the Metrics SDK OTEL representation to OTLP format. -// - Exports OTLP metric data to an external endpoint using a configurable client. -package telemetry diff --git a/agent/hcp/telemetry/gauge_store.go b/agent/hcp/telemetry/gauge_store.go deleted file mode 100644 index bb7030dae852..000000000000 --- a/agent/hcp/telemetry/gauge_store.go +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package telemetry - -import ( - "context" - "sync" - - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/metric" -) - -// gaugeStore holds last seen Gauge values for a particular metric () in the store. -// OTEL does not currently have a synchronous Gauge instrument. Instead, it allows the registration of callbacks. -// The callbacks are called during export, where the Gauge value must be returned. -// This store is a workaround, which holds last seen Gauge values until the callback is called. -type gaugeStore struct { - store map[string]*gaugeValue - mutex sync.Mutex -} - -// gaugeValues are the last seen measurement for a Gauge metric, which contains a float64 value and labels. -type gaugeValue struct { - Value float64 - Attributes []attribute.KeyValue -} - -// NewGaugeStore returns an initialized empty gaugeStore. -func NewGaugeStore() *gaugeStore { - return &gaugeStore{ - store: make(map[string]*gaugeValue, 0), - } -} - -// LoadAndDelete will read a Gauge value and delete it. -// Once registered for a metric name, a Gauge callback will continue to execute every collection cycel. -// We must delete the value once we have read it, to avoid repeat values being sent. -func (g *gaugeStore) LoadAndDelete(key string) (*gaugeValue, bool) { - g.mutex.Lock() - defer g.mutex.Unlock() - - gauge, ok := g.store[key] - if !ok { - return nil, ok - } - - delete(g.store, key) - - return gauge, ok -} - -// Set adds a gaugeValue to the global gauge store. -func (g *gaugeStore) Set(key string, value float64, labels []attribute.KeyValue) { - g.mutex.Lock() - defer g.mutex.Unlock() - - gv := &gaugeValue{ - Value: value, - Attributes: labels, - } - - g.store[key] = gv -} - -// gaugeCallback returns a callback which gets called when metrics are collected for export. -func (g *gaugeStore) gaugeCallback(key string) metric.Float64Callback { - // Closures keep a reference to the key string, that get garbage collected when code completes. - return func(ctx context.Context, obs metric.Float64Observer) error { - select { - case <-ctx.Done(): - return ctx.Err() - default: - if gauge, ok := g.LoadAndDelete(key); ok { - obs.Observe(gauge.Value, metric.WithAttributes(gauge.Attributes...)) - } - return nil - } - } -} diff --git a/agent/hcp/telemetry/gauge_store_test.go b/agent/hcp/telemetry/gauge_store_test.go deleted file mode 100644 index 4ccac624dbd9..000000000000 --- a/agent/hcp/telemetry/gauge_store_test.go +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package telemetry - -import ( - "context" - "fmt" - "sync" - "testing" - - "github.com/stretchr/testify/require" - "go.opentelemetry.io/otel/attribute" -) - -func TestGaugeStore(t *testing.T) { - t.Parallel() - - gaugeStore := NewGaugeStore() - - attributes := []attribute.KeyValue{ - { - Key: attribute.Key("test_key"), - Value: attribute.StringValue("test_value"), - }, - } - - gaugeStore.Set("test", 1.23, attributes) - - // Should store a new gauge. - val, ok := gaugeStore.LoadAndDelete("test") - require.True(t, ok) - require.Equal(t, val.Value, 1.23) - require.Equal(t, val.Attributes, attributes) - - // Gauge with key "test" have been deleted. - val, ok = gaugeStore.LoadAndDelete("test") - require.False(t, ok) - require.Nil(t, val) - - gaugeStore.Set("duplicate", 1.5, nil) - gaugeStore.Set("duplicate", 6.7, nil) - - // Gauge with key "duplicate" should hold the latest (last seen) value. - val, ok = gaugeStore.LoadAndDelete("duplicate") - require.True(t, ok) - require.Equal(t, val.Value, 6.7) -} - -func TestGaugeCallback_Failure(t *testing.T) { - t.Parallel() - - k := "consul.raft.apply" - gaugeStore := NewGaugeStore() - gaugeStore.Set(k, 1.23, nil) - - cb := gaugeStore.gaugeCallback(k) - ctx, cancel := context.WithCancel(context.Background()) - - cancel() - err := cb(ctx, nil) - require.ErrorIs(t, err, context.Canceled) -} - -// TestGaugeStore_Race induces a race condition. When run with go test -race, -// this test should pass if implementation is concurrency safe. -func TestGaugeStore_Race(t *testing.T) { - t.Parallel() - - gaugeStore := NewGaugeStore() - - wg := &sync.WaitGroup{} - samples := 100 - errCh := make(chan error, samples) - for i := 0; i < samples; i++ { - wg.Add(1) - key := fmt.Sprintf("consul.test.%d", i) - value := 12.34 - go func() { - defer wg.Done() - gaugeStore.Set(key, value, nil) - gv, _ := gaugeStore.LoadAndDelete(key) - if gv.Value != value { - errCh <- fmt.Errorf("expected value: '%f', but got: '%f' for key: '%s'", value, gv.Value, key) - } - }() - } - - wg.Wait() - - require.Empty(t, errCh) -} diff --git a/agent/hcp/telemetry/otel_exporter.go b/agent/hcp/telemetry/otel_exporter.go deleted file mode 100644 index 63b7e51bf2ef..000000000000 --- a/agent/hcp/telemetry/otel_exporter.go +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package telemetry - -import ( - "context" - "fmt" - "net/url" - - goMetrics "github.com/armon/go-metrics" - "go.opentelemetry.io/otel/sdk/metric" - "go.opentelemetry.io/otel/sdk/metric/metricdata" - metricpb "go.opentelemetry.io/proto/otlp/metrics/v1" -) - -// MetricsClient exports Consul metrics in OTLP format to the desired endpoint. -type MetricsClient interface { - ExportMetrics(ctx context.Context, protoMetrics *metricpb.ResourceMetrics, endpoint string) error -} - -// EndpointProvider provides the endpoint where metrics are exported to by the OTELExporter. -// EndpointProvider exposes the GetEndpoint() interface method to fetch the endpoint. -// This abstraction layer offers flexibility, in particular for dynamic configuration or changes to the endpoint. -// The OTELExporter calls the Disabled interface to verify that it should actually export metrics. -type EndpointProvider interface { - Disabled - GetEndpoint() *url.URL -} - -// otelExporter is a custom implementation of a OTEL Metrics SDK metrics.Exporter. -// The exporter is used by a OTEL Metrics SDK PeriodicReader to export aggregated metrics. -// This allows us to use a custom client - HCP authenticated MetricsClient. -type otelExporter struct { - client MetricsClient - endpointProvider EndpointProvider -} - -// newOTELExporter returns a configured OTELExporter. -func newOTELExporter(client MetricsClient, endpointProvider EndpointProvider) *otelExporter { - return &otelExporter{ - client: client, - endpointProvider: endpointProvider, - } -} - -// Temporality returns the Cumulative temporality for metrics aggregation. -// Telemetry Gateway stores metrics in Prometheus format, so use Cummulative aggregation as default. -func (e *otelExporter) Temporality(_ metric.InstrumentKind) metricdata.Temporality { - return metricdata.CumulativeTemporality -} - -// Aggregation returns the Aggregation to use for an instrument kind. -// The default implementation provided by the OTEL Metrics SDK library DefaultAggregationSelector panics. -// This custom version replicates that logic, but removes the panic. -func (e *otelExporter) Aggregation(kind metric.InstrumentKind) metric.Aggregation { - switch kind { - case metric.InstrumentKindObservableGauge: - return metric.AggregationLastValue{} - case metric.InstrumentKindHistogram: - return metric.AggregationExplicitBucketHistogram{ - Boundaries: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, - NoMinMax: false, - } - } - // for metric.InstrumentKindCounter and others, default to sum. - return metric.AggregationSum{} -} - -// Export serializes and transmits metric data to a receiver. -func (e *otelExporter) Export(ctx context.Context, metrics *metricdata.ResourceMetrics) error { - if e.endpointProvider.IsDisabled() { - return nil - } - - endpoint := e.endpointProvider.GetEndpoint() - if endpoint == nil { - return nil - } - - otlpMetrics := transformOTLP(metrics) - if isEmpty(otlpMetrics) { - return nil - } - - err := e.client.ExportMetrics(ctx, otlpMetrics, endpoint.String()) - if err != nil { - goMetrics.IncrCounter(internalMetricExportFailure, 1) - return fmt.Errorf("failed to export metrics: %w", err) - } - - goMetrics.IncrCounter(internalMetricExportSuccess, 1) - return nil -} - -// ForceFlush is a no-op, as the MetricsClient client holds no state. -func (e *otelExporter) ForceFlush(ctx context.Context) error { - goMetrics.IncrCounter(internalMetricExporterForceFlush, 1) - return ctx.Err() -} - -// Shutdown is a no-op, as the MetricsClient is a HTTP client that requires no graceful shutdown. -func (e *otelExporter) Shutdown(ctx context.Context) error { - goMetrics.IncrCounter(internalMetricExporterShutdown, 1) - return ctx.Err() -} diff --git a/agent/hcp/telemetry/otel_exporter_test.go b/agent/hcp/telemetry/otel_exporter_test.go deleted file mode 100644 index 1d480da8ff93..000000000000 --- a/agent/hcp/telemetry/otel_exporter_test.go +++ /dev/null @@ -1,250 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package telemetry - -import ( - "context" - "fmt" - "net/url" - "strings" - "testing" - "time" - - "github.com/armon/go-metrics" - "github.com/stretchr/testify/require" - "go.opentelemetry.io/otel/sdk/metric" - "go.opentelemetry.io/otel/sdk/metric/metricdata" - "go.opentelemetry.io/otel/sdk/resource" - metricpb "go.opentelemetry.io/proto/otlp/metrics/v1" -) - -const ( - testExportEndpoint = "https://test.com/v1/metrics" -) - -type mockMetricsClient struct { - exportErr error -} - -func (m *mockMetricsClient) ExportMetrics(ctx context.Context, protoMetrics *metricpb.ResourceMetrics, endpoint string) error { - return m.exportErr -} - -type mockEndpointProvider struct { - endpoint *url.URL - disabled bool -} - -func (m *mockEndpointProvider) GetEndpoint() *url.URL { return m.endpoint } -func (m *mockEndpointProvider) IsDisabled() bool { return m.disabled } - -func TestTemporality(t *testing.T) { - t.Parallel() - exp := &otelExporter{} - require.Equal(t, metricdata.CumulativeTemporality, exp.Temporality(metric.InstrumentKindCounter)) -} - -func TestAggregation(t *testing.T) { - t.Parallel() - for name, test := range map[string]struct { - kind metric.InstrumentKind - expAgg metric.Aggregation - }{ - "gauge": { - kind: metric.InstrumentKindObservableGauge, - expAgg: metric.AggregationLastValue{}, - }, - "counter": { - kind: metric.InstrumentKindCounter, - expAgg: metric.AggregationSum{}, - }, - "histogram": { - kind: metric.InstrumentKindHistogram, - expAgg: metric.AggregationExplicitBucketHistogram{Boundaries: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, NoMinMax: false}, - }, - } { - test := test - t.Run(name, func(t *testing.T) { - t.Parallel() - exp := &otelExporter{} - require.Equal(t, test.expAgg, exp.Aggregation(test.kind)) - }) - } -} - -func TestExport(t *testing.T) { - t.Parallel() - for name, test := range map[string]struct { - wantErr string - metrics *metricdata.ResourceMetrics - client MetricsClient - provider EndpointProvider - }{ - "earlyReturnDisabledProvider": { - client: &mockMetricsClient{}, - provider: &mockEndpointProvider{ - disabled: true, - }, - }, - "earlyReturnWithoutEndpoint": { - client: &mockMetricsClient{}, - provider: &mockEndpointProvider{}, - }, - "earlyReturnWithoutScopeMetrics": { - client: &mockMetricsClient{}, - metrics: mutateMetrics(nil), - provider: &mockEndpointProvider{}, - }, - "earlyReturnWithoutMetrics": { - client: &mockMetricsClient{}, - metrics: mutateMetrics([]metricdata.ScopeMetrics{ - {Metrics: []metricdata.Metrics{}}, - }, - ), - provider: &mockEndpointProvider{}, - }, - "errorWithExportFailure": { - client: &mockMetricsClient{ - exportErr: fmt.Errorf("failed to export metrics."), - }, - metrics: mutateMetrics([]metricdata.ScopeMetrics{ - { - Metrics: []metricdata.Metrics{ - { - Name: "consul.raft.commitTime", - Data: metricdata.Gauge[float64]{}, - }, - }, - }, - }, - ), - provider: &mockEndpointProvider{ - endpoint: &url.URL{}, - }, - wantErr: "failed to export metrics", - }, - } { - test := test - t.Run(name, func(t *testing.T) { - t.Parallel() - provider := test.provider - if provider == nil { - u, err := url.Parse(testExportEndpoint) - require.NoError(t, err) - provider = &mockEndpointProvider{ - endpoint: u, - } - } - - exp := newOTELExporter(test.client, provider) - - err := exp.Export(context.Background(), test.metrics) - if test.wantErr != "" { - require.Error(t, err) - require.Contains(t, err.Error(), test.wantErr) - return - } - - require.NoError(t, err) - }) - } -} - -// TestExport_CustomMetrics tests that a custom metric (hcp.otel.exporter.*) is emitted -// for exporter operations. This test cannot be run in parallel as the metrics.NewGlobal() -// sets a shared global sink. -func TestExport_CustomMetrics(t *testing.T) { - for name, tc := range map[string]struct { - client MetricsClient - metricKey []string - operation string - }{ - "exportSuccessEmitsCustomMetric": { - client: &mockMetricsClient{}, - metricKey: internalMetricExportSuccess, - operation: "export", - }, - "exportFailureEmitsCustomMetric": { - client: &mockMetricsClient{ - exportErr: fmt.Errorf("client err"), - }, - metricKey: internalMetricExportFailure, - operation: "export", - }, - "shutdownEmitsCustomMetric": { - metricKey: internalMetricExporterShutdown, - operation: "shutdown", - }, - "forceFlushEmitsCustomMetric": { - metricKey: internalMetricExporterForceFlush, - operation: "flush", - }, - } { - t.Run(name, func(t *testing.T) { - // Init global sink. - serviceName := "test.transform" - cfg := metrics.DefaultConfig(serviceName) - cfg.EnableHostname = false - - sink := metrics.NewInmemSink(10*time.Second, 10*time.Second) - metrics.NewGlobal(cfg, sink) - - // Perform operation that emits metric. - u, err := url.Parse(testExportEndpoint) - require.NoError(t, err) - - exp := newOTELExporter(tc.client, &mockEndpointProvider{ - endpoint: u, - }) - - ctx := context.Background() - switch tc.operation { - case "flush": - exp.ForceFlush(ctx) - case "shutdown": - exp.Shutdown(ctx) - default: - exp.Export(ctx, inputResourceMetrics) - } - - // Collect sink metrics. - intervals := sink.Data() - require.Len(t, intervals, 1) - key := serviceName + "." + strings.Join(tc.metricKey, ".") - sv := intervals[0].Counters[key] - - // Verify count for transform failure metric. - require.NotNil(t, sv) - require.NotNil(t, sv.AggregateSample) - require.Equal(t, 1, sv.Count) - }) - } -} - -func TestForceFlush(t *testing.T) { - t.Parallel() - exp := &otelExporter{} - ctx, cancel := context.WithCancel(context.Background()) - cancel() - - err := exp.ForceFlush(ctx) - require.ErrorIs(t, err, context.Canceled) -} - -func TestShutdown(t *testing.T) { - t.Parallel() - exp := &otelExporter{} - ctx, cancel := context.WithCancel(context.Background()) - cancel() - - err := exp.Shutdown(ctx) - require.ErrorIs(t, err, context.Canceled) -} - -func mutateMetrics(m []metricdata.ScopeMetrics) *metricdata.ResourceMetrics { - return &metricdata.ResourceMetrics{ - Resource: resource.Empty(), - ScopeMetrics: m, - } -} diff --git a/agent/hcp/telemetry/otel_sink.go b/agent/hcp/telemetry/otel_sink.go deleted file mode 100644 index ad310774047b..000000000000 --- a/agent/hcp/telemetry/otel_sink.go +++ /dev/null @@ -1,298 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package telemetry - -import ( - "bytes" - "context" - "errors" - "regexp" - "strings" - "sync" - "time" - - gometrics "github.com/armon/go-metrics" - "go.opentelemetry.io/otel/attribute" - otelmetric "go.opentelemetry.io/otel/metric" - otelsdk "go.opentelemetry.io/otel/sdk/metric" - "go.opentelemetry.io/otel/sdk/resource" - - "github.com/hashicorp/go-hclog" -) - -const ( - // defaultExportInterval is a default time interval between export of aggregated metrics. - // At the time of writing this is the same as the otelsdk.Reader's default export interval. - defaultExportInterval = 60 * time.Second - - // defaultExportTimeout is the time the otelsdk.Reader waits on an export before cancelling it. - // At the time of writing this is the same as the otelsdk.Reader's default export timeout default. - // - // note: in practice we are more likely to hit the http.Client Timeout in telemetry.MetricsClient. - // That http.Client Timeout is 15 seconds (at the time of writing). The otelsdk.Reader will use - // defaultExportTimeout for the entire Export call, but since the http.Client's Timeout is 15s, - // we should hit that first before reaching the 30 second timeout set here. - defaultExportTimeout = 30 * time.Second -) - -// Disabled should be implemented to turn on/off metrics processing -type Disabled interface { - // IsDisabled() can return true disallow the sink from accepting metrics. - IsDisabled() bool -} - -// ConfigProvider is required to provide custom metrics processing. -type ConfigProvider interface { - Disabled - // GetLabels should return a set of OTEL attributes added by default all metrics. - GetLabels() map[string]string - - // GetFilters should return filtesr that are required to enable metric processing. - // Filters act as an allowlist to collect only the required metrics. - GetFilters() *regexp.Regexp -} - -// OTELSinkOpts is used to provide configuration when initializing an OTELSink using NewOTELSink. -type OTELSinkOpts struct { - Reader otelsdk.Reader - ConfigProvider ConfigProvider -} - -// OTELSink captures and aggregates telemetry data as per the OpenTelemetry (OTEL) specification. -// Metric data is exported in OpenTelemetry Protocol (OTLP) wire format. -// This should be used as a Go Metrics backend, as it implements the MetricsSink interface. -type OTELSink struct { - // spaceReplacer cleans the flattened key by removing any spaces. - spaceReplacer *strings.Replacer - logger hclog.Logger - cfgProvider ConfigProvider - - // meterProvider is an OTEL MeterProvider, the entrypoint to the OTEL Metrics SDK. - // It handles reading/export of aggregated metric data. - // It enables creation and usage of an OTEL Meter. - meterProvider *otelsdk.MeterProvider - - // meter is an OTEL Meter, which enables the creation of OTEL instruments. - meter *otelmetric.Meter - - // Instrument stores contain an OTEL Instrument per metric name () - // for each gauge, counter and histogram types. - // An instrument allows us to record a measurement for a particular metric, and continuously aggregates metrics. - // We lazy load the creation of these intruments until a metric is seen, and use them repeatedly to record measurements. - gaugeInstruments map[string]otelmetric.Float64ObservableGauge - counterInstruments map[string]otelmetric.Float64Counter - histogramInstruments map[string]otelmetric.Float64Histogram - - // gaugeStore is required to hold last-seen values of gauges - // This is a workaround, as OTEL currently does not have synchronous gauge instruments. - // It only allows the registration of "callbacks", which obtain values when the callback is called. - // We must hold gauge values until the callback is called, when the measurement is exported, and can be removed. - gaugeStore *gaugeStore - - mutex sync.Mutex -} - -// NewOTELReader returns a configured OTEL PeriodicReader to export metrics every X seconds. -// It configures the reader with a custom OTELExporter with a MetricsClient to transform and export -// metrics in OTLP format to an external url. -func NewOTELReader(client MetricsClient, endpointProvider EndpointProvider) otelsdk.Reader { - return otelsdk.NewPeriodicReader( - newOTELExporter(client, endpointProvider), - otelsdk.WithInterval(defaultExportInterval), - otelsdk.WithTimeout(defaultExportTimeout), - ) -} - -// NewOTELSink returns a sink which fits the Go Metrics MetricsSink interface. -// It sets up a MeterProvider and Meter, key pieces of the OTEL Metrics SDK which -// enable us to create OTEL Instruments to record measurements. -func NewOTELSink(ctx context.Context, opts *OTELSinkOpts) (*OTELSink, error) { - if opts.Reader == nil { - return nil, errors.New("ferror: provide valid reader") - } - - if opts.ConfigProvider == nil { - return nil, errors.New("ferror: provide valid config provider") - } - - logger := hclog.FromContext(ctx).Named("otel_sink") - - // Setup OTEL Metrics SDK to aggregate, convert and export metrics periodically. - res := resource.NewSchemaless() - meterProvider := otelsdk.NewMeterProvider(otelsdk.WithResource(res), otelsdk.WithReader(opts.Reader)) - meter := meterProvider.Meter("github.com/hashicorp/consul/agent/hcp/telemetry") - - return &OTELSink{ - cfgProvider: opts.ConfigProvider, - spaceReplacer: strings.NewReplacer(" ", "_"), - logger: logger, - meterProvider: meterProvider, - meter: &meter, - gaugeStore: NewGaugeStore(), - gaugeInstruments: make(map[string]otelmetric.Float64ObservableGauge, 0), - counterInstruments: make(map[string]otelmetric.Float64Counter, 0), - histogramInstruments: make(map[string]otelmetric.Float64Histogram, 0), - }, nil -} - -func (o *OTELSink) Shutdown() { - o.meterProvider.Shutdown(context.TODO()) -} - -// SetGauge emits a Consul gauge metric. -func (o *OTELSink) SetGauge(key []string, val float32) { - o.SetGaugeWithLabels(key, val, nil) -} - -// AddSample emits a Consul histogram metric. -func (o *OTELSink) AddSample(key []string, val float32) { - o.AddSampleWithLabels(key, val, nil) -} - -// IncrCounter emits a Consul counter metric. -func (o *OTELSink) IncrCounter(key []string, val float32) { - o.IncrCounterWithLabels(key, val, nil) -} - -// AddSampleWithLabels emits a Consul gauge metric that gets -// registed by an OpenTelemetry Histogram instrument. -func (o *OTELSink) SetGaugeWithLabels(key []string, val float32, labels []gometrics.Label) { - if o.cfgProvider.IsDisabled() { - return - } - - k := o.flattenKey(key) - if !o.allowedMetric(k) { - return - } - - // Set value in global Gauge store. - o.gaugeStore.Set(k, float64(val), o.labelsToAttributes(labels)) - - o.mutex.Lock() - defer o.mutex.Unlock() - - // If instrument does not exist, create it and register callback to emit last value in global Gauge store. - if _, ok := o.gaugeInstruments[k]; !ok { - // The registration of a callback only needs to happen once, when the instrument is created. - // The callback will be triggered every export cycle for that metric. - // It must be explicitly de-registered to be removed (which we do not do), to ensure new gauge values are exported every cycle. - inst, err := (*o.meter).Float64ObservableGauge(k, otelmetric.WithFloat64Callback(o.gaugeStore.gaugeCallback(k))) - if err != nil { - o.logger.Error("Failed to create gauge instrument", "error", err) - return - } - o.gaugeInstruments[k] = inst - } -} - -// AddSampleWithLabels emits a Consul sample metric that gets registed by an OpenTelemetry Histogram instrument. -func (o *OTELSink) AddSampleWithLabels(key []string, val float32, labels []gometrics.Label) { - if o.cfgProvider.IsDisabled() { - return - } - - k := o.flattenKey(key) - if !o.allowedMetric(k) { - return - } - - o.mutex.Lock() - defer o.mutex.Unlock() - - inst, ok := o.histogramInstruments[k] - if !ok { - histogram, err := (*o.meter).Float64Histogram(k) - if err != nil { - o.logger.Error("Failed create histogram instrument", "error", err) - return - } - inst = histogram - o.histogramInstruments[k] = inst - } - - attrs := o.labelsToAttributes(labels) - inst.Record(context.TODO(), float64(val), otelmetric.WithAttributes(attrs...)) -} - -// IncrCounterWithLabels emits a Consul counter metric that gets registed by an OpenTelemetry Histogram instrument. -func (o *OTELSink) IncrCounterWithLabels(key []string, val float32, labels []gometrics.Label) { - if o.cfgProvider.IsDisabled() { - return - } - - k := o.flattenKey(key) - if !o.allowedMetric(k) { - return - } - - o.mutex.Lock() - defer o.mutex.Unlock() - - inst, ok := o.counterInstruments[k] - if !ok { - counter, err := (*o.meter).Float64Counter(k) - if err != nil { - o.logger.Error("Failed to create counter instrument:", "error", err) - return - } - - inst = counter - o.counterInstruments[k] = inst - } - - attrs := o.labelsToAttributes(labels) - inst.Add(context.TODO(), float64(val), otelmetric.WithAttributes(attrs...)) -} - -// EmitKey unsupported. -func (o *OTELSink) EmitKey(key []string, val float32) {} - -// flattenKey key along with its labels. -func (o *OTELSink) flattenKey(parts []string) string { - buf := &bytes.Buffer{} - joined := strings.Join(parts, ".") - - o.spaceReplacer.WriteString(buf, joined) - - return buf.String() -} - -// filter checks the filter allowlist, if it exists, to verify if this metric should be recorded. -func (o *OTELSink) allowedMetric(key string) bool { - if filters := o.cfgProvider.GetFilters(); filters != nil { - return filters.MatchString(key) - } - - return true -} - -// labelsToAttributes converts go metrics and provider labels into OTEL format []attributes.KeyValue -func (o *OTELSink) labelsToAttributes(goMetricsLabels []gometrics.Label) []attribute.KeyValue { - providerLabels := o.cfgProvider.GetLabels() - - length := len(goMetricsLabels) + len(providerLabels) - if length == 0 { - return []attribute.KeyValue{} - } - - attrs := make([]attribute.KeyValue, 0, length) - // Convert provider labels to OTEL attributes. - for _, label := range goMetricsLabels { - attrs = append(attrs, attribute.KeyValue{ - Key: attribute.Key(label.Name), - Value: attribute.StringValue(label.Value), - }) - } - - // Convert provider labels to OTEL attributes. - for k, v := range providerLabels { - attrs = append(attrs, attribute.KeyValue{ - Key: attribute.Key(k), - Value: attribute.StringValue(v), - }) - } - - return attrs -} diff --git a/agent/hcp/telemetry/otel_sink_test.go b/agent/hcp/telemetry/otel_sink_test.go deleted file mode 100644 index f282ddad59f4..000000000000 --- a/agent/hcp/telemetry/otel_sink_test.go +++ /dev/null @@ -1,579 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package telemetry - -import ( - "context" - "fmt" - "regexp" - "sort" - "strings" - "sync" - "testing" - - gometrics "github.com/armon/go-metrics" - "github.com/stretchr/testify/require" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/sdk/metric" - "go.opentelemetry.io/otel/sdk/metric/metricdata" - "go.opentelemetry.io/otel/sdk/resource" -) - -type mockConfigProvider struct { - filter *regexp.Regexp - labels map[string]string - disabled bool -} - -func (m *mockConfigProvider) GetLabels() map[string]string { - return m.labels -} - -func (m *mockConfigProvider) GetFilters() *regexp.Regexp { - return m.filter -} - -func (m *mockConfigProvider) IsDisabled() bool { - return m.disabled -} - -var ( - expectedResource = resource.NewSchemaless() - - attrs = attribute.NewSet(attribute.KeyValue{ - Key: attribute.Key("node_id"), - Value: attribute.StringValue("test"), - }) - attrsWithMetricLabel = attribute.NewSet(attribute.KeyValue{ - Key: attribute.Key("metric.label"), - Value: attribute.StringValue("test"), - }, attribute.KeyValue{ - Key: attribute.Key("node_id"), - Value: attribute.StringValue("test"), - }) - - expectedSinkMetrics = map[string]metricdata.Metrics{ - "consul.raft.leader": { - Name: "consul.raft.leader", - Description: "", - Unit: "", - Data: metricdata.Gauge[float64]{ - DataPoints: []metricdata.DataPoint[float64]{ - { - Attributes: attrs, - Value: float64(float32(0)), - }, - }, - }, - }, - "consul.autopilot.healthy": { - Name: "consul.autopilot.healthy", - Description: "", - Unit: "", - Data: metricdata.Gauge[float64]{ - DataPoints: []metricdata.DataPoint[float64]{ - { - Attributes: attrsWithMetricLabel, - Value: float64(float32(1.23)), - }, - }, - }, - }, - "consul.raft.state.leader": { - Name: "consul.raft.state.leader", - Description: "", - Unit: "", - Data: metricdata.Sum[float64]{ - DataPoints: []metricdata.DataPoint[float64]{ - { - Attributes: attrs, - Value: float64(float32(23.23)), - }, - }, - }, - }, - "consul.raft.apply": { - Name: "consul.raft.apply", - Description: "", - Unit: "", - Data: metricdata.Sum[float64]{ - DataPoints: []metricdata.DataPoint[float64]{ - { - Attributes: attrsWithMetricLabel, - Value: float64(float32(1.44)), - }, - }, - }, - }, - "consul.raft.leader.lastContact": { - Name: "consul.raft.leader.lastContact", - Description: "", - Unit: "", - Data: metricdata.Histogram[float64]{ - DataPoints: []metricdata.HistogramDataPoint[float64]{ - { - Attributes: attrs, - Count: 1, - Sum: float64(float32(45.32)), - Min: metricdata.NewExtrema(float64(float32(45.32))), - Max: metricdata.NewExtrema(float64(float32(45.32))), - }, - }, - }, - }, - "consul.raft.commitTime": { - Name: "consul.raft.commitTime", - Description: "", - Unit: "", - Data: metricdata.Histogram[float64]{ - DataPoints: []metricdata.HistogramDataPoint[float64]{ - { - Attributes: attrsWithMetricLabel, - Count: 1, - Sum: float64(float32(26.34)), - Min: metricdata.NewExtrema(float64(float32(26.34))), - Max: metricdata.NewExtrema(float64(float32(26.34))), - }, - }, - }, - }, - } -) - -func TestNewOTELSink(t *testing.T) { - t.Parallel() - for name, test := range map[string]struct { - wantErr string - opts *OTELSinkOpts - }{ - "failsWithEmptyReader": { - wantErr: "ferror: provide valid reader", - opts: &OTELSinkOpts{ - Reader: nil, - ConfigProvider: &mockConfigProvider{}, - }, - }, - "failsWithEmptyConfigProvider": { - wantErr: "ferror: provide valid config provider", - opts: &OTELSinkOpts{ - Reader: metric.NewManualReader(), - }, - }, - "success": { - opts: &OTELSinkOpts{ - Reader: metric.NewManualReader(), - ConfigProvider: &mockConfigProvider{}, - }, - }, - } { - test := test - t.Run(name, func(t *testing.T) { - t.Parallel() - sink, err := NewOTELSink(context.Background(), test.opts) - if test.wantErr != "" { - require.Error(t, err) - require.Contains(t, err.Error(), test.wantErr) - return - } - - require.NotNil(t, sink) - }) - } -} - -func TestOTELSink(t *testing.T) { - t.Parallel() - - // Manual reader outputs the aggregated metrics when reader.Collect is called. - reader := metric.NewManualReader() - - ctx := context.Background() - opts := &OTELSinkOpts{ - Reader: reader, - ConfigProvider: &mockConfigProvider{ - filter: regexp.MustCompile("raft|autopilot"), - labels: map[string]string{ - "node_id": "test", - }, - }, - } - - sink, err := NewOTELSink(ctx, opts) - require.NoError(t, err) - - labels := []gometrics.Label{ - { - Name: "metric.label", - Value: "test", - }, - } - - sink.SetGauge([]string{"test", "bad_filter", "gauge"}, float32(0)) - sink.SetGauge([]string{"consul", "raft", "leader"}, float32(0)) - sink.SetGaugeWithLabels([]string{"consul", "autopilot", "healthy"}, float32(1.23), labels) - - sink.IncrCounter([]string{"test", "bad_filter", "counter"}, float32(23.23)) - sink.IncrCounter([]string{"consul", "raft", "state", "leader"}, float32(23.23)) - sink.IncrCounterWithLabels([]string{"consul", "raft", "apply"}, float32(1.44), labels) - - sink.AddSample([]string{"test", "bad_filter", "sample"}, float32(45.32)) - sink.AddSample([]string{"consul", "raft", "leader", "lastContact"}, float32(45.32)) - sink.AddSampleWithLabels([]string{"consul", "raft", "commitTime"}, float32(26.34), labels) - - var collected metricdata.ResourceMetrics - err = reader.Collect(ctx, &collected) - require.NoError(t, err) - - isSame(t, expectedSinkMetrics, collected) -} - -func TestOTELSinkDisabled(t *testing.T) { - reader := metric.NewManualReader() - ctx := context.Background() - - sink, err := NewOTELSink(ctx, &OTELSinkOpts{ - ConfigProvider: &mockConfigProvider{ - filter: regexp.MustCompile("raft"), - disabled: true, - }, - Reader: reader, - }) - require.NoError(t, err) - - sink.SetGauge([]string{"consul", "raft", "gauge"}, 1) - sink.IncrCounter([]string{"consul", "raft", "counter"}, 1) - sink.AddSample([]string{"consul", "raft", "sample"}, 1) - - var collected metricdata.ResourceMetrics - err = reader.Collect(ctx, &collected) - require.NoError(t, err) - require.Empty(t, collected.ScopeMetrics) -} - -func TestLabelsToAttributes(t *testing.T) { - for name, test := range map[string]struct { - providerLabels map[string]string - goMetricsLabels []gometrics.Label - expectedOTELAttributes []attribute.KeyValue - }{ - "emptyLabels": { - expectedOTELAttributes: []attribute.KeyValue{}, - }, - "emptyGoMetricsLabels": { - providerLabels: map[string]string{ - "node_id": "test", - }, - expectedOTELAttributes: []attribute.KeyValue{ - { - Key: attribute.Key("node_id"), - Value: attribute.StringValue("test"), - }, - }, - }, - "emptyProviderLabels": { - goMetricsLabels: []gometrics.Label{ - { - Name: "server_type", - Value: "internal", - }, - }, - expectedOTELAttributes: []attribute.KeyValue{ - { - Key: attribute.Key("server_type"), - Value: attribute.StringValue("internal"), - }, - }, - }, - "combinedLabels": { - goMetricsLabels: []gometrics.Label{ - { - Name: "server_type", - Value: "internal", - }, - { - Name: "method", - Value: "get", - }, - }, - providerLabels: map[string]string{ - "node_id": "test", - "node_name": "labels_test", - }, - expectedOTELAttributes: []attribute.KeyValue{ - { - Key: attribute.Key("server_type"), - Value: attribute.StringValue("internal"), - }, - { - Key: attribute.Key("method"), - Value: attribute.StringValue("get"), - }, - { - Key: attribute.Key("node_id"), - Value: attribute.StringValue("test"), - }, - { - Key: attribute.Key("node_name"), - Value: attribute.StringValue("labels_test"), - }, - }, - }, - } { - test := test - t.Run(name, func(t *testing.T) { - t.Parallel() - ctx := context.Background() - opts := &OTELSinkOpts{ - Reader: metric.NewManualReader(), - ConfigProvider: &mockConfigProvider{ - filter: regexp.MustCompile("raft|autopilot"), - labels: test.providerLabels, - }, - } - sink, err := NewOTELSink(ctx, opts) - require.NoError(t, err) - - require.ElementsMatch(t, test.expectedOTELAttributes, sink.labelsToAttributes(test.goMetricsLabels)) - }) - } -} - -func TestOTELSinkFilters(t *testing.T) { - t.Parallel() - for name, tc := range map[string]struct { - cfgProvider ConfigProvider - expected bool - }{ - "emptyMatch": { - cfgProvider: &mockConfigProvider{}, - expected: true, - }, - "matchingFilter": { - cfgProvider: &mockConfigProvider{ - filter: regexp.MustCompile("raft"), - }, - expected: true, - }, - "mismatchFilter": {cfgProvider: &mockConfigProvider{ - filter: regexp.MustCompile("test"), - }}, - } { - tc := tc - t.Run(name, func(t *testing.T) { - t.Parallel() - testMetricKey := "consul.raft" - s, err := NewOTELSink(context.Background(), &OTELSinkOpts{ - ConfigProvider: tc.cfgProvider, - Reader: metric.NewManualReader(), - }) - require.NoError(t, err) - require.Equal(t, tc.expected, s.allowedMetric(testMetricKey)) - }) - } -} - -func TestOTELSink_Race(t *testing.T) { - reader := metric.NewManualReader() - ctx := context.Background() - defaultLabels := map[string]string{ - "node_id": "test", - } - opts := &OTELSinkOpts{ - Reader: reader, - ConfigProvider: &mockConfigProvider{ - filter: regexp.MustCompile("test"), - labels: defaultLabels, - }, - } - - sink, err := NewOTELSink(context.Background(), opts) - require.NoError(t, err) - - samples := 100 - expectedMetrics := generateSamples(samples, defaultLabels) - wg := &sync.WaitGroup{} - errCh := make(chan error, samples) - for k, v := range expectedMetrics { - wg.Add(1) - go func(k string, v metricdata.Metrics) { - defer wg.Done() - performSinkOperation(sink, k, v) - }(k, v) - } - wg.Wait() - - require.Empty(t, errCh) - - var collected metricdata.ResourceMetrics - err = reader.Collect(ctx, &collected) - require.NoError(t, err) - - isSame(t, expectedMetrics, collected) -} - -// generateSamples generates n of each gauges, counter and histogram measurements to use for test purposes. -func generateSamples(n int, labels map[string]string) map[string]metricdata.Metrics { - generated := make(map[string]metricdata.Metrics, 3*n) - attrs := *attribute.EmptySet() - - kvs := make([]attribute.KeyValue, 0, len(labels)) - for k, v := range labels { - kvs = append(kvs, attribute.KeyValue{Key: attribute.Key(k), Value: attribute.StringValue(v)}) - } - if len(kvs) > 0 { - attrs = attribute.NewSet(kvs...) - } - - for i := 0; i < n; i++ { - v := 12.3 - k := fmt.Sprintf("consul.test.gauges.%d", i) - generated[k] = metricdata.Metrics{ - Name: k, - Data: metricdata.Gauge[float64]{ - DataPoints: []metricdata.DataPoint[float64]{ - { - Attributes: attrs, - Value: float64(float32(v)), - }, - }, - }, - } - } - - for i := 0; i < n; i++ { - v := 22.23 - k := fmt.Sprintf("consul.test.sum.%d", i) - generated[k] = metricdata.Metrics{ - Name: k, - Data: metricdata.Sum[float64]{ - DataPoints: []metricdata.DataPoint[float64]{ - { - Attributes: attrs, - Value: float64(float32(v)), - }, - }, - }, - } - - } - - for i := 0; i < n; i++ { - v := 13.24 - k := fmt.Sprintf("consul.test.hist.%d", i) - generated[k] = metricdata.Metrics{ - Name: k, - Data: metricdata.Histogram[float64]{ - DataPoints: []metricdata.HistogramDataPoint[float64]{ - { - Attributes: attrs, - Sum: float64(float32(v)), - Max: metricdata.NewExtrema(float64(float32(v))), - Min: metricdata.NewExtrema(float64(float32(v))), - Count: 1, - }, - }, - }, - } - } - - return generated -} - -// performSinkOperation emits a measurement using the OTELSink and calls wg.Done() when completed. -func performSinkOperation(sink *OTELSink, k string, v metricdata.Metrics) { - key := strings.Split(k, ".") - data := v.Data - switch data := data.(type) { - case metricdata.Gauge[float64]: - sink.SetGauge(key, float32(data.DataPoints[0].Value)) - case metricdata.Sum[float64]: - sink.IncrCounter(key, float32(data.DataPoints[0].Value)) - case metricdata.Histogram[float64]: - sink.AddSample(key, float32(data.DataPoints[0].Sum)) - } -} - -func isSame(t *testing.T, expectedMap map[string]metricdata.Metrics, actual metricdata.ResourceMetrics) { - // Validate resource - require.Equal(t, expectedResource, actual.Resource) - - // Validate Metrics - require.NotEmpty(t, actual.ScopeMetrics) - actualMetrics := actual.ScopeMetrics[0].Metrics - require.Equal(t, len(expectedMap), len(actualMetrics)) - - for _, actual := range actualMetrics { - name := actual.Name - expected, ok := expectedMap[actual.Name] - require.True(t, ok, "metric key %s should be in expectedMetrics map", name) - isSameMetrics(t, expected, actual) - } -} - -// compareMetrics verifies if two metricdata.Metric objects are equal by ignoring the time component. -// avoid duplicate datapoint values to ensure predictable order of sort. -func isSameMetrics(t *testing.T, expected metricdata.Metrics, actual metricdata.Metrics) { - require.Equal(t, expected.Name, actual.Name, "different .Name field") - require.Equal(t, expected.Description, actual.Description, "different .Description field") - require.Equal(t, expected.Unit, actual.Unit, "different .Unit field") - - switch expectedData := expected.Data.(type) { - case metricdata.Gauge[float64]: - actualData, ok := actual.Data.(metricdata.Gauge[float64]) - require.True(t, ok, "different metric types: expected metricdata.Gauge[float64]") - - isSameDataPoint(t, expectedData.DataPoints, actualData.DataPoints) - case metricdata.Sum[float64]: - actualData, ok := actual.Data.(metricdata.Sum[float64]) - require.True(t, ok, "different metric types: expected metricdata.Sum[float64]") - - isSameDataPoint(t, expectedData.DataPoints, actualData.DataPoints) - case metricdata.Histogram[float64]: - actualData, ok := actual.Data.(metricdata.Histogram[float64]) - require.True(t, ok, "different metric types: expected metricdata.Histogram") - - isSameHistogramData(t, expectedData.DataPoints, actualData.DataPoints) - } -} - -func isSameDataPoint(t *testing.T, expected []metricdata.DataPoint[float64], actual []metricdata.DataPoint[float64]) { - require.Equal(t, len(expected), len(actual), "different datapoints length") - - // Sort for predictable data in order of lowest value. - sort.Slice(expected, func(i, j int) bool { - return expected[i].Value < expected[j].Value - }) - sort.Slice(actual, func(i, j int) bool { - return expected[i].Value < expected[j].Value - }) - - // Only verify the value and attributes. - for i, dp := range expected { - currActual := actual[i] - require.Equal(t, dp.Value, currActual.Value, "different datapoint value") - require.Equal(t, dp.Attributes, currActual.Attributes, "different attributes") - } -} - -func isSameHistogramData(t *testing.T, expected []metricdata.HistogramDataPoint[float64], actual []metricdata.HistogramDataPoint[float64]) { - require.Equal(t, len(expected), len(actual), "different histogram datapoint length") - - // Sort for predictable data in order of lowest sum. - sort.Slice(expected, func(i, j int) bool { - return expected[i].Sum < expected[j].Sum - }) - sort.Slice(actual, func(i, j int) bool { - return expected[i].Sum < expected[j].Sum - }) - - // Only verify the value and the attributes. - for i, dp := range expected { - currActual := actual[i] - require.Equal(t, dp.Sum, currActual.Sum, "different histogram datapoint .Sum value") - require.Equal(t, dp.Max, currActual.Max, "different histogram datapoint .Max value") - require.Equal(t, dp.Min, currActual.Min, "different histogram datapoint .Min value") - require.Equal(t, dp.Count, currActual.Count, "different histogram datapoint .Count value") - require.Equal(t, dp.Attributes, currActual.Attributes, "different attributes") - } -} diff --git a/agent/hcp/telemetry/otlp_transform.go b/agent/hcp/telemetry/otlp_transform.go deleted file mode 100644 index 907e7922ad98..000000000000 --- a/agent/hcp/telemetry/otlp_transform.go +++ /dev/null @@ -1,189 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package telemetry - -import ( - "errors" - "fmt" - - goMetrics "github.com/armon/go-metrics" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/sdk/metric/metricdata" - cpb "go.opentelemetry.io/proto/otlp/common/v1" - mpb "go.opentelemetry.io/proto/otlp/metrics/v1" - rpb "go.opentelemetry.io/proto/otlp/resource/v1" -) - -var ( - errAggregaton = errors.New("unsupported aggregation") - errTemporality = errors.New("unsupported temporality") -) - -// isEmpty verifies if the given OTLP protobuf metrics contains metric data. -// isEmpty returns true if no ScopeMetrics exist or all metrics within ScopeMetrics are empty. -func isEmpty(rm *mpb.ResourceMetrics) bool { - // No ScopeMetrics - if len(rm.ScopeMetrics) == 0 { - return true - } - - // If any inner metrics contain data, return false. - for _, v := range rm.ScopeMetrics { - if len(v.Metrics) != 0 { - return false - } - } - - // All inner metrics are empty. - return true -} - -// TransformOTLP returns an OTLP ResourceMetrics generated from OTEL metrics. If rm -// contains invalid ScopeMetrics, an error will be returned along with an OTLP -// ResourceMetrics that contains partial OTLP ScopeMetrics. -func transformOTLP(rm *metricdata.ResourceMetrics) *mpb.ResourceMetrics { - sms := scopeMetricsToPB(rm.ScopeMetrics) - return &mpb.ResourceMetrics{ - Resource: &rpb.Resource{ - Attributes: attributesToPB(rm.Resource.Iter()), - }, - ScopeMetrics: sms, - } -} - -// scopeMetrics returns a slice of OTLP ScopeMetrics. -func scopeMetricsToPB(scopeMetrics []metricdata.ScopeMetrics) []*mpb.ScopeMetrics { - out := make([]*mpb.ScopeMetrics, 0, len(scopeMetrics)) - for _, sm := range scopeMetrics { - ms := metricsToPB(sm.Metrics) - out = append(out, &mpb.ScopeMetrics{ - Scope: &cpb.InstrumentationScope{ - Name: sm.Scope.Name, - Version: sm.Scope.Version, - }, - Metrics: ms, - }) - } - return out -} - -// metrics returns a slice of OTLP Metric generated from OTEL metrics sdk ones. -func metricsToPB(metrics []metricdata.Metrics) []*mpb.Metric { - out := make([]*mpb.Metric, 0, len(metrics)) - for _, m := range metrics { - o, err := metricTypeToPB(m) - if err != nil { - goMetrics.IncrCounter(internalMetricTransformFailure, 1) - continue - } - out = append(out, o) - } - return out -} - -// metricType identifies the instrument type and converts it to OTLP format. -// only float64 values are accepted since the go metrics sink only receives float64 values. -func metricTypeToPB(m metricdata.Metrics) (*mpb.Metric, error) { - out := &mpb.Metric{ - Name: m.Name, - Description: m.Description, - Unit: m.Unit, - } - switch a := m.Data.(type) { - case metricdata.Gauge[float64]: - out.Data = &mpb.Metric_Gauge{ - Gauge: &mpb.Gauge{ - DataPoints: dataPointsToPB(a.DataPoints), - }, - } - case metricdata.Sum[float64]: - if a.Temporality != metricdata.CumulativeTemporality { - return out, fmt.Errorf("failed to convert metric to otel format: %w: %T", errTemporality, a) - } - out.Data = &mpb.Metric_Sum{ - Sum: &mpb.Sum{ - AggregationTemporality: mpb.AggregationTemporality_AGGREGATION_TEMPORALITY_CUMULATIVE, - IsMonotonic: a.IsMonotonic, - DataPoints: dataPointsToPB(a.DataPoints), - }, - } - case metricdata.Histogram[float64]: - if a.Temporality != metricdata.CumulativeTemporality { - return out, fmt.Errorf("failed to convert metric to otel format: %w: %T", errTemporality, a) - } - out.Data = &mpb.Metric_Histogram{ - Histogram: &mpb.Histogram{ - AggregationTemporality: mpb.AggregationTemporality_AGGREGATION_TEMPORALITY_CUMULATIVE, - DataPoints: histogramDataPointsToPB(a.DataPoints), - }, - } - default: - return out, fmt.Errorf("failed to convert metric to otel format: %w: %T", errAggregaton, a) - } - return out, nil -} - -// DataPoints returns a slice of OTLP NumberDataPoint generated from OTEL metrics sdk ones. -func dataPointsToPB(dataPoints []metricdata.DataPoint[float64]) []*mpb.NumberDataPoint { - out := make([]*mpb.NumberDataPoint, 0, len(dataPoints)) - for _, dp := range dataPoints { - ndp := &mpb.NumberDataPoint{ - Attributes: attributesToPB(dp.Attributes.Iter()), - StartTimeUnixNano: uint64(dp.StartTime.UnixNano()), - TimeUnixNano: uint64(dp.Time.UnixNano()), - } - - ndp.Value = &mpb.NumberDataPoint_AsDouble{ - AsDouble: dp.Value, - } - out = append(out, ndp) - } - return out -} - -// HistogramDataPoints returns a slice of OTLP HistogramDataPoint from OTEL metrics sdk ones. -func histogramDataPointsToPB(dataPoints []metricdata.HistogramDataPoint[float64]) []*mpb.HistogramDataPoint { - out := make([]*mpb.HistogramDataPoint, 0, len(dataPoints)) - for _, dp := range dataPoints { - sum := dp.Sum - hdp := &mpb.HistogramDataPoint{ - Attributes: attributesToPB(dp.Attributes.Iter()), - StartTimeUnixNano: uint64(dp.StartTime.UnixNano()), - TimeUnixNano: uint64(dp.Time.UnixNano()), - Count: dp.Count, - Sum: &sum, - BucketCounts: dp.BucketCounts, - ExplicitBounds: dp.Bounds, - } - if v, ok := dp.Min.Value(); ok { - hdp.Min = &v - } - if v, ok := dp.Max.Value(); ok { - hdp.Max = &v - } - out = append(out, hdp) - } - return out -} - -// attributes transforms items of an attribute iterator into OTLP key-values. -// Currently, labels are only key-value pairs. -func attributesToPB(iter attribute.Iterator) []*cpb.KeyValue { - l := iter.Len() - if iter.Len() == 0 { - return nil - } - - out := make([]*cpb.KeyValue, 0, l) - for iter.Next() { - kv := iter.Attribute() - av := &cpb.AnyValue{ - Value: &cpb.AnyValue_StringValue{ - StringValue: kv.Value.AsString(), - }, - } - out = append(out, &cpb.KeyValue{Key: string(kv.Key), Value: av}) - } - return out -} diff --git a/agent/hcp/telemetry/otlp_transform_test.go b/agent/hcp/telemetry/otlp_transform_test.go deleted file mode 100644 index ec7ac16e131f..000000000000 --- a/agent/hcp/telemetry/otlp_transform_test.go +++ /dev/null @@ -1,345 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package telemetry - -import ( - "strings" - "testing" - "time" - - "github.com/armon/go-metrics" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/sdk/instrumentation" - "go.opentelemetry.io/otel/sdk/metric/metricdata" - "go.opentelemetry.io/otel/sdk/resource" - semconv "go.opentelemetry.io/otel/semconv/v1.17.0" - cpb "go.opentelemetry.io/proto/otlp/common/v1" - mpb "go.opentelemetry.io/proto/otlp/metrics/v1" - rpb "go.opentelemetry.io/proto/otlp/resource/v1" -) - -var ( - // Common attributes for test cases. - start = time.Date(2000, time.January, 01, 0, 0, 0, 0, time.FixedZone("GMT", 0)) - end = start.Add(30 * time.Second) - - alice = attribute.NewSet(attribute.String("user", "alice")) - bob = attribute.NewSet(attribute.String("user", "bob")) - - pbAlice = &cpb.KeyValue{Key: "user", Value: &cpb.AnyValue{ - Value: &cpb.AnyValue_StringValue{StringValue: "alice"}, - }} - pbBob = &cpb.KeyValue{Key: "user", Value: &cpb.AnyValue{ - Value: &cpb.AnyValue_StringValue{StringValue: "bob"}, - }} - - // DataPoint test case : Histogram Datapoints (Histogram) - minA, maxA, sumA = 2.0, 4.0, 90.0 - minB, maxB, sumB = 4.0, 150.0, 234.0 - inputHDP = []metricdata.HistogramDataPoint[float64]{{ - Attributes: alice, - StartTime: start, - Time: end, - Count: 30, - Bounds: []float64{1, 5}, - BucketCounts: []uint64{0, 30, 0}, - Min: metricdata.NewExtrema(minA), - Max: metricdata.NewExtrema(maxA), - Sum: sumA, - }, { - Attributes: bob, - StartTime: start, - Time: end, - Count: 3, - Bounds: []float64{1, 5}, - BucketCounts: []uint64{0, 1, 2}, - Min: metricdata.NewExtrema(minB), - Max: metricdata.NewExtrema(maxB), - Sum: sumB, - }} - - expectedHDP = []*mpb.HistogramDataPoint{{ - Attributes: []*cpb.KeyValue{pbAlice}, - StartTimeUnixNano: uint64(start.UnixNano()), - TimeUnixNano: uint64(end.UnixNano()), - Count: 30, - Sum: &sumA, - ExplicitBounds: []float64{1, 5}, - BucketCounts: []uint64{0, 30, 0}, - Min: &minA, - Max: &maxA, - }, { - Attributes: []*cpb.KeyValue{pbBob}, - StartTimeUnixNano: uint64(start.UnixNano()), - TimeUnixNano: uint64(end.UnixNano()), - Count: 3, - Sum: &sumB, - ExplicitBounds: []float64{1, 5}, - BucketCounts: []uint64{0, 1, 2}, - Min: &minB, - Max: &maxB, - }} - // DataPoint test case : Number Datapoints (Gauge / Counter) - inputDP = []metricdata.DataPoint[float64]{ - {Attributes: alice, StartTime: start, Time: end, Value: 1.0}, - {Attributes: bob, StartTime: start, Time: end, Value: 2.0}, - } - - expectedDP = []*mpb.NumberDataPoint{ - { - Attributes: []*cpb.KeyValue{pbAlice}, - StartTimeUnixNano: uint64(start.UnixNano()), - TimeUnixNano: uint64(end.UnixNano()), - Value: &mpb.NumberDataPoint_AsDouble{AsDouble: 1.0}, - }, - { - Attributes: []*cpb.KeyValue{pbBob}, - StartTimeUnixNano: uint64(start.UnixNano()), - TimeUnixNano: uint64(end.UnixNano()), - Value: &mpb.NumberDataPoint_AsDouble{AsDouble: 2.0}, - }, - } - - invalidSumTemporality = metricdata.Metrics{ - Name: "invalid-sum", - Description: "Sum with invalid temporality", - Unit: "1", - Data: metricdata.Sum[float64]{ - Temporality: metricdata.DeltaTemporality, - IsMonotonic: false, - DataPoints: inputDP, - }, - } - - invalidSumAgg = metricdata.Metrics{ - Name: "unknown", - Description: "Unknown aggregation", - Unit: "1", - Data: metricdata.Sum[int64]{}, - } - - invalidHistTemporality = metricdata.Metrics{ - Name: "invalid-histogram", - Description: "Invalid histogram", - Unit: "1", - Data: metricdata.Histogram[float64]{ - Temporality: metricdata.DeltaTemporality, - DataPoints: inputHDP, - }, - } - - validFloat64Gauge = metricdata.Metrics{ - Name: "float64-gauge", - Description: "Gauge with float64 values", - Unit: "1", - Data: metricdata.Gauge[float64]{DataPoints: inputDP}, - } - - validFloat64Sum = metricdata.Metrics{ - Name: "float64-sum", - Description: "Sum with float64 values", - Unit: "1", - Data: metricdata.Sum[float64]{ - Temporality: metricdata.CumulativeTemporality, - IsMonotonic: false, - DataPoints: inputDP, - }, - } - - validFloat64Hist = metricdata.Metrics{ - Name: "float64-histogram", - Description: "Histogram", - Unit: "1", - Data: metricdata.Histogram[float64]{ - Temporality: metricdata.CumulativeTemporality, - DataPoints: inputHDP, - }, - } - - // Metrics Test Case - // - 3 invalid metrics and 3 Valid to test filtering - // - 1 invalid metric type - // - 2 invalid cummulative temporalities (only cummulative supported) - // - 3 types (Gauge, Counter, and Histogram) supported - inputMetrics = []metricdata.Metrics{ - validFloat64Gauge, - validFloat64Sum, - validFloat64Hist, - invalidSumTemporality, - invalidHistTemporality, - invalidSumAgg, - } - - expectedMetrics = []*mpb.Metric{ - { - Name: "float64-gauge", - Description: "Gauge with float64 values", - Unit: "1", - Data: &mpb.Metric_Gauge{Gauge: &mpb.Gauge{DataPoints: expectedDP}}, - }, - { - Name: "float64-sum", - Description: "Sum with float64 values", - Unit: "1", - Data: &mpb.Metric_Sum{Sum: &mpb.Sum{ - AggregationTemporality: mpb.AggregationTemporality_AGGREGATION_TEMPORALITY_CUMULATIVE, - IsMonotonic: false, - DataPoints: expectedDP, - }}, - }, - { - Name: "float64-histogram", - Description: "Histogram", - Unit: "1", - Data: &mpb.Metric_Histogram{Histogram: &mpb.Histogram{ - AggregationTemporality: mpb.AggregationTemporality_AGGREGATION_TEMPORALITY_CUMULATIVE, - DataPoints: expectedHDP, - }}, - }, - } - - // ScopeMetrics Test Cases - inputScopeMetrics = []metricdata.ScopeMetrics{{ - Scope: instrumentation.Scope{ - Name: "test/code/path", - Version: "v0.1.0", - }, - Metrics: inputMetrics, - }} - - expectedScopeMetrics = []*mpb.ScopeMetrics{{ - Scope: &cpb.InstrumentationScope{ - Name: "test/code/path", - Version: "v0.1.0", - }, - Metrics: expectedMetrics, - }} - - // ResourceMetrics Test Cases - inputResourceMetrics = &metricdata.ResourceMetrics{ - Resource: resource.NewSchemaless( - semconv.ServiceName("test server"), - semconv.ServiceVersion("v0.1.0"), - ), - ScopeMetrics: inputScopeMetrics, - } - - expectedResourceMetrics = &mpb.ResourceMetrics{ - Resource: &rpb.Resource{ - Attributes: []*cpb.KeyValue{ - { - Key: "service.name", - Value: &cpb.AnyValue{ - Value: &cpb.AnyValue_StringValue{StringValue: "test server"}, - }, - }, - { - Key: "service.version", - Value: &cpb.AnyValue{ - Value: &cpb.AnyValue_StringValue{StringValue: "v0.1.0"}, - }, - }, - }, - }, - ScopeMetrics: expectedScopeMetrics, - } -) - -// TestTransformOTLP runs tests from the "bottom-up" of the metricdata data types. -func TestTransformOTLP(t *testing.T) { - t.Parallel() - // Histogram DataPoint Test Case (Histograms) - assert.Equal(t, expectedHDP, histogramDataPointsToPB(inputHDP)) - - // Number DataPoint Test Case (Counters / Gauges) - require.Equal(t, expectedDP, dataPointsToPB(inputDP)) - - // MetricType Error Test Cases - _, err := metricTypeToPB(invalidHistTemporality) - require.Error(t, err) - require.ErrorIs(t, err, errTemporality) - - _, err = metricTypeToPB(invalidSumTemporality) - require.Error(t, err) - require.ErrorIs(t, err, errTemporality) - - _, err = metricTypeToPB(invalidSumAgg) - require.Error(t, err) - require.ErrorIs(t, err, errAggregaton) - - // Metrics Test Case - m := metricsToPB(inputMetrics) - require.Equal(t, expectedMetrics, m) - require.Equal(t, len(expectedMetrics), 3) - - // Scope Metrics Test Case - sm := scopeMetricsToPB(inputScopeMetrics) - require.Equal(t, expectedScopeMetrics, sm) - - // // Resource Metrics Test Case - rm := transformOTLP(inputResourceMetrics) - require.Equal(t, expectedResourceMetrics, rm) -} - -// TestTransformOTLP_CustomMetrics tests that a custom metric (hcp.otel.transform.failure) is emitted -// when transform fails. This test cannot be run in parallel as the metrics.NewGlobal() -// sets a shared global sink. -func TestTransformOTLP_CustomMetrics(t *testing.T) { - for name, tc := range map[string]struct { - inputRM *metricdata.ResourceMetrics - expectedMetricCount int - }{ - "successNoMetric": { - inputRM: &metricdata.ResourceMetrics{ - // 3 valid metrics. - ScopeMetrics: []metricdata.ScopeMetrics{ - { - Metrics: []metricdata.Metrics{ - validFloat64Gauge, - validFloat64Hist, - validFloat64Sum, - }, - }, - }, - }, - }, - "failureEmitsMetric": { - // inputScopeMetrics contains 3 bad metrics. - inputRM: inputResourceMetrics, - expectedMetricCount: 3, - }, - } { - tc := tc - t.Run(name, func(t *testing.T) { - // Init global sink. - serviceName := "test.transform" - cfg := metrics.DefaultConfig(serviceName) - cfg.EnableHostname = false - - sink := metrics.NewInmemSink(10*time.Second, 10*time.Second) - metrics.NewGlobal(cfg, sink) - - // Perform operation that emits metric. - transformOTLP(tc.inputRM) - - // Collect sink metrics. - intervals := sink.Data() - require.Len(t, intervals, 1) - key := serviceName + "." + strings.Join(internalMetricTransformFailure, ".") - sv := intervals[0].Counters[key] - - if tc.expectedMetricCount == 0 { - require.Empty(t, sv) - return - } - - // Verify count for transform failure metric. - require.NotNil(t, sv) - require.NotNil(t, sv.AggregateSample) - require.Equal(t, 3, sv.Count) - }) - } -} diff --git a/agent/hcp/telemetry_provider.go b/agent/hcp/telemetry_provider.go deleted file mode 100644 index a575d63f8a82..000000000000 --- a/agent/hcp/telemetry_provider.go +++ /dev/null @@ -1,369 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package hcp - -import ( - "context" - "errors" - "fmt" - "net/http" - "net/url" - "reflect" - "regexp" - "sync" - "time" - - "github.com/armon/go-metrics" - "github.com/go-openapi/runtime" - "github.com/hashicorp/go-hclog" - "github.com/hashicorp/go-retryablehttp" - - "github.com/hashicorp/consul/agent/hcp/client" - "github.com/hashicorp/consul/agent/hcp/config" - "github.com/hashicorp/consul/agent/hcp/telemetry" - "github.com/hashicorp/consul/version" -) - -var ( - // internalMetricRefreshFailure is a metric to monitor refresh failures. - internalMetricRefreshFailure []string = []string{"hcp", "telemetry_config_provider", "refresh", "failure"} - // internalMetricRefreshSuccess is a metric to monitor refresh successes. - internalMetricRefreshSuccess []string = []string{"hcp", "telemetry_config_provider", "refresh", "success"} - // defaultTelemetryConfigRefreshInterval is a default fallback in case the first HCP fetch fails. - defaultTelemetryConfigRefreshInterval = 1 * time.Minute -) - -// Ensure hcpProviderImpl implements telemetry provider interfaces. -var _ TelemetryProvider = &hcpProviderImpl{} -var _ telemetry.ConfigProvider = &hcpProviderImpl{} -var _ telemetry.EndpointProvider = &hcpProviderImpl{} -var _ client.MetricsClientProvider = &hcpProviderImpl{} - -// hcpProviderImpl holds telemetry configuration and settings for continuous fetch of new config from HCP. -// it updates configuration, if changes are detected. -type hcpProviderImpl struct { - // cfg holds configuration that can be dynamically updated. - cfg *dynamicConfig - // httpCfg holds configuration for the HTTP client - httpCfg *httpCfg - - // Reader-writer mutexes are used as the provider is read heavy. - // OTEL components access telemetryConfig during metrics collection and export (read). - // Meanwhile, configs are only updated when there are changes (write). - rw sync.RWMutex - httpCfgRW sync.RWMutex - - // running indicates if the HCP telemetry config provider has been started - running bool - - // stopCh is used to signal that the telemetry config provider should stop running. - stopCh chan struct{} - - // hcpClient is an authenticated client used to make HTTP requests to HCP. - hcpClient client.Client - - // logger is the HCP logger for the provider - logger hclog.Logger - - // testUpdateConfigCh is used by unit tests to signal when an update config has occurred - testUpdateConfigCh chan struct{} -} - -// dynamicConfig is a set of configurable settings for metrics collection, processing and export. -// fields MUST be exported to compute hash for equals method. -type dynamicConfig struct { - disabled bool - endpoint *url.URL - labels map[string]string - filters *regexp.Regexp - // refreshInterval controls the interval at which configuration is fetched from HCP to refresh config. - refreshInterval time.Duration -} - -// defaultDisabledCfg disables metric collection and contains default config values. -func defaultDisabledCfg() *dynamicConfig { - return &dynamicConfig{ - labels: map[string]string{}, - filters: client.DefaultMetricFilters, - refreshInterval: defaultTelemetryConfigRefreshInterval, - endpoint: nil, - disabled: true, - } -} - -// httpCfg is a set of configurable settings for the HTTP client used to export metrics -type httpCfg struct { - header *http.Header - client *retryablehttp.Client -} - -//go:generate mockery --name TelemetryProvider --with-expecter --inpackage -type TelemetryProvider interface { - Start(ctx context.Context, c *HCPProviderCfg) error - Stop() -} - -type HCPProviderCfg struct { - HCPClient client.Client - HCPConfig config.CloudConfigurer -} - -// NewHCPProvider initializes and starts a HCP Telemetry provider. -func NewHCPProvider(ctx context.Context) *hcpProviderImpl { - h := &hcpProviderImpl{ - // Initialize with default config values. - cfg: defaultDisabledCfg(), - httpCfg: &httpCfg{}, - logger: hclog.FromContext(ctx), - } - - return h -} - -// Start starts a process that continuously checks for updates to the telemetry configuration -// by making a request to HCP. It only starts running if it's not already running. -func (h *hcpProviderImpl) Start(ctx context.Context, c *HCPProviderCfg) error { - changed := h.setRunning(true) - if !changed { - // Provider is already running. - return nil - } - - // Update the provider with the HCP configurations - h.hcpClient = c.HCPClient - err := h.updateHTTPConfig(c.HCPConfig) - if err != nil { - return fmt.Errorf("failed to initialize HCP telemetry provider: %v", err) - } - - go h.run(ctx) - - return nil -} - -// run continuously checks for updates to the telemetry configuration by making a request to HCP. -func (h *hcpProviderImpl) run(ctx context.Context) error { - h.logger.Debug("starting telemetry config provider") - - // Try to initialize config once before starting periodic fetch. - h.updateConfig(ctx) - - ticker := time.NewTicker(h.getRefreshInterval()) - defer ticker.Stop() - for { - select { - case <-ticker.C: - if newRefreshInterval := h.updateConfig(ctx); newRefreshInterval > 0 { - ticker.Reset(newRefreshInterval) - } - case <-ctx.Done(): - return nil - case <-h.stopCh: - return nil - } - } -} - -// updateConfig makes a HTTP request to HCP to update metrics configuration held in the provider. -func (h *hcpProviderImpl) updateConfig(ctx context.Context) time.Duration { - logger := h.logger.Named("telemetry_config_provider") - - if h.testUpdateConfigCh != nil { - defer func() { - select { - case h.testUpdateConfigCh <- struct{}{}: - default: - } - }() - } - - if h.hcpClient == nil || reflect.ValueOf(h.hcpClient).IsNil() { - // Disable metrics if HCP client is not configured - disabledMetricsCfg := defaultDisabledCfg() - h.modifyDynamicCfg(disabledMetricsCfg) - return disabledMetricsCfg.refreshInterval - } - - ctx, cancel := context.WithTimeout(ctx, 5*time.Second) - defer cancel() - - logger.Trace("fetching telemetry config") - telemetryCfg, err := h.hcpClient.FetchTelemetryConfig(ctx) - if err != nil { - // Only disable metrics on 404 or 401 to handle the case of an unlinked cluster. - // For other errors such as 5XX ones, we continue metrics collection, as these are potentially transient server-side errors. - apiErr, ok := err.(*runtime.APIError) - if ok && apiErr.IsClientError() { - disabledMetricsCfg := defaultDisabledCfg() - h.modifyDynamicCfg(disabledMetricsCfg) - return disabledMetricsCfg.refreshInterval - } - - logger.Error("failed to fetch telemetry config from HCP", "error", err) - metrics.IncrCounter(internalMetricRefreshFailure, 1) - return 0 - } - logger.Trace("successfully fetched telemetry config") - - // newRefreshInterval of 0 or less can cause ticker Reset() panic. - newRefreshInterval := telemetryCfg.RefreshConfig.RefreshInterval - if newRefreshInterval <= 0 { - logger.Error("invalid refresh interval duration", "refreshInterval", newRefreshInterval) - metrics.IncrCounter(internalMetricRefreshFailure, 1) - return 0 - } - - newCfg := &dynamicConfig{ - filters: telemetryCfg.MetricsConfig.Filters, - endpoint: telemetryCfg.MetricsConfig.Endpoint, - labels: telemetryCfg.MetricsConfig.Labels, - refreshInterval: telemetryCfg.RefreshConfig.RefreshInterval, - disabled: telemetryCfg.MetricsConfig.Disabled, - } - - h.modifyDynamicCfg(newCfg) - - return newCfg.refreshInterval -} - -// modifyDynamicCfg acquires a write lock to update new configuration and emits a success metric. -func (h *hcpProviderImpl) modifyDynamicCfg(newCfg *dynamicConfig) { - h.rw.Lock() - h.cfg = newCfg - h.rw.Unlock() - - metrics.IncrCounter(internalMetricRefreshSuccess, 1) -} - -func (h *hcpProviderImpl) getRefreshInterval() time.Duration { - h.rw.RLock() - defer h.rw.RUnlock() - - return h.cfg.refreshInterval -} - -// GetEndpoint acquires a read lock to return endpoint configuration for consumers. -func (h *hcpProviderImpl) GetEndpoint() *url.URL { - h.rw.RLock() - defer h.rw.RUnlock() - - return h.cfg.endpoint -} - -// GetFilters acquires a read lock to return filters configuration for consumers. -func (h *hcpProviderImpl) GetFilters() *regexp.Regexp { - h.rw.RLock() - defer h.rw.RUnlock() - - return h.cfg.filters -} - -// GetLabels acquires a read lock to return labels configuration for consumers. -func (h *hcpProviderImpl) GetLabels() map[string]string { - h.rw.RLock() - defer h.rw.RUnlock() - - return h.cfg.labels -} - -// IsDisabled acquires a read lock and return true if metrics are enabled. -func (h *hcpProviderImpl) IsDisabled() bool { - h.rw.RLock() - defer h.rw.RUnlock() - - return h.cfg.disabled -} - -// updateHTTPConfig updates the HTTP configuration values that rely on the HCP configuration. -func (h *hcpProviderImpl) updateHTTPConfig(cfg config.CloudConfigurer) error { - h.httpCfgRW.Lock() - defer h.httpCfgRW.Unlock() - - if cfg == nil { - return errors.New("must provide valid HCP configuration") - } - - // Update headers - r, err := cfg.Resource() - if err != nil { - return fmt.Errorf("failed set telemetry client headers: %v", err) - } - header := make(http.Header) - header.Set("content-type", "application/x-protobuf") - header.Set("x-hcp-resource-id", r.String()) - header.Set("x-channel", fmt.Sprintf("consul/%s", version.GetHumanVersion())) - h.httpCfg.header = &header - - // Update HTTP client - hcpCfg, err := cfg.HCPConfig() - if err != nil { - return fmt.Errorf("failed to configure telemetry HTTP client: %v", err) - } - h.httpCfg.client = client.NewHTTPClient(hcpCfg.APITLSConfig(), hcpCfg) - - return nil -} - -// GetHeader acquires a read lock to return the HTTP request headers needed -// to export metrics. -func (h *hcpProviderImpl) GetHeader() http.Header { - h.httpCfgRW.RLock() - defer h.httpCfgRW.RUnlock() - - if h.httpCfg.header == nil { - return nil - } - - return h.httpCfg.header.Clone() -} - -// GetHTTPClient acquires a read lock to return the retryable HTTP client needed -// to export metrics. -func (h *hcpProviderImpl) GetHTTPClient() *retryablehttp.Client { - h.httpCfgRW.RLock() - defer h.httpCfgRW.RUnlock() - - return h.httpCfg.client -} - -// setRunning acquires a write lock to set whether the provider is running. -// If the given value is the same as the current running status, it returns -// false. If current status is updated to the given status, it returns true. -func (h *hcpProviderImpl) setRunning(r bool) bool { - h.rw.Lock() - defer h.rw.Unlock() - - if h.running == r { - return false - } - - // Initialize or close the stop channel depending what running status - // we're transitioning to. Channel must be initialized on start since - // a provider can be stopped and started multiple times. - if r { - h.stopCh = make(chan struct{}) - } else { - close(h.stopCh) - } - - h.running = r - - return true -} - -// Stop acquires a write lock to mark the provider as not running and sends a stop signal to the -// main run loop. It also updates the provider with a disabled configuration. -func (h *hcpProviderImpl) Stop() { - changed := h.setRunning(false) - if !changed { - h.logger.Trace("telemetry config provider already stopped") - return - } - - h.rw.Lock() - h.cfg = defaultDisabledCfg() - h.rw.Unlock() - - h.logger.Debug("telemetry config provider stopped") -} diff --git a/agent/hcp/telemetry_provider_test.go b/agent/hcp/telemetry_provider_test.go deleted file mode 100644 index 8a2c8acd4c65..000000000000 --- a/agent/hcp/telemetry_provider_test.go +++ /dev/null @@ -1,660 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package hcp - -import ( - "context" - "fmt" - "net/http" - "net/url" - "regexp" - "strings" - "sync" - "testing" - "time" - - "github.com/armon/go-metrics" - "github.com/go-openapi/runtime" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - - "github.com/hashicorp/consul/agent/hcp/client" - "github.com/hashicorp/consul/agent/hcp/config" - "github.com/hashicorp/consul/version" - "github.com/hashicorp/go-hclog" -) - -const ( - testRefreshInterval = 100 * time.Millisecond - testSinkServiceName = "test.telemetry_config_provider" - testRaceWriteSampleCount = 100 - testRaceReadSampleCount = 5000 -) - -var ( - // Test constants to verify inmem sink metrics. - testMetricKeyFailure = testSinkServiceName + "." + strings.Join(internalMetricRefreshFailure, ".") - testMetricKeySuccess = testSinkServiceName + "." + strings.Join(internalMetricRefreshSuccess, ".") -) - -type testConfig struct { - filters string - endpoint string - labels map[string]string - refreshInterval time.Duration - disabled bool -} - -func TestNewTelemetryConfigProvider_DefaultConfig(t *testing.T) { - t.Parallel() - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - // Initialize new provider - provider := NewHCPProvider(ctx) - provider.updateConfig(ctx) - - // Assert provider has default configuration and metrics processing is disabled. - defaultCfg := &dynamicConfig{ - labels: map[string]string{}, - filters: client.DefaultMetricFilters, - refreshInterval: defaultTelemetryConfigRefreshInterval, - endpoint: nil, - disabled: true, - } - require.Equal(t, defaultCfg, provider.cfg) -} - -func TestTelemetryConfigProvider_UpdateConfig(t *testing.T) { - for name, tc := range map[string]struct { - mockExpect func(*client.MockClient) - metricKey string - initCfg *dynamicConfig - expected *dynamicConfig - expectedInterval time.Duration - skipHCPClient bool - }{ - "noChanges": { - initCfg: testDynamicCfg(&testConfig{ - endpoint: "http://test.com/v1/metrics", - filters: "test", - labels: map[string]string{ - "test_label": "123", - }, - refreshInterval: testRefreshInterval, - }), - mockExpect: func(m *client.MockClient) { - mockCfg, _ := testTelemetryCfg(&testConfig{ - endpoint: "http://test.com/v1/metrics", - filters: "test", - labels: map[string]string{ - "test_label": "123", - }, - refreshInterval: testRefreshInterval, - }) - m.EXPECT().FetchTelemetryConfig(mock.Anything).Return(mockCfg, nil) - }, - expected: testDynamicCfg(&testConfig{ - endpoint: "http://test.com/v1/metrics", - labels: map[string]string{ - "test_label": "123", - }, - filters: "test", - refreshInterval: testRefreshInterval, - }), - metricKey: testMetricKeySuccess, - expectedInterval: testRefreshInterval, - }, - "newConfig": { - initCfg: testDynamicCfg(&testConfig{ - endpoint: "http://test.com/v1/metrics", - filters: "test", - labels: map[string]string{ - "test_label": "123", - }, - refreshInterval: 2 * time.Second, - }), - mockExpect: func(m *client.MockClient) { - mockCfg, _ := testTelemetryCfg(&testConfig{ - endpoint: "http://newendpoint/v1/metrics", - filters: "consul", - labels: map[string]string{ - "new_label": "1234", - }, - refreshInterval: 2 * time.Second, - }) - m.EXPECT().FetchTelemetryConfig(mock.Anything).Return(mockCfg, nil) - }, - expected: testDynamicCfg(&testConfig{ - endpoint: "http://newendpoint/v1/metrics", - filters: "consul", - labels: map[string]string{ - "new_label": "1234", - }, - refreshInterval: 2 * time.Second, - }), - expectedInterval: 2 * time.Second, - metricKey: testMetricKeySuccess, - }, - "newConfigMetricsDisabled": { - initCfg: testDynamicCfg(&testConfig{ - endpoint: "http://test.com/v1/metrics", - filters: "test", - labels: map[string]string{ - "test_label": "123", - }, - refreshInterval: 2 * time.Second, - }), - mockExpect: func(m *client.MockClient) { - mockCfg, _ := testTelemetryCfg(&testConfig{ - endpoint: "", - filters: "consul", - labels: map[string]string{ - "new_label": "1234", - }, - refreshInterval: 2 * time.Second, - disabled: true, - }) - m.EXPECT().FetchTelemetryConfig(mock.Anything).Return(mockCfg, nil) - }, - expected: testDynamicCfg(&testConfig{ - endpoint: "", - filters: "consul", - labels: map[string]string{ - "new_label": "1234", - }, - refreshInterval: 2 * time.Second, - disabled: true, - }), - metricKey: testMetricKeySuccess, - expectedInterval: 2 * time.Second, - }, - "sameConfigInvalidRefreshInterval": { - initCfg: testDynamicCfg(&testConfig{ - endpoint: "http://test.com/v1/metrics", - filters: "test", - labels: map[string]string{ - "test_label": "123", - }, - refreshInterval: testRefreshInterval, - }), - mockExpect: func(m *client.MockClient) { - mockCfg, _ := testTelemetryCfg(&testConfig{ - refreshInterval: 0 * time.Second, - }) - m.EXPECT().FetchTelemetryConfig(mock.Anything).Return(mockCfg, nil) - }, - expected: testDynamicCfg(&testConfig{ - endpoint: "http://test.com/v1/metrics", - labels: map[string]string{ - "test_label": "123", - }, - filters: "test", - refreshInterval: testRefreshInterval, - }), - metricKey: testMetricKeyFailure, - expectedInterval: 0, - }, - "sameConfigHCPClientFailure": { - initCfg: testDynamicCfg(&testConfig{ - endpoint: "http://test.com/v1/metrics", - filters: "test", - labels: map[string]string{ - "test_label": "123", - }, - refreshInterval: testRefreshInterval, - }), - mockExpect: func(m *client.MockClient) { - m.EXPECT().FetchTelemetryConfig(mock.Anything).Return(nil, fmt.Errorf("failure")) - }, - expected: testDynamicCfg(&testConfig{ - endpoint: "http://test.com/v1/metrics", - filters: "test", - labels: map[string]string{ - "test_label": "123", - }, - refreshInterval: testRefreshInterval, - }), - metricKey: testMetricKeyFailure, - expectedInterval: 0, - }, - "disableMetrics404": { - initCfg: testDynamicCfg(&testConfig{ - endpoint: "http://test.com/v1/metrics", - filters: "test", - labels: map[string]string{ - "test_label": "123", - }, - refreshInterval: testRefreshInterval, - }), - mockExpect: func(m *client.MockClient) { - err := runtime.NewAPIError("404 failure", nil, 404) - m.EXPECT().FetchTelemetryConfig(mock.Anything).Return(nil, err) - }, - expected: defaultDisabledCfg(), - metricKey: testMetricKeySuccess, - expectedInterval: defaultTelemetryConfigRefreshInterval, - }, - "hcpClientNotConfigured": { - skipHCPClient: true, - initCfg: testDynamicCfg(&testConfig{ - endpoint: "http://test.com/v1/metrics", - filters: "test", - labels: map[string]string{ - "test_label": "123", - }, - refreshInterval: testRefreshInterval, - }), - expected: defaultDisabledCfg(), - metricKey: testMetricKeySuccess, - expectedInterval: defaultTelemetryConfigRefreshInterval, - }, - } { - tc := tc - t.Run(name, func(t *testing.T) { - sink := initGlobalSink() - var mockClient *client.MockClient - if !tc.skipHCPClient { - mockClient = client.NewMockClient(t) - tc.mockExpect(mockClient) - } - - provider := &hcpProviderImpl{ - hcpClient: mockClient, - cfg: tc.initCfg, - logger: hclog.NewNullLogger(), - } - - newInterval := provider.updateConfig(context.Background()) - require.Equal(t, tc.expectedInterval, newInterval) - - // Verify endpoint provider returns correct config values. - require.Equal(t, tc.expected.endpoint, provider.GetEndpoint()) - require.Equal(t, tc.expected.filters, provider.GetFilters()) - require.Equal(t, tc.expected.labels, provider.GetLabels()) - require.Equal(t, tc.expected.disabled, provider.IsDisabled()) - - // Verify count for transform success metric. - interval := sink.Data()[0] - require.NotNil(t, interval, 1) - sv := interval.Counters[tc.metricKey] - assert.NotNil(t, sv.AggregateSample) - require.Equal(t, sv.Count, 1) - }) - } -} - -func TestTelemetryConfigProvider_Start(t *testing.T) { - t.Parallel() - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - provider := NewHCPProvider(ctx) - - testUpdateConfigCh := make(chan struct{}, 1) - provider.testUpdateConfigCh = testUpdateConfigCh - - // Configure mocks - mockClient := client.NewMockClient(t) - mTelemetryCfg, err := testTelemetryCfg(&testConfig{ - endpoint: "http://test.com/v1/metrics", - filters: "test", - labels: map[string]string{ - "test_label": "123", - }, - refreshInterval: testRefreshInterval, - }) - require.NoError(t, err) - mockClient.EXPECT().FetchTelemetryConfig(mock.Anything).Return(mTelemetryCfg, nil) - mockHCPCfg := &config.MockCloudCfg{} - - // Run provider - go provider.Start(context.Background(), &HCPProviderCfg{ - HCPClient: mockClient, - HCPConfig: mockHCPCfg, - }) - - // Expect at least two update config calls to validate provider is running - // and has entered the main run loop - select { - case <-testUpdateConfigCh: - case <-time.After(time.Second): - require.Fail(t, "provider did not attempt to update config in expected time") - } - select { - case <-testUpdateConfigCh: - case <-time.After(time.Millisecond * 500): - require.Fail(t, "provider did not attempt to update config in expected time") - } - - mockClient.AssertExpectations(t) -} - -func TestTelemetryConfigProvider_MultipleRun(t *testing.T) { - t.Parallel() - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - provider := NewHCPProvider(ctx) - - testUpdateConfigCh := make(chan struct{}, 1) - provider.testUpdateConfigCh = testUpdateConfigCh - - // Configure mocks - mockClient := client.NewMockClient(t) - mTelemetryCfg, err := testTelemetryCfg(&testConfig{ - refreshInterval: 30 * time.Minute, - }) - require.NoError(t, err) - mockClient.EXPECT().FetchTelemetryConfig(mock.Anything).Return(mTelemetryCfg, nil) - mockHCPCfg := &config.MockCloudCfg{} - - // Run provider twice in parallel - go provider.Start(context.Background(), &HCPProviderCfg{ - HCPClient: mockClient, - HCPConfig: mockHCPCfg, - }) - go provider.Start(context.Background(), &HCPProviderCfg{ - HCPClient: mockClient, - HCPConfig: mockHCPCfg, - }) - - // Expect only one update config call - select { - case <-testUpdateConfigCh: - case <-time.After(time.Second): - require.Fail(t, "provider did not attempt to update config in expected time") - } - - select { - case <-testUpdateConfigCh: - require.Fail(t, "provider unexpectedly updated config") - case <-time.After(time.Second): - } - - // Try calling run again, should not update again - provider.Start(context.Background(), &HCPProviderCfg{ - HCPClient: mockClient, - HCPConfig: mockHCPCfg, - }) - - select { - case <-testUpdateConfigCh: - require.Fail(t, "provider unexpectedly updated config") - case <-time.After(time.Second): - } - - mockClient.AssertExpectations(t) -} - -func TestTelemetryConfigProvider_updateHTTPConfig(t *testing.T) { - for name, test := range map[string]struct { - wantErr string - cfg config.CloudConfigurer - }{ - "success": { - cfg: &config.MockCloudCfg{}, - }, - "failsWithoutCloudCfg": { - wantErr: "must provide valid HCP configuration", - cfg: nil, - }, - "failsHCPConfig": { - wantErr: "failed to configure telemetry HTTP client", - cfg: config.MockCloudCfg{ - ConfigErr: fmt.Errorf("test bad hcp config"), - }, - }, - "failsBadResource": { - wantErr: "failed set telemetry client headers", - cfg: config.MockCloudCfg{ - ResourceErr: fmt.Errorf("test bad resource"), - }, - }, - } { - t.Run(name, func(t *testing.T) { - provider := NewHCPProvider(context.Background()) - err := provider.updateHTTPConfig(test.cfg) - - if test.wantErr != "" { - require.Error(t, err) - require.Contains(t, err.Error(), test.wantErr) - return - } - - require.NoError(t, err) - require.NotNil(t, provider.GetHTTPClient()) - - expectedHeader := make(http.Header) - expectedHeader.Set("content-type", "application/x-protobuf") - expectedHeader.Set("x-hcp-resource-id", "organization/test-org/project/test-project/test-type/test-id") - expectedHeader.Set("x-channel", fmt.Sprintf("consul/%s", version.GetHumanVersion())) - require.Equal(t, expectedHeader, provider.GetHeader()) - }) - } -} - -func TestTelemetryConfigProvider_Stop(t *testing.T) { - t.Parallel() - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - provider := NewHCPProvider(ctx) - - testUpdateConfigCh := make(chan struct{}, 1) - provider.testUpdateConfigCh = testUpdateConfigCh - - // Configure mocks - mockClient := client.NewMockClient(t) - mTelemetryCfg, err := testTelemetryCfg(&testConfig{ - endpoint: "http://test.com/v1/metrics", - filters: "test", - labels: map[string]string{ - "test_label": "123", - }, - refreshInterval: testRefreshInterval, - }) - require.NoError(t, err) - mockClient.EXPECT().FetchTelemetryConfig(mock.Anything).Return(mTelemetryCfg, nil) - mockHCPCfg := &config.MockCloudCfg{} - - // Run provider - provider.Start(context.Background(), &HCPProviderCfg{ - HCPClient: mockClient, - HCPConfig: mockHCPCfg, - }) - - // Wait for at least two update config calls to ensure provider is running - // and has entered the main run loop - select { - case <-testUpdateConfigCh: - case <-time.After(time.Second): - require.Fail(t, "provider did not attempt to update config in expected time") - } - select { - case <-testUpdateConfigCh: - case <-time.After(time.Millisecond * 500): - require.Fail(t, "provider did not attempt to update config in expected time") - } - - // Stop the provider - provider.Stop() - require.Equal(t, defaultDisabledCfg(), provider.cfg) - select { - case <-testUpdateConfigCh: - require.Fail(t, "provider should not attempt to update config after stop") - case <-time.After(time.Second): - // Success, no updates have happened after stopping - } - - mockClient.AssertExpectations(t) -} - -// mockRaceClient is a mock HCP client that fetches TelemetryConfig. -// The mock TelemetryConfig returned can be manually updated at any time. -// It manages concurrent read/write access to config with a sync.RWMutex. -type mockRaceClient struct { - client.Client - cfg *client.TelemetryConfig - rw sync.RWMutex -} - -// updateCfg acquires a write lock and updates client config to a new value givent a count. -func (m *mockRaceClient) updateCfg(count int) (*client.TelemetryConfig, error) { - m.rw.Lock() - defer m.rw.Unlock() - - labels := map[string]string{fmt.Sprintf("label_%d", count): fmt.Sprintf("value_%d", count)} - - filters, err := regexp.Compile(fmt.Sprintf("consul_filter_%d", count)) - if err != nil { - return nil, err - } - - endpoint, err := url.Parse(fmt.Sprintf("http://consul-endpoint-%d.com", count)) - if err != nil { - return nil, err - } - - cfg := &client.TelemetryConfig{ - MetricsConfig: &client.MetricsConfig{ - Filters: filters, - Endpoint: endpoint, - Labels: labels, - }, - RefreshConfig: &client.RefreshConfig{ - RefreshInterval: testRefreshInterval, - }, - } - m.cfg = cfg - - return cfg, nil -} - -// FetchTelemetryConfig returns the current config held by the mockRaceClient. -func (m *mockRaceClient) FetchTelemetryConfig(ctx context.Context) (*client.TelemetryConfig, error) { - m.rw.RLock() - defer m.rw.RUnlock() - - return m.cfg, nil -} - -func TestTelemetryConfigProvider_Race(t *testing.T) { - //todo(achooo): address flaky test - t.Skip("TODO(flaky): This test fails often in the CI") - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - initCfg, err := testTelemetryCfg(&testConfig{ - endpoint: "test.com", - filters: "test", - labels: map[string]string{"test_label": "test_value"}, - refreshInterval: testRefreshInterval, - }) - require.NoError(t, err) - - m := &mockRaceClient{ - cfg: initCfg, - } - - // Start the provider goroutine, which fetches client TelemetryConfig every RefreshInterval. - provider := NewHCPProvider(ctx) - err = provider.Start(context.Background(), &HCPProviderCfg{m, config.MockCloudCfg{}}) - require.NoError(t, err) - - for count := 0; count < testRaceWriteSampleCount; count++ { - // Force a TelemetryConfig value change in the mockRaceClient. - newCfg, err := m.updateCfg(count) - require.NoError(t, err) - // Force provider to obtain new client TelemetryConfig immediately. - // This call is necessary to guarantee TelemetryConfig changes to assert on expected values below. - provider.updateConfig(context.Background()) - - // Start goroutines to access label configuration. - wg := &sync.WaitGroup{} - kickOff(wg, testRaceReadSampleCount, provider, func(provider *hcpProviderImpl) { - require.Equal(t, provider.GetLabels(), newCfg.MetricsConfig.Labels) - }) - - // Start goroutines to access endpoint configuration. - kickOff(wg, testRaceReadSampleCount, provider, func(provider *hcpProviderImpl) { - require.Equal(t, provider.GetFilters(), newCfg.MetricsConfig.Filters) - }) - - // Start goroutines to access filter configuration. - kickOff(wg, testRaceReadSampleCount, provider, func(provider *hcpProviderImpl) { - require.Equal(t, provider.GetEndpoint(), newCfg.MetricsConfig.Endpoint) - }) - - wg.Wait() - } -} - -func kickOff(wg *sync.WaitGroup, count int, provider *hcpProviderImpl, check func(cfgProvider *hcpProviderImpl)) { - for i := 0; i < count; i++ { - wg.Add(1) - go func() { - defer wg.Done() - check(provider) - }() - } -} - -// initGlobalSink is a helper function to initialize a Go metrics inmemsink. -func initGlobalSink() *metrics.InmemSink { - cfg := metrics.DefaultConfig(testSinkServiceName) - cfg.EnableHostname = false - - sink := metrics.NewInmemSink(10*time.Second, 10*time.Second) - metrics.NewGlobal(cfg, sink) - - return sink -} - -// testDynamicCfg converts testConfig inputs to a dynamicConfig to be used in tests. -func testDynamicCfg(testCfg *testConfig) *dynamicConfig { - filters, _ := regexp.Compile(testCfg.filters) - - var endpoint *url.URL - if testCfg.endpoint != "" { - endpoint, _ = url.Parse(testCfg.endpoint) - } - return &dynamicConfig{ - endpoint: endpoint, - filters: filters, - labels: testCfg.labels, - refreshInterval: testCfg.refreshInterval, - disabled: testCfg.disabled, - } -} - -// testTelemetryCfg converts testConfig inputs to a TelemetryConfig to be used in tests. -func testTelemetryCfg(testCfg *testConfig) (*client.TelemetryConfig, error) { - filters, err := regexp.Compile(testCfg.filters) - if err != nil { - return nil, err - } - - var endpoint *url.URL - if testCfg.endpoint != "" { - u, err := url.Parse(testCfg.endpoint) - if err != nil { - return nil, err - } - endpoint = u - } - - return &client.TelemetryConfig{ - MetricsConfig: &client.MetricsConfig{ - Endpoint: endpoint, - Filters: filters, - Labels: testCfg.labels, - Disabled: testCfg.disabled, - }, - RefreshConfig: &client.RefreshConfig{ - RefreshInterval: testCfg.refreshInterval, - }, - }, nil -} diff --git a/agent/hcp/testing.go b/agent/hcp/testing.go deleted file mode 100644 index c4f7770f1a73..000000000000 --- a/agent/hcp/testing.go +++ /dev/null @@ -1,182 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package hcp - -import ( - "encoding/json" - "fmt" - "log" - "net/http" - "regexp" - "strings" - "sync" - "time" - - hcpgnm "github.com/hashicorp/hcp-sdk-go/clients/cloud-global-network-manager-service/preview/2022-02-15/client/global_network_manager_service" - gnmmod "github.com/hashicorp/hcp-sdk-go/clients/cloud-global-network-manager-service/preview/2022-02-15/models" - "github.com/hashicorp/hcp-sdk-go/resource" -) - -type TestEndpoint struct { - Methods []string - PathSuffix string - Handler func(r *http.Request, cluster resource.Resource) (interface{}, error) -} - -type MockHCPServer struct { - mu sync.Mutex - handlers map[string]TestEndpoint - - servers map[string]*gnmmod.HashicorpCloudGlobalNetworkManager20220215Server -} - -var basePathRe = regexp.MustCompile("/global-network-manager/[^/]+/organizations/([^/]+)/projects/([^/]+)/clusters/([^/]+)/([^/]+.*)") - -func NewMockHCPServer() *MockHCPServer { - s := &MockHCPServer{ - handlers: make(map[string]TestEndpoint), - servers: make(map[string]*gnmmod.HashicorpCloudGlobalNetworkManager20220215Server), - } - // Define endpoints in this package - s.AddEndpoint(TestEndpoint{ - Methods: []string{"POST"}, - PathSuffix: "agent/server-state", - Handler: s.handleStatus, - }) - s.AddEndpoint(TestEndpoint{ - Methods: []string{"POST"}, - PathSuffix: "agent/discover", - Handler: s.handleDiscover, - }) - return s -} - -// AddEndpoint allows adding additional endpoints from other packages e.g. -// bootstrap (which can't be merged into one package due to dependency cycles). -// It's not safe to call this concurrently with any other call to AddEndpoint or -// ServeHTTP. -func (s *MockHCPServer) AddEndpoint(e TestEndpoint) { - s.handlers[e.PathSuffix] = e -} - -func (s *MockHCPServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { - s.mu.Lock() - defer s.mu.Unlock() - - if r.URL.Path == "/oauth2/token" { - mockTokenResponse(w) - return - } - - matches := basePathRe.FindStringSubmatch(r.URL.Path) - if len(matches) < 5 { - w.WriteHeader(404) - log.Printf("ERROR 404: %s %s\n", r.Method, r.URL.Path) - return - } - - cluster := resource.Resource{ - ID: matches[3], - Type: "cluster", - Organization: matches[1], - Project: matches[2], - } - found := false - var resp interface{} - var err error - for _, e := range s.handlers { - if e.PathSuffix == matches[4] { - found = true - if !enforceMethod(w, r, e.Methods) { - return - } - resp, err = e.Handler(r, cluster) - break - } - } - if !found { - w.WriteHeader(404) - log.Printf("ERROR 404: %s %s\n", r.Method, r.URL.Path) - return - } - if err != nil { - errResponse(w, err) - return - } - - if resp == nil { - // no response body - log.Printf("OK 204: %s %s\n", r.Method, r.URL.Path) - w.WriteHeader(http.StatusNoContent) - return - } - - bs, err := json.MarshalIndent(resp, "", " ") - if err != nil { - errResponse(w, err) - return - } - - log.Printf("OK 200: %s %s\n", r.Method, r.URL.Path) - w.Header().Set("content-type", "application/json; charset=utf-8") - w.WriteHeader(http.StatusOK) - w.Write(bs) -} - -func enforceMethod(w http.ResponseWriter, r *http.Request, methods []string) bool { - for _, m := range methods { - if strings.EqualFold(r.Method, m) { - return true - } - } - // No match, sent 4xx - w.WriteHeader(http.StatusMethodNotAllowed) - log.Printf("ERROR 405: bad method (not in %v): %s %s\n", methods, r.Method, r.URL.Path) - return false -} - -func mockTokenResponse(w http.ResponseWriter) { - w.Header().Set("Content-Type", "application/json; charset=utf-8") - w.WriteHeader(http.StatusOK) - - w.Write([]byte(`{"access_token": "token", "token_type": "Bearer"}`)) -} - -func (s *MockHCPServer) handleStatus(r *http.Request, cluster resource.Resource) (interface{}, error) { - var req hcpgnm.AgentPushServerStateBody - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - return nil, err - } - log.Printf("STATUS UPDATE: server=%s version=%s leader=%v hasLeader=%v healthy=%v tlsCertExpiryDays=%1.0f", - req.ServerState.Name, - req.ServerState.Version, - req.ServerState.Raft.IsLeader, - req.ServerState.Raft.KnownLeader, - req.ServerState.Autopilot.Healthy, - time.Until(time.Time(req.ServerState.ServerTLS.InternalRPC.CertExpiry)).Hours()/24, - ) - s.servers[req.ServerState.Name] = &gnmmod.HashicorpCloudGlobalNetworkManager20220215Server{ - GossipPort: req.ServerState.GossipPort, - ID: req.ServerState.ID, - LanAddress: req.ServerState.LanAddress, - Name: req.ServerState.Name, - RPCPort: req.ServerState.RPCPort, - } - return "{}", nil -} - -func (s *MockHCPServer) handleDiscover(r *http.Request, cluster resource.Resource) (interface{}, error) { - servers := make([]*gnmmod.HashicorpCloudGlobalNetworkManager20220215Server, len(s.servers)) - for _, server := range s.servers { - servers = append(servers, server) - } - - return gnmmod.HashicorpCloudGlobalNetworkManager20220215AgentDiscoverResponse{Servers: servers}, nil -} - -func errResponse(w http.ResponseWriter, err error) { - log.Printf("ERROR 500: %s\n", err) - w.WriteHeader(500) - fmt.Fprintf(w, `{"error": %q}`, err.Error()) -} diff --git a/agent/hcp/testserver/main.go b/agent/hcp/testserver/main.go deleted file mode 100644 index e0db7670ef99..000000000000 --- a/agent/hcp/testserver/main.go +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package main - -import ( - "flag" - "fmt" - "log" - "net/http" - "os" - "os/signal" - "syscall" - - "github.com/hashicorp/consul/agent/hcp" - "github.com/hashicorp/consul/agent/hcp/bootstrap" -) - -var port int - -func main() { - flag.IntVar(&port, "port", 9999, "port to listen on") - flag.Parse() - - s := hcp.NewMockHCPServer() - s.AddEndpoint(bootstrap.TestEndpoint()) - - sigs := make(chan os.Signal, 1) - signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) - - addr := fmt.Sprintf("127.0.0.1:%d", port) - srv := http.Server{ - Addr: addr, - Handler: s, - } - - log.Printf("Listening on %s\n", addr) - - go func() { - if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { - log.Fatalf("listen: %s\n", err) - } - }() - - <-sigs - log.Println("Shutting down HTTP server") - srv.Close() -} diff --git a/agent/retry_join.go b/agent/retry_join.go index eb010c0c22c4..15f36aa5babe 100644 --- a/agent/retry_join.go +++ b/agent/retry_join.go @@ -8,8 +8,7 @@ import ( "strings" "time" - discoverhcp "github.com/hashicorp/consul/agent/hcp/discover" - discover "github.com/hashicorp/go-discover" + "github.com/hashicorp/go-discover" discoverk8s "github.com/hashicorp/go-discover/provider/k8s" "github.com/hashicorp/go-hclog" @@ -116,7 +115,6 @@ func newDiscover() (*discover.Discover, error) { providers[k] = v } providers["k8s"] = &discoverk8s.Provider{} - providers["hcp"] = &discoverhcp.Provider{} return discover.New( discover.WithUserAgent(lib.UserAgent()), diff --git a/agent/retry_join_test.go b/agent/retry_join_test.go index 4184ab0a9f3d..5a4b95d533e0 100644 --- a/agent/retry_join_test.go +++ b/agent/retry_join_test.go @@ -15,7 +15,7 @@ func TestAgentRetryNewDiscover(t *testing.T) { d, err := newDiscover() require.NoError(t, err) expected := []string{ - "aliyun", "aws", "azure", "digitalocean", "gce", "hcp", "k8s", "linode", + "aliyun", "aws", "azure", "digitalocean", "gce", "k8s", "linode", "mdns", "os", "packet", "scaleway", "softlayer", "tencentcloud", "triton", "vsphere", } diff --git a/agent/setup.go b/agent/setup.go index ce64aa7eb2df..6bfa0ba387d9 100644 --- a/agent/setup.go +++ b/agent/setup.go @@ -33,7 +33,6 @@ import ( "github.com/hashicorp/consul/agent/grpc-internal/balancer" "github.com/hashicorp/consul/agent/grpc-internal/resolver" grpcWare "github.com/hashicorp/consul/agent/grpc-middleware" - "github.com/hashicorp/consul/agent/hcp" "github.com/hashicorp/consul/agent/leafcert" "github.com/hashicorp/consul/agent/local" "github.com/hashicorp/consul/agent/pool" @@ -137,18 +136,6 @@ func NewBaseDeps(configLoader ConfigLoader, logOut io.Writer, providedLogger hcl cfg.Telemetry.PrometheusOpts.SummaryDefinitions = summaries var extraSinks []metrics.MetricSink - if cfg.IsCloudEnabled() { - // This values is set late within newNodeIDFromConfig above - cfg.Cloud.NodeID = cfg.NodeID - - d.HCP, err = hcp.NewDeps(cfg.Cloud, d.Logger.Named("hcp"), cfg.DataDir) - if err != nil { - return d, err - } - if d.HCP.Sink != nil { - extraSinks = append(extraSinks, d.HCP.Sink) - } - } d.MetricsConfig, err = lib.InitTelemetry(cfg.Telemetry, d.Logger, extraSinks...) if err != nil { @@ -273,9 +260,6 @@ func (bd BaseDeps) Close() { bd.AutoConfig.Stop() bd.LeafCertManager.Stop() bd.MetricsConfig.Cancel() - if bd.HCP.Sink != nil { - bd.HCP.Sink.Shutdown() - } for _, fn := range []func(){bd.deregisterBalancer, bd.deregisterResolver, bd.stopHostCollector} { if fn != nil { diff --git a/agent/uiserver/ui_template_data.go b/agent/uiserver/ui_template_data.go index 726207b148f0..b7d56ddcf7c1 100644 --- a/agent/uiserver/ui_template_data.go +++ b/agent/uiserver/ui_template_data.go @@ -22,7 +22,6 @@ func uiTemplateDataFromConfig(cfg *config.RuntimeConfig) (map[string]interface{} // browser. "metrics_proxy_enabled": cfg.UIConfig.MetricsProxy.BaseURL != "", "dashboard_url_templates": cfg.UIConfig.DashboardURLTemplates, - "hcp_enabled": cfg.UIConfig.HCPEnabled, } // Only set this if there is some actual JSON or we'll cause a JSON @@ -34,7 +33,6 @@ func uiTemplateDataFromConfig(cfg *config.RuntimeConfig) (map[string]interface{} d := map[string]interface{}{ "ContentPath": cfg.UIConfig.ContentPath, "ACLsEnabled": cfg.ACLsEnabled, - "HCPEnabled": cfg.UIConfig.HCPEnabled, "UIConfig": uiCfg, "LocalDatacenter": cfg.Datacenter, "PrimaryDatacenter": cfg.PrimaryDatacenter, diff --git a/agent/uiserver/uiserver_test.go b/agent/uiserver/uiserver_test.go index 5934564a0ed9..fa0dc9d9c150 100644 --- a/agent/uiserver/uiserver_test.go +++ b/agent/uiserver/uiserver_test.go @@ -42,13 +42,11 @@ func TestUIServerIndex(t *testing.T) { wantContains: []string{"