From 35e8ef4956b92edbab66ad2f0a61f124ab7c047e Mon Sep 17 00:00:00 2001 From: Ganeshrockz Date: Mon, 19 Jun 2023 23:54:19 +0530 Subject: [PATCH 1/8] Added config-file flag to accept file based input params --- cmd/consul-dataplane/config.go | 294 +++++++++++++++++ cmd/consul-dataplane/config_test.go | 468 ++++++++++++++++++++++++++++ cmd/consul-dataplane/main.go | 279 +++++------------ cmd/consul-dataplane/merge.go | 269 ++++++++++++++++ pkg/consuldp/config.go | 124 ++++---- 5 files changed, 1168 insertions(+), 266 deletions(-) create mode 100644 cmd/consul-dataplane/config.go create mode 100644 cmd/consul-dataplane/config_test.go create mode 100644 cmd/consul-dataplane/merge.go diff --git a/cmd/consul-dataplane/config.go b/cmd/consul-dataplane/config.go new file mode 100644 index 00000000..a30a0911 --- /dev/null +++ b/cmd/consul-dataplane/config.go @@ -0,0 +1,294 @@ +package main + +import ( + "encoding/json" + "flag" + "os" + "strings" + "time" + + "github.com/hashicorp/consul-dataplane/pkg/consuldp" +) + +type FlagOpts struct { + printVersion bool + + addresses string + grpcPort int + serverWatchDisabled bool + + tlsDisabled bool + tlsCACertsPath string + tlsServerName string + tlsCertFile string + tlsKeyFile string + tlsInsecureSkipVerify bool + + logLevel string + logJSON bool + + nodeName string + nodeID string + serviceID string + serviceIDPath string + namespace string + partition string + + credentialType string + token string + loginAuthMethod string + loginNamespace string + loginPartition string + loginDatacenter string + loginBearerToken string + loginBearerTokenPath string + loginMeta map[string]string + + useCentralTelemetryConfig bool + + promRetentionTime time.Duration + promCACertsPath string + promKeyFile string + promCertFile string + promServiceMetricsURL string + promScrapePath string + promMergePort int + + adminBindAddr string + adminBindPort int + readyBindAddr string + readyBindPort int + envoyConcurrency int + envoyDrainTimeSeconds int + envoyDrainStrategy string + + xdsBindAddr string + xdsBindPort int + + consulDNSBindAddr string + consulDNSPort int + + shutdownDrainListenersEnabled bool + shutdownGracePeriodSeconds int + gracefulShutdownPath string + gracefulPort int + + dumpEnvoyConfigOnExitEnabled bool + + configFile string +} + +const ( + DefaultGRPCPort = 8502 + DefaultServerWatchDisabled = false + + DefaultTLSDisabled = false + DefaultTLSInsecureSkipVerify = false + + DefaultDNSBindAddr = "127.0.0.1" + DefaultDNSBindPort = -1 + + DefaultXDSBindAddr = "127.0.0.1" + DefaultXDSBindPort = 0 + + DefaultLogLevel = "info" + DefaultLogJSON = false + + DefaultEnvoyAdminBindAddr = "127.0.0.1" + DefaultEnvoyAdminBindPort = 19000 + DefaultEnvoyReadyBindPort = 0 + DefaultEnvoyConcurrency = 2 + DefaultEnvoyDrainTimeSeconds = 30 + DefaultEnvoyDrainStrategy = "immediate" + DefaultEnvoyShutdownDrainListenersEnabled = false + DefaultEnvoyShutdownGracePeriodSeconds = 0 + DefaultGracefulShutdownPath = "/graceful_shutdown" + DefaultGracefulPort = 20300 + DefaultDumpEnvoyConfigOnExitEnabled = false + + DefaultUseCentralTelemetryConfig = true + DefaultPromRetentionTime = 60 * time.Second + DefaultPromScrapePath = "/metrics" + DefaultPromMergePort = 20100 +) + +// buildDataplaneConfig builds the necessary config needed for the +// dataplane to start. We begin with the default version of the dataplane +// config(with the default values) followed by merging the file based +// config generated from the `-config-file` input into it. +// Since values given via CLI flags take the most precedence, we finally +// merge the config generated from the flags into the previously +// generated/merged config +func (f *FlagOpts) buildDataplaneConfig() (*consuldp.Config, error) { + var consuldpConfig, consuldpCfgFromFlags *consuldp.Config + + consuldpConfig = buildDefaultConsulDPConfig() + consuldpCfgFromFlags = f.buildConfig() + + if f.configFile != "" { + consuldpCfgFromFile, err := f.buildConfigFromFile() + if err != nil { + return nil, err + } + + mergeConfigs(consuldpConfig, consuldpCfgFromFile) + } + + mergeConfigs(consuldpConfig, consuldpCfgFromFlags) + + return consuldpConfig, nil +} + +// Constructs a config based on the values present in the config json file +func (f *FlagOpts) buildConfigFromFile() (*consuldp.Config, error) { + var cfg *consuldp.Config + data, err := os.ReadFile(f.configFile) + if err != nil { + return nil, err + } + + err = json.Unmarshal(data, &cfg) + if err != nil { + return nil, err + } + + return cfg, nil +} + +// Constructs a config based on the values given via the CLI flags +func (f *FlagOpts) buildConfig() *consuldp.Config { + return &consuldp.Config{ + Consul: &consuldp.ConsulConfig{ + Addresses: f.addresses, + GRPCPort: f.grpcPort, + Credentials: &consuldp.CredentialsConfig{ + Type: consuldp.CredentialsType(f.credentialType), + Static: consuldp.StaticCredentialsConfig{ + Token: f.token, + }, + Login: consuldp.LoginCredentialsConfig{ + AuthMethod: f.loginAuthMethod, + Namespace: f.loginNamespace, + Partition: f.loginPartition, + Datacenter: f.loginDatacenter, + BearerToken: f.loginBearerToken, + BearerTokenPath: f.loginBearerTokenPath, + Meta: f.loginMeta, + }, + }, + ServerWatchDisabled: f.serverWatchDisabled, + TLS: &consuldp.TLSConfig{ + Disabled: f.tlsDisabled, + CACertsPath: f.tlsCACertsPath, + ServerName: f.tlsServerName, + CertFile: f.tlsCertFile, + KeyFile: f.tlsKeyFile, + InsecureSkipVerify: f.tlsInsecureSkipVerify, + }, + }, + Service: &consuldp.ServiceConfig{ + NodeName: f.nodeName, + NodeID: f.nodeID, + ServiceID: f.serviceID, + Namespace: f.namespace, + Partition: f.partition, + }, + Logging: &consuldp.LoggingConfig{ + Name: "consul-dataplane", + LogLevel: strings.ToUpper(f.logLevel), + LogJSON: f.logJSON, + }, + Telemetry: &consuldp.TelemetryConfig{ + UseCentralConfig: f.useCentralTelemetryConfig, + Prometheus: consuldp.PrometheusTelemetryConfig{ + RetentionTime: f.promRetentionTime, + CACertsPath: f.promCACertsPath, + KeyFile: f.promKeyFile, + CertFile: f.promCertFile, + ServiceMetricsURL: f.promServiceMetricsURL, + ScrapePath: f.promScrapePath, + MergePort: f.promMergePort, + }, + }, + Envoy: &consuldp.EnvoyConfig{ + AdminBindAddress: f.adminBindAddr, + AdminBindPort: f.adminBindPort, + ReadyBindAddress: f.readyBindAddr, + ReadyBindPort: f.readyBindPort, + EnvoyConcurrency: f.envoyConcurrency, + EnvoyDrainTimeSeconds: f.envoyDrainTimeSeconds, + EnvoyDrainStrategy: f.envoyDrainStrategy, + ShutdownDrainListenersEnabled: f.shutdownDrainListenersEnabled, + ShutdownGracePeriodSeconds: f.shutdownGracePeriodSeconds, + GracefulShutdownPath: f.gracefulShutdownPath, + GracefulPort: f.gracefulPort, + DumpEnvoyConfigOnExitEnabled: f.dumpEnvoyConfigOnExitEnabled, + ExtraArgs: flag.Args(), + }, + XDSServer: &consuldp.XDSServer{ + BindAddress: f.xdsBindAddr, + BindPort: f.xdsBindPort, + }, + DNSServer: &consuldp.DNSServerConfig{ + BindAddr: f.consulDNSBindAddr, + Port: f.consulDNSPort, + }, + } +} + +// Constructs a config with the default values +func buildDefaultConsulDPConfig() *consuldp.Config { + return &consuldp.Config{ + Consul: &consuldp.ConsulConfig{ + GRPCPort: DefaultGRPCPort, + Credentials: &consuldp.CredentialsConfig{ + Type: consuldp.CredentialsType(""), + Static: consuldp.StaticCredentialsConfig{}, + Login: consuldp.LoginCredentialsConfig{ + Meta: map[string]string{}, + }, + }, + ServerWatchDisabled: DefaultServerWatchDisabled, + TLS: &consuldp.TLSConfig{ + Disabled: DefaultTLSDisabled, + InsecureSkipVerify: DefaultTLSInsecureSkipVerify, + }, + }, + Service: &consuldp.ServiceConfig{}, + Logging: &consuldp.LoggingConfig{ + Name: "consul-dataplane", + LogLevel: strings.ToUpper(DefaultLogLevel), + LogJSON: DefaultLogJSON, + }, + Telemetry: &consuldp.TelemetryConfig{ + UseCentralConfig: DefaultUseCentralTelemetryConfig, + Prometheus: consuldp.PrometheusTelemetryConfig{ + RetentionTime: DefaultPromRetentionTime, + ScrapePath: DefaultPromScrapePath, + MergePort: DefaultPromMergePort, + }, + }, + Envoy: &consuldp.EnvoyConfig{ + AdminBindAddress: DefaultEnvoyAdminBindAddr, + AdminBindPort: DefaultEnvoyAdminBindPort, + ReadyBindPort: DefaultEnvoyReadyBindPort, + EnvoyConcurrency: DefaultEnvoyConcurrency, + EnvoyDrainTimeSeconds: DefaultEnvoyDrainTimeSeconds, + EnvoyDrainStrategy: DefaultEnvoyDrainStrategy, + ShutdownDrainListenersEnabled: DefaultEnvoyShutdownDrainListenersEnabled, + ShutdownGracePeriodSeconds: DefaultEnvoyShutdownGracePeriodSeconds, + GracefulShutdownPath: DefaultGracefulShutdownPath, + GracefulPort: DefaultGracefulPort, + DumpEnvoyConfigOnExitEnabled: DefaultDumpEnvoyConfigOnExitEnabled, + ExtraArgs: []string{}, + }, + XDSServer: &consuldp.XDSServer{ + BindAddress: DefaultXDSBindAddr, + BindPort: DefaultXDSBindPort, + }, + DNSServer: &consuldp.DNSServerConfig{ + BindAddr: DefaultDNSBindAddr, + Port: DefaultDNSBindPort, + }, + } +} diff --git a/cmd/consul-dataplane/config_test.go b/cmd/consul-dataplane/config_test.go new file mode 100644 index 00000000..20d9814c --- /dev/null +++ b/cmd/consul-dataplane/config_test.go @@ -0,0 +1,468 @@ +package main + +import ( + "fmt" + "math/rand" + "os" + "reflect" + "testing" + "time" + + "github.com/hashicorp/consul-dataplane/pkg/consuldp" + "github.com/stretchr/testify/require" +) + +func TestConfigGeneration(t *testing.T) { + type testCase struct { + desc string + flagOpts func() *FlagOpts + writeConfigFile func() error + assertConfig func(cfg *consuldp.Config, f *FlagOpts) bool + cleanup func() + wantErr bool + } + + testCases := []testCase{ + { + desc: "able to generate config properly when the config file input is empty", + flagOpts: func() *FlagOpts { + return generateFlagOpts() + }, + assertConfig: func(cfg *consuldp.Config, flagOpts *FlagOpts) bool { + expectedCfg := &consuldp.Config{ + Consul: &consuldp.ConsulConfig{ + Addresses: flagOpts.addresses, + GRPCPort: flagOpts.grpcPort, + ServerWatchDisabled: true, + Credentials: &consuldp.CredentialsConfig{ + Type: "static", + Static: consuldp.StaticCredentialsConfig{ + Token: "test-token-123", + }, + Login: consuldp.LoginCredentialsConfig{ + Meta: make(map[string]string), + AuthMethod: "test-iam-auth", + BearerToken: "bearer-login", + }, + }, + TLS: &consuldp.TLSConfig{ + Disabled: false, + CACertsPath: "/consul/", + CertFile: "ca-cert.pem", + KeyFile: "key.pem", + ServerName: "tls-server-name", + InsecureSkipVerify: true, + }, + }, + Service: &consuldp.ServiceConfig{ + NodeName: "test-node-dc1", + NodeID: "dc1.node.id", + Namespace: "default", + ServiceID: "node1.service1", + Partition: "default", + }, + Logging: &consuldp.LoggingConfig{ + Name: "consul-dataplane", + LogJSON: true, + LogLevel: "WARN", + }, + DNSServer: &consuldp.DNSServerConfig{ + BindAddr: "127.0.0.1", + Port: 8604, + }, + XDSServer: &consuldp.XDSServer{ + BindAddress: "127.0.1.0", + BindPort: 0, + }, + Envoy: &consuldp.EnvoyConfig{ + AdminBindAddress: "127.0.1.0", + AdminBindPort: 18000, + ReadyBindAddress: "127.0.1.0", + ReadyBindPort: 18003, + EnvoyConcurrency: 4, + EnvoyDrainStrategy: "test-strategy", + ShutdownDrainListenersEnabled: true, + GracefulShutdownPath: "/graceful_shutdown", + EnvoyDrainTimeSeconds: 30, + GracefulPort: 20300, + ExtraArgs: []string{}, + }, + Telemetry: &consuldp.TelemetryConfig{ + UseCentralConfig: true, + Prometheus: consuldp.PrometheusTelemetryConfig{ + RetentionTime: 10 * time.Second, + ScrapePath: "/metrics", + MergePort: 12000, + CACertsPath: "/consul/", + CertFile: "prom-ca-cert.pem", + KeyFile: "prom-key.pem", + }, + }, + } + + return reflect.DeepEqual(cfg, expectedCfg) + }, + wantErr: false, + }, + { + desc: "able to override all the config fields with CLI flags", + flagOpts: func() *FlagOpts { + opts := generateFlagOpts() + opts.loginBearerTokenPath = "/consul/bearertokenpath/" + opts.loginDatacenter = "dc100" + opts.loginMeta = map[string]string{ + "key-1": "value-1", + "key-2": "value-2", + } + opts.loginNamespace = "default" + opts.loginPartition = "default" + + opts.logJSON = false + opts.consulDNSBindAddr = "127.0.0.2" + opts.xdsBindPort = 6060 + opts.dumpEnvoyConfigOnExitEnabled = true + return opts + }, + assertConfig: func(cfg *consuldp.Config, flagOpts *FlagOpts) bool { + expectedCfg := &consuldp.Config{ + Consul: &consuldp.ConsulConfig{ + Addresses: flagOpts.addresses, + GRPCPort: flagOpts.grpcPort, + ServerWatchDisabled: true, + Credentials: &consuldp.CredentialsConfig{ + Type: "static", + Static: consuldp.StaticCredentialsConfig{ + Token: "test-token-123", + }, + Login: consuldp.LoginCredentialsConfig{ + Meta: map[string]string{ + "key-1": "value-1", + "key-2": "value-2", + }, + AuthMethod: "test-iam-auth", + BearerToken: "bearer-login", + BearerTokenPath: "/consul/bearertokenpath/", + Namespace: "default", + Partition: "default", + Datacenter: "dc100", + }, + }, + TLS: &consuldp.TLSConfig{ + Disabled: false, + CACertsPath: "/consul/", + CertFile: "ca-cert.pem", + KeyFile: "key.pem", + ServerName: "tls-server-name", + InsecureSkipVerify: true, + }, + }, + Service: &consuldp.ServiceConfig{ + NodeName: "test-node-dc1", + NodeID: "dc1.node.id", + Namespace: "default", + ServiceID: "node1.service1", + Partition: "default", + }, + Logging: &consuldp.LoggingConfig{ + Name: "consul-dataplane", + LogJSON: false, + LogLevel: "WARN", + }, + DNSServer: &consuldp.DNSServerConfig{ + BindAddr: "127.0.0.2", + Port: 8604, + }, + XDSServer: &consuldp.XDSServer{ + BindAddress: "127.0.1.0", + BindPort: 6060, + }, + Envoy: &consuldp.EnvoyConfig{ + AdminBindAddress: "127.0.1.0", + AdminBindPort: 18000, + ReadyBindAddress: "127.0.1.0", + ReadyBindPort: 18003, + EnvoyConcurrency: 4, + EnvoyDrainStrategy: "test-strategy", + ShutdownDrainListenersEnabled: true, + GracefulShutdownPath: "/graceful_shutdown", + EnvoyDrainTimeSeconds: 30, + GracefulPort: 20300, + DumpEnvoyConfigOnExitEnabled: true, + ExtraArgs: []string{}, + }, + Telemetry: &consuldp.TelemetryConfig{ + UseCentralConfig: true, + Prometheus: consuldp.PrometheusTelemetryConfig{ + RetentionTime: 10 * time.Second, + ScrapePath: "/metrics", + MergePort: 12000, + CACertsPath: "/consul/", + CertFile: "prom-ca-cert.pem", + KeyFile: "prom-key.pem", + }, + }, + } + + return reflect.DeepEqual(cfg, expectedCfg) + }, + wantErr: false, + }, + { + desc: "able to generate config properly when config file is given without flag inputs", + flagOpts: func() *FlagOpts { + opts := &FlagOpts{} + opts.configFile = "test.json" + return opts + }, + writeConfigFile: func() error { + inputJson := `{ + "consul": { + "addresses": "consul_server.dc1", + "grpcPort": 8502, + "serverWatchDisabled": false + }, + "service": { + "nodeName": "test-node-1", + "serviceId": "frontend-service-sidecar-proxy", + "namespace": "default", + "partition": "default" + }, + "envoy": { + "adminBindAddress": "127.0.0.1", + "adminBindPort": 19000 + }, + "logging": { + "logLevel": "info", + "logJSON": false + } + }` + + err := os.WriteFile("test.json", []byte(inputJson), 0600) + if err != nil { + return err + } + return nil + }, + assertConfig: func(cfg *consuldp.Config, flagOpts *FlagOpts) bool { + expectedCfg := buildDefaultConsulDPConfig() + expectedCfg.Consul.Addresses = "consul_server.dc1" + expectedCfg.Consul.GRPCPort = 8502 + expectedCfg.Consul.ServerWatchDisabled = false + expectedCfg.Service.NodeName = "test-node-1" + expectedCfg.Service.ServiceID = "frontend-service-sidecar-proxy" + expectedCfg.Service.Namespace = "default" + expectedCfg.Service.Partition = "default" + expectedCfg.Envoy.AdminBindAddress = "127.0.0.1" + expectedCfg.Envoy.AdminBindPort = 19000 + expectedCfg.Logging.LogJSON = false + expectedCfg.Logging.LogLevel = "INFO" + expectedCfg.Telemetry.UseCentralConfig = false + + return reflect.DeepEqual(cfg.Telemetry, expectedCfg.Telemetry) + }, + cleanup: func() { + os.Remove("test.json") + }, + wantErr: false, + }, + { + desc: "test whether CLI flag values override the file values", + flagOpts: func() *FlagOpts { + opts := generateFlagOpts() + opts.configFile = "test.json" + + opts.logLevel = "info" + opts.logJSON = false + + return opts + }, + writeConfigFile: func() error { + inputJson := `{ + "consul": { + "addresses": "consul_server.dc1", + "grpcPort": 8502, + "serverWatchDisabled": false + }, + "service": { + "nodeName": "test-node-1", + "serviceId": "frontend-service-sidecar-proxy", + "namespace": "default", + "partition": "default" + }, + "envoy": { + "adminBindAddress": "127.0.0.1", + "adminBindPort": 19000 + }, + "logging": { + "logLevel": "warn", + "logJSON": true + } + }` + + err := os.WriteFile("test.json", []byte(inputJson), 0600) + if err != nil { + return err + } + return nil + }, + assertConfig: func(cfg *consuldp.Config, flagOpts *FlagOpts) bool { + expectedCfg := &consuldp.Config{ + Consul: &consuldp.ConsulConfig{ + Addresses: flagOpts.addresses, + GRPCPort: flagOpts.grpcPort, + ServerWatchDisabled: true, + Credentials: &consuldp.CredentialsConfig{ + Type: "static", + Static: consuldp.StaticCredentialsConfig{ + Token: "test-token-123", + }, + Login: consuldp.LoginCredentialsConfig{ + Meta: map[string]string{ + "key-1": "value-1", + "key-2": "value-2", + }, + AuthMethod: "test-iam-auth", + BearerToken: "bearer-login", + BearerTokenPath: "/consul/bearertokenpath/", + Namespace: "default", + Partition: "default", + Datacenter: "dc100", + }, + }, + TLS: &consuldp.TLSConfig{ + Disabled: false, + CACertsPath: "/consul/", + CertFile: "ca-cert.pem", + KeyFile: "key.pem", + ServerName: "tls-server-name", + InsecureSkipVerify: true, + }, + }, + Service: &consuldp.ServiceConfig{ + NodeName: "test-node-dc1", + NodeID: "dc1.node.id", + Namespace: "default", + ServiceID: "node1.service1", + Partition: "default", + }, + Logging: &consuldp.LoggingConfig{ + Name: "consul-dataplane", + LogJSON: true, + LogLevel: "INFO", + }, + DNSServer: &consuldp.DNSServerConfig{ + BindAddr: "127.0.0.2", + Port: 8604, + }, + XDSServer: &consuldp.XDSServer{ + BindAddress: "127.0.1.0", + BindPort: 6060, + }, + Envoy: &consuldp.EnvoyConfig{ + AdminBindAddress: "127.0.1.0", + AdminBindPort: 18000, + ReadyBindAddress: "127.0.1.0", + ReadyBindPort: 18003, + EnvoyConcurrency: 4, + EnvoyDrainStrategy: "test-strategy", + ShutdownDrainListenersEnabled: true, + GracefulShutdownPath: "/graceful_shutdown", + EnvoyDrainTimeSeconds: 30, + GracefulPort: 20300, + DumpEnvoyConfigOnExitEnabled: true, + ExtraArgs: []string{}, + }, + Telemetry: &consuldp.TelemetryConfig{ + UseCentralConfig: true, + Prometheus: consuldp.PrometheusTelemetryConfig{ + RetentionTime: 10 * time.Second, + ScrapePath: "/metrics", + MergePort: 12000, + CACertsPath: "/consul/", + CertFile: "prom-ca-cert.pem", + KeyFile: "prom-key.pem", + }, + }, + } + + return reflect.DeepEqual(cfg.Telemetry, expectedCfg.Telemetry) + }, + cleanup: func() { + os.Remove("test.json") + }, + wantErr: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + opts := tc.flagOpts() + + if tc.writeConfigFile != nil { + require.NoError(t, tc.writeConfigFile()) + } + + cfg, err := opts.buildDataplaneConfig() + + if tc.wantErr { + require.Error(t, err) + } else { + require.NoError(t, err) + require.True(t, tc.assertConfig(cfg, opts)) + } + + if tc.cleanup != nil { + tc.cleanup() + } + }) + } +} + +func generateFlagOpts() *FlagOpts { + return &FlagOpts{ + addresses: fmt.Sprintf("consul.address.server_%d", rand.Int()), + grpcPort: rand.Int(), + serverWatchDisabled: true, + + tlsDisabled: false, + tlsCACertsPath: "/consul/", + tlsCertFile: "ca-cert.pem", + tlsKeyFile: "key.pem", + tlsServerName: "tls-server-name", + tlsInsecureSkipVerify: true, + + logLevel: "warn", + logJSON: true, + + nodeName: "test-node-dc1", + nodeID: "dc1.node.id", + namespace: "default", + serviceID: "node1.service1", + serviceIDPath: "/consul/service-id", + partition: "default", + + credentialType: "static", + token: "test-token-123", + loginAuthMethod: "test-iam-auth", + loginBearerToken: "bearer-login", + + adminBindAddr: "127.0.1.0", + adminBindPort: 18000, + readyBindAddr: "127.0.1.0", + readyBindPort: 18003, + envoyConcurrency: 4, + envoyDrainStrategy: "test-strategy", + shutdownDrainListenersEnabled: true, + + xdsBindAddr: "127.0.1.0", + consulDNSPort: 8604, + + promMergePort: 12000, + + useCentralTelemetryConfig: true, + promRetentionTime: 10 * time.Second, + promCACertsPath: "/consul/", + promCertFile: "prom-ca-cert.pem", + promKeyFile: "prom-key.pem", + } +} diff --git a/cmd/consul-dataplane/main.go b/cmd/consul-dataplane/main.go index bfcb5b4f..1de7bb1e 100644 --- a/cmd/consul-dataplane/main.go +++ b/cmd/consul-dataplane/main.go @@ -12,171 +12,115 @@ import ( "os/signal" "strings" "syscall" - "time" "github.com/hashicorp/consul-dataplane/pkg/consuldp" "github.com/hashicorp/consul-dataplane/pkg/version" ) var ( - printVersion bool - - addresses string - grpcPort int - serverWatchDisabled bool - - tlsDisabled bool - tlsCACertsPath string - tlsServerName string - tlsCertFile string - tlsKeyFile string - tlsInsecureSkipVerify bool - - logLevel string - logJSON bool - - nodeName string - nodeID string - serviceID string - serviceIDPath string - namespace string - partition string - - credentialType string - token string - loginAuthMethod string - loginNamespace string - loginPartition string - loginDatacenter string - loginBearerToken string - loginBearerTokenPath string - loginMeta map[string]string - - useCentralTelemetryConfig bool - - promRetentionTime time.Duration - promCACertsPath string - promKeyFile string - promCertFile string - promServiceMetricsURL string - promScrapePath string - promMergePort int - - adminBindAddr string - adminBindPort int - readyBindAddr string - readyBindPort int - envoyConcurrency int - envoyDrainTimeSeconds int - envoyDrainStrategy string - - xdsBindAddr string - xdsBindPort int - - consulDNSBindAddr string - consulDNSPort int - - shutdownDrainListenersEnabled bool - shutdownGracePeriodSeconds int - gracefulShutdownPath string - gracefulPort int - - dumpEnvoyConfigOnExitEnabled bool + flagOpts *FlagOpts ) func init() { - flag.BoolVar(&printVersion, "version", false, "Prints the current version of consul-dataplane.") + flagOpts = &FlagOpts{} + flag.BoolVar(&flagOpts.printVersion, "version", false, "Prints the current version of consul-dataplane.") - StringVar(&addresses, "addresses", "", "DP_CONSUL_ADDRESSES", "Consul server gRPC addresses. Value can be:\n"+ + StringVar(&flagOpts.addresses, "addresses", "", "DP_CONSUL_ADDRESSES", "Consul server gRPC addresses. Value can be:\n"+ "1. A DNS name that resolves to server addresses or the DNS name of a load balancer in front of the Consul servers; OR\n"+ "2. An executable command in the format, 'exec='. The executable\n"+ " a) on success - should exit 0 and print to stdout whitespace delimited IP (v4/v6) addresses\n"+ " b) on failure - exit with a non-zero code and optionally print an error message of up to 1024 bytes to stderr.\n"+ " Refer to https://github.com/hashicorp/go-netaddrs#summary for more details and examples.\n") - IntVar(&grpcPort, "grpc-port", 8502, "DP_CONSUL_GRPC_PORT", "The Consul server gRPC port to which consul-dataplane connects.") + IntVar(&flagOpts.grpcPort, "grpc-port", DefaultGRPCPort, "DP_CONSUL_GRPC_PORT", "The Consul server gRPC port to which consul-dataplane connects.") - BoolVar(&serverWatchDisabled, "server-watch-disabled", false, "DP_SERVER_WATCH_DISABLED", "Setting this prevents consul-dataplane from consuming the server update stream. This is useful for situations where Consul servers are behind a load balancer.") + BoolVar(&flagOpts.serverWatchDisabled, "server-watch-disabled", DefaultServerWatchDisabled, "DP_SERVER_WATCH_DISABLED", "Setting this prevents consul-dataplane from consuming the server update stream. This is useful for situations where Consul servers are behind a load balancer.") - StringVar(&logLevel, "log-level", "info", "DP_LOG_LEVEL", "Log level of the messages to print. "+ + StringVar(&flagOpts.logLevel, "log-level", DefaultLogLevel, "DP_LOG_LEVEL", "Log level of the messages to print. "+ "Available log levels are \"trace\", \"debug\", \"info\", \"warn\", and \"error\".") - BoolVar(&logJSON, "log-json", false, "DP_LOG_JSON", "Enables log messages in JSON format.") - - StringVar(&nodeName, "service-node-name", "", "DP_SERVICE_NODE_NAME", "The name of the Consul node to which the proxy service instance is registered.") - StringVar(&nodeID, "service-node-id", "", "DP_SERVICE_NODE_ID", "The ID of the Consul node to which the proxy service instance is registered.") - StringVar(&serviceID, "proxy-service-id", "", "DP_PROXY_SERVICE_ID", "The proxy service instance's ID.") - StringVar(&serviceIDPath, "proxy-service-id-path", "", "DP_PROXY_SERVICE_ID_PATH", "The path to a file containing the proxy service instance's ID.") - StringVar(&namespace, "service-namespace", "", "DP_SERVICE_NAMESPACE", "The Consul Enterprise namespace in which the proxy service instance is registered.") - StringVar(&partition, "service-partition", "", "DP_SERVICE_PARTITION", "The Consul Enterprise partition in which the proxy service instance is registered.") - - StringVar(&credentialType, "credential-type", "", "DP_CREDENTIAL_TYPE", "The type of credentials, either static or login, used to authenticate with Consul servers.") - StringVar(&token, "static-token", "", "DP_CREDENTIAL_STATIC_TOKEN", "The ACL token used to authenticate requests to Consul servers when -credential-type is set to static.") - StringVar(&loginAuthMethod, "login-auth-method", "", "DP_CREDENTIAL_LOGIN_AUTH_METHOD", "The auth method used to log in.") - StringVar(&loginNamespace, "login-namespace", "", "DP_CREDENTIAL_LOGIN_NAMESPACE", "The Consul Enterprise namespace containing the auth method.") - StringVar(&loginPartition, "login-partition", "", "DP_CREDENTIAL_LOGIN_PARTITION", "The Consul Enterprise partition containing the auth method.") - StringVar(&loginDatacenter, "login-datacenter", "", "DP_CREDENTIAL_LOGIN_DATACENTER", "The datacenter containing the auth method.") - StringVar(&loginBearerToken, "login-bearer-token", "", "DP_CREDENTIAL_LOGIN_BEARER_TOKEN", "The bearer token presented to the auth method.") - StringVar(&loginBearerTokenPath, "login-bearer-token-path", "", "DP_CREDENTIAL_LOGIN_BEARER_TOKEN_PATH", "The path to a file containing the bearer token presented to the auth method.") - MapVar((*FlagMapValue)(&loginMeta), "login-meta", "DP_CREDENTIAL_LOGIN_META", `A set of key/value pairs to attach to the ACL token. Each pair is formatted as "=". This flag may be passed multiple times.`) - - BoolVar(&useCentralTelemetryConfig, "telemetry-use-central-config", true, "DP_TELEMETRY_USE_CENTRAL_CONFIG", "Controls whether the proxy applies the central telemetry configuration.") - - DurationVar(&promRetentionTime, "telemetry-prom-retention-time", 60*time.Second, "DP_TELEMETRY_PROM_RETENTION_TIME", "The duration for prometheus metrics aggregation.") - StringVar(&promCACertsPath, "telemetry-prom-ca-certs-path", "", "DP_TELEMETRY_PROM_CA_CERTS_PATH", "The path to a file or directory containing CA certificates used to verify the Prometheus server's certificate.") - StringVar(&promKeyFile, "telemetry-prom-key-file", "", "DP_TELEMETRY_PROM_KEY_FILE", "The path to the client private key used to serve Prometheus metrics.") - StringVar(&promCertFile, "telemetry-prom-cert-file", "", "DP_TELEMETRY_PROM_CERT_FILE", "The path to the client certificate used to serve Prometheus metrics.") - StringVar(&promServiceMetricsURL, "telemetry-prom-service-metrics-url", "", "DP_TELEMETRY_PROM_SERVICE_METRICS_URL", "Prometheus metrics at this URL are scraped and included in Consul Dataplane's main Prometheus metrics.") - StringVar(&promScrapePath, "telemetry-prom-scrape-path", "/metrics", "DP_TELEMETRY_PROM_SCRAPE_PATH", "The URL path where Envoy serves Prometheus metrics.") - IntVar(&promMergePort, "telemetry-prom-merge-port", 20100, "DP_TELEMETRY_PROM_MERGE_PORT", "The port to serve merged Prometheus metrics.") - - StringVar(&adminBindAddr, "envoy-admin-bind-address", "127.0.0.1", "DP_ENVOY_ADMIN_BIND_ADDRESS", "The address on which the Envoy admin server is available.") - IntVar(&adminBindPort, "envoy-admin-bind-port", 19000, "DP_ENVOY_ADMIN_BIND_PORT", "The port on which the Envoy admin server is available.") - StringVar(&readyBindAddr, "envoy-ready-bind-address", "", "DP_ENVOY_READY_BIND_ADDRESS", "The address on which Envoy's readiness probe is available.") - IntVar(&readyBindPort, "envoy-ready-bind-port", 0, "DP_ENVOY_READY_BIND_PORT", "The port on which Envoy's readiness probe is available.") - IntVar(&envoyConcurrency, "envoy-concurrency", 2, "DP_ENVOY_CONCURRENCY", "The number of worker threads that Envoy uses.") - IntVar(&envoyDrainTimeSeconds, "envoy-drain-time-seconds", 30, "DP_ENVOY_DRAIN_TIME", "The time in seconds for which Envoy will drain connections.") - StringVar(&envoyDrainStrategy, "envoy-drain-strategy", "immediate", "DP_ENVOY_DRAIN_STRATEGY", "The behaviour of Envoy during the drain sequence. Determines whether all open connections should be encouraged to drain immediately or to increase the percentage gradually as the drain time elapses.") - - StringVar(&xdsBindAddr, "xds-bind-addr", "127.0.0.1", "DP_XDS_BIND_ADDR", "The address on which the Envoy xDS server is available.") - IntVar(&xdsBindPort, "xds-bind-port", 0, "DP_XDS_BIND_PORT", "The port on which the Envoy xDS server is available.") - - BoolVar(&tlsDisabled, "tls-disabled", false, "DP_TLS_DISABLED", "Communicate with Consul servers over a plaintext connection. Useful for testing, but not recommended for production.") - StringVar(&tlsCACertsPath, "ca-certs", "", "DP_CA_CERTS", "The path to a file or directory containing CA certificates used to verify the server's certificate.") - StringVar(&tlsCertFile, "tls-cert", "", "DP_TLS_CERT", "The path to a client certificate file. This is required if tls.grpc.verify_incoming is enabled on the server.") - StringVar(&tlsKeyFile, "tls-key", "", "DP_TLS_KEY", "The path to a client private key file. This is required if tls.grpc.verify_incoming is enabled on the server.") - StringVar(&tlsServerName, "tls-server-name", "", "DP_TLS_SERVER_NAME", "The hostname to expect in the server certificate's subject. This is required if -addresses is not a DNS name.") - BoolVar(&tlsInsecureSkipVerify, "tls-insecure-skip-verify", false, "DP_TLS_INSECURE_SKIP_VERIFY", "Do not verify the server's certificate. Useful for testing, but not recommended for production.") - - StringVar(&consulDNSBindAddr, "consul-dns-bind-addr", "127.0.0.1", "DP_CONSUL_DNS_BIND_ADDR", "The address that will be bound to the consul dns proxy.") - IntVar(&consulDNSPort, "consul-dns-bind-port", -1, "DP_CONSUL_DNS_BIND_PORT", "The port the consul dns proxy will listen on. By default -1 disables the dns proxy") + BoolVar(&flagOpts.logJSON, "log-json", DefaultLogJSON, "DP_LOG_JSON", "Enables log messages in JSON format.") + + StringVar(&flagOpts.nodeName, "service-node-name", "", "DP_SERVICE_NODE_NAME", "The name of the Consul node to which the proxy service instance is registered.") + StringVar(&flagOpts.nodeID, "service-node-id", "", "DP_SERVICE_NODE_ID", "The ID of the Consul node to which the proxy service instance is registered.") + StringVar(&flagOpts.serviceID, "proxy-service-id", "", "DP_PROXY_SERVICE_ID", "The proxy service instance's ID.") + StringVar(&flagOpts.serviceIDPath, "proxy-service-id-path", "", "DP_PROXY_SERVICE_ID_PATH", "The path to a file containing the proxy service instance's ID.") + StringVar(&flagOpts.namespace, "service-namespace", "", "DP_SERVICE_NAMESPACE", "The Consul Enterprise namespace in which the proxy service instance is registered.") + StringVar(&flagOpts.partition, "service-partition", "", "DP_SERVICE_PARTITION", "The Consul Enterprise partition in which the proxy service instance is registered.") + + StringVar(&flagOpts.credentialType, "credential-type", "", "DP_CREDENTIAL_TYPE", "The type of credentials, either static or login, used to authenticate with Consul servers.") + StringVar(&flagOpts.token, "static-token", "", "DP_CREDENTIAL_STATIC_TOKEN", "The ACL token used to authenticate requests to Consul servers when -credential-type is set to static.") + StringVar(&flagOpts.loginAuthMethod, "login-auth-method", "", "DP_CREDENTIAL_LOGIN_AUTH_METHOD", "The auth method used to log in.") + StringVar(&flagOpts.loginNamespace, "login-namespace", "", "DP_CREDENTIAL_LOGIN_NAMESPACE", "The Consul Enterprise namespace containing the auth method.") + StringVar(&flagOpts.loginPartition, "login-partition", "", "DP_CREDENTIAL_LOGIN_PARTITION", "The Consul Enterprise partition containing the auth method.") + StringVar(&flagOpts.loginDatacenter, "login-datacenter", "", "DP_CREDENTIAL_LOGIN_DATACENTER", "The datacenter containing the auth method.") + StringVar(&flagOpts.loginBearerToken, "login-bearer-token", "", "DP_CREDENTIAL_LOGIN_BEARER_TOKEN", "The bearer token presented to the auth method.") + StringVar(&flagOpts.loginBearerTokenPath, "login-bearer-token-path", "", "DP_CREDENTIAL_LOGIN_BEARER_TOKEN_PATH", "The path to a file containing the bearer token presented to the auth method.") + MapVar((*FlagMapValue)(&flagOpts.loginMeta), "login-meta", "DP_CREDENTIAL_LOGIN_META", `A set of key/value pairs to attach to the ACL token. Each pair is formatted as "=". This flag may be passed multiple times.`) + + BoolVar(&flagOpts.useCentralTelemetryConfig, "telemetry-use-central-config", DefaultUseCentralTelemetryConfig, "DP_TELEMETRY_USE_CENTRAL_CONFIG", "Controls whether the proxy applies the central telemetry configuration.") + + DurationVar(&flagOpts.promRetentionTime, "telemetry-prom-retention-time", DefaultPromRetentionTime, "DP_TELEMETRY_PROM_RETENTION_TIME", "The duration for prometheus metrics aggregation.") + StringVar(&flagOpts.promCACertsPath, "telemetry-prom-ca-certs-path", "", "DP_TELEMETRY_PROM_CA_CERTS_PATH", "The path to a file or directory containing CA certificates used to verify the Prometheus server's certificate.") + StringVar(&flagOpts.promKeyFile, "telemetry-prom-key-file", "", "DP_TELEMETRY_PROM_KEY_FILE", "The path to the client private key used to serve Prometheus metrics.") + StringVar(&flagOpts.promCertFile, "telemetry-prom-cert-file", "", "DP_TELEMETRY_PROM_CERT_FILE", "The path to the client certificate used to serve Prometheus metrics.") + StringVar(&flagOpts.promServiceMetricsURL, "telemetry-prom-service-metrics-url", "", "DP_TELEMETRY_PROM_SERVICE_METRICS_URL", "Prometheus metrics at this URL are scraped and included in Consul Dataplane's main Prometheus metrics.") + StringVar(&flagOpts.promScrapePath, "telemetry-prom-scrape-path", DefaultPromScrapePath, "DP_TELEMETRY_PROM_SCRAPE_PATH", "The URL path where Envoy serves Prometheus metrics.") + IntVar(&flagOpts.promMergePort, "telemetry-prom-merge-port", DefaultPromMergePort, "DP_TELEMETRY_PROM_MERGE_PORT", "The port to serve merged Prometheus metrics.") + + StringVar(&flagOpts.adminBindAddr, "envoy-admin-bind-address", DefaultEnvoyAdminBindAddr, "DP_ENVOY_ADMIN_BIND_ADDRESS", "The address on which the Envoy admin server is available.") + IntVar(&flagOpts.adminBindPort, "envoy-admin-bind-port", DefaultEnvoyAdminBindPort, "DP_ENVOY_ADMIN_BIND_PORT", "The port on which the Envoy admin server is available.") + StringVar(&flagOpts.readyBindAddr, "envoy-ready-bind-address", "", "DP_ENVOY_READY_BIND_ADDRESS", "The address on which Envoy's readiness probe is available.") + IntVar(&flagOpts.readyBindPort, "envoy-ready-bind-port", DefaultEnvoyReadyBindPort, "DP_ENVOY_READY_BIND_PORT", "The port on which Envoy's readiness probe is available.") + IntVar(&flagOpts.envoyConcurrency, "envoy-concurrency", DefaultEnvoyConcurrency, "DP_ENVOY_CONCURRENCY", "The number of worker threads that Envoy uses.") + IntVar(&flagOpts.envoyDrainTimeSeconds, "envoy-drain-time-seconds", DefaultEnvoyDrainTimeSeconds, "DP_ENVOY_DRAIN_TIME", "The time in seconds for which Envoy will drain connections.") + StringVar(&flagOpts.envoyDrainStrategy, "envoy-drain-strategy", DefaultEnvoyDrainStrategy, "DP_ENVOY_DRAIN_STRATEGY", "The behaviour of Envoy during the drain sequence. Determines whether all open connections should be encouraged to drain immediately or to increase the percentage gradually as the drain time elapses.") + + StringVar(&flagOpts.xdsBindAddr, "xds-bind-addr", DefaultXDSBindAddr, "DP_XDS_BIND_ADDR", "The address on which the Envoy xDS server is available.") + IntVar(&flagOpts.xdsBindPort, "xds-bind-port", DefaultXDSBindPort, "DP_XDS_BIND_PORT", "The port on which the Envoy xDS server is available.") + + BoolVar(&flagOpts.tlsDisabled, "tls-disabled", DefaultTLSDisabled, "DP_TLS_DISABLED", "Communicate with Consul servers over a plaintext connection. Useful for testing, but not recommended for production.") + StringVar(&flagOpts.tlsCACertsPath, "ca-certs", "", "DP_CA_CERTS", "The path to a file or directory containing CA certificates used to verify the server's certificate.") + StringVar(&flagOpts.tlsCertFile, "tls-cert", "", "DP_TLS_CERT", "The path to a client certificate file. This is required if tls.grpc.verify_incoming is enabled on the server.") + StringVar(&flagOpts.tlsKeyFile, "tls-key", "", "DP_TLS_KEY", "The path to a client private key file. This is required if tls.grpc.verify_incoming is enabled on the server.") + StringVar(&flagOpts.tlsServerName, "tls-server-name", "", "DP_TLS_SERVER_NAME", "The hostname to expect in the server certificate's subject. This is required if -addresses is not a DNS name.") + BoolVar(&flagOpts.tlsInsecureSkipVerify, "tls-insecure-skip-verify", DefaultTLSInsecureSkipVerify, "DP_TLS_INSECURE_SKIP_VERIFY", "Do not verify the server's certificate. Useful for testing, but not recommended for production.") + + StringVar(&flagOpts.consulDNSBindAddr, "consul-dns-bind-addr", DefaultDNSBindAddr, "DP_CONSUL_DNS_BIND_ADDR", "The address that will be bound to the consul dns proxy.") + IntVar(&flagOpts.consulDNSPort, "consul-dns-bind-port", DefaultDNSBindPort, "DP_CONSUL_DNS_BIND_PORT", "The port the consul dns proxy will listen on. By default -1 disables the dns proxy") // Default is false because it will generally be configured appropriately by Helm // configuration or pod annotation. - BoolVar(&shutdownDrainListenersEnabled, "shutdown-drain-listeners", false, "DP_SHUTDOWN_DRAIN_LISTENERS", "Wait for proxy listeners to drain before terminating the proxy container.") + BoolVar(&flagOpts.shutdownDrainListenersEnabled, "shutdown-drain-listeners", DefaultEnvoyShutdownDrainListenersEnabled, "DP_SHUTDOWN_DRAIN_LISTENERS", "Wait for proxy listeners to drain before terminating the proxy container.") // Default is 0 because it will generally be configured appropriately by Helm // configuration or pod annotation. - IntVar(&shutdownGracePeriodSeconds, "shutdown-grace-period-seconds", 0, "DP_SHUTDOWN_GRACE_PERIOD_SECONDS", "Amount of time to wait after receiving a SIGTERM signal before terminating the proxy.") - StringVar(&gracefulShutdownPath, "graceful-shutdown-path", "/graceful_shutdown", "DP_GRACEFUL_SHUTDOWN_PATH", "An HTTP path to serve the graceful shutdown endpoint.") - IntVar(&gracefulPort, "graceful-port", 20300, "DP_GRACEFUL_PORT", "A port to serve HTTP endpoints for graceful shutdown.") + IntVar(&flagOpts.shutdownGracePeriodSeconds, "shutdown-grace-period-seconds", DefaultEnvoyShutdownGracePeriodSeconds, "DP_SHUTDOWN_GRACE_PERIOD_SECONDS", "Amount of time to wait after receiving a SIGTERM signal before terminating the proxy.") + StringVar(&flagOpts.gracefulShutdownPath, "graceful-shutdown-path", DefaultGracefulShutdownPath, "DP_GRACEFUL_SHUTDOWN_PATH", "An HTTP path to serve the graceful shutdown endpoint.") + IntVar(&flagOpts.gracefulPort, "graceful-port", DefaultGracefulPort, "DP_GRACEFUL_PORT", "A port to serve HTTP endpoints for graceful shutdown.") // Default is false, may be useful for debugging unexpected termination. - BoolVar(&dumpEnvoyConfigOnExitEnabled, "dump-envoy-config-on-exit", false, "DP_DUMP_ENVOY_CONFIG_ON_EXIT", "Call the Envoy /config_dump endpoint during consul-dataplane controlled shutdown.") + BoolVar(&flagOpts.dumpEnvoyConfigOnExitEnabled, "dump-envoy-config-on-exit", DefaultDumpEnvoyConfigOnExitEnabled, "DP_DUMP_ENVOY_CONFIG_ON_EXIT", "Call the Envoy /config_dump endpoint during consul-dataplane controlled shutdown.") + + StringVar(&flagOpts.configFile, "config-file", "", "DP_CONFIG_FILE", "The json config file for configuring consul data plane") } // validateFlags performs semantic validation of the flag values func validateFlags() { - switch strings.ToUpper(logLevel) { + switch strings.ToUpper(flagOpts.logLevel) { case "TRACE", "DEBUG", "INFO", "WARN", "ERROR": default: log.Fatal("invalid log level. valid values - TRACE, DEBUG, INFO, WARN, ERROR") } + + if flagOpts.configFile != "" && !strings.HasSuffix(flagOpts.configFile, ".json") { + log.Fatal("invalid config file format. Should be a json file") + } } func run() error { flag.Parse() - if printVersion { + if flagOpts.printVersion { fmt.Printf("Consul Dataplane v%s\n", version.GetHumanVersion()) fmt.Printf("Revision %s\n", version.GitCommit) return nil @@ -185,82 +129,9 @@ func run() error { readServiceIDFromFile() validateFlags() - consuldpCfg := &consuldp.Config{ - Consul: &consuldp.ConsulConfig{ - Addresses: addresses, - GRPCPort: grpcPort, - Credentials: &consuldp.CredentialsConfig{ - Type: consuldp.CredentialsType(credentialType), - Static: consuldp.StaticCredentialsConfig{ - Token: token, - }, - Login: consuldp.LoginCredentialsConfig{ - AuthMethod: loginAuthMethod, - Namespace: loginNamespace, - Partition: loginPartition, - Datacenter: loginDatacenter, - BearerToken: loginBearerToken, - BearerTokenPath: loginBearerTokenPath, - Meta: loginMeta, - }, - }, - ServerWatchDisabled: serverWatchDisabled, - TLS: &consuldp.TLSConfig{ - Disabled: tlsDisabled, - CACertsPath: tlsCACertsPath, - ServerName: tlsServerName, - CertFile: tlsCertFile, - KeyFile: tlsKeyFile, - InsecureSkipVerify: tlsInsecureSkipVerify, - }, - }, - Service: &consuldp.ServiceConfig{ - NodeName: nodeName, - NodeID: nodeID, - ServiceID: serviceID, - Namespace: namespace, - Partition: partition, - }, - Logging: &consuldp.LoggingConfig{ - Name: "consul-dataplane", - LogLevel: strings.ToUpper(logLevel), - LogJSON: logJSON, - }, - Telemetry: &consuldp.TelemetryConfig{ - UseCentralConfig: useCentralTelemetryConfig, - Prometheus: consuldp.PrometheusTelemetryConfig{ - RetentionTime: promRetentionTime, - CACertsPath: promCACertsPath, - KeyFile: promKeyFile, - CertFile: promCertFile, - ServiceMetricsURL: promServiceMetricsURL, - ScrapePath: promScrapePath, - MergePort: promMergePort, - }, - }, - Envoy: &consuldp.EnvoyConfig{ - AdminBindAddress: adminBindAddr, - AdminBindPort: adminBindPort, - ReadyBindAddress: readyBindAddr, - ReadyBindPort: readyBindPort, - EnvoyConcurrency: envoyConcurrency, - EnvoyDrainTimeSeconds: envoyDrainTimeSeconds, - EnvoyDrainStrategy: envoyDrainStrategy, - ShutdownDrainListenersEnabled: shutdownDrainListenersEnabled, - ShutdownGracePeriodSeconds: shutdownGracePeriodSeconds, - GracefulShutdownPath: gracefulShutdownPath, - GracefulPort: gracefulPort, - DumpEnvoyConfigOnExitEnabled: dumpEnvoyConfigOnExitEnabled, - ExtraArgs: flag.Args(), - }, - XDSServer: &consuldp.XDSServer{ - BindAddress: xdsBindAddr, - BindPort: xdsBindPort, - }, - DNSServer: &consuldp.DNSServerConfig{ - BindAddr: consulDNSBindAddr, - Port: consulDNSPort, - }, + consuldpCfg, err := flagOpts.buildDataplaneConfig() + if err != nil { + return err } consuldpInstance, err := consuldp.NewConsulDP(consuldpCfg) @@ -298,11 +169,11 @@ func main() { // because this option only really makes sense as a CLI flag (and we handle // all flag parsing here). func readServiceIDFromFile() { - if serviceID == "" && serviceIDPath != "" { - id, err := os.ReadFile(serviceIDPath) + if flagOpts.serviceID == "" && flagOpts.serviceIDPath != "" { + id, err := os.ReadFile(flagOpts.serviceIDPath) if err != nil { log.Fatalf("failed to read given -proxy-service-id-path: %v", err) } - serviceID = string(id) + flagOpts.serviceID = string(id) } } diff --git a/cmd/consul-dataplane/merge.go b/cmd/consul-dataplane/merge.go new file mode 100644 index 00000000..b625ec9d --- /dev/null +++ b/cmd/consul-dataplane/merge.go @@ -0,0 +1,269 @@ +package main + +import ( + "strings" + + "github.com/hashicorp/consul-dataplane/pkg/consuldp" +) + +// mergeConfigs takes in a couple of configs(c1&c2) and applies +// c2 on to c1 based on the following conditions +// +// 1. If an attribute of c2 is a string/integer/bool, then it is applied to the +// same field in c1 if it holds non default value for the field +// 2. If an attribute of c2 is a slice/map, then it overrides the same field in c1 +func mergeConfigs(c1, c2 *consuldp.Config) { + mergeConsulConfigs(c1.Consul, c2.Consul) + mergeServiceConfigs(c1.Service, c2.Service) + mergeTelemetryConfigs(c1.Telemetry, c2.Telemetry) + mergeLoggingConfigs(c1.Logging, c2.Logging) + mergeEnvoyConfigs(c1.Envoy, c2.Envoy) + mergeXDSServerConfigs(c1.XDSServer, c2.XDSServer) + mergeDNSServerConfigs(c1.DNSServer, c2.DNSServer) +} + +func mergeConsulConfigs(c1, c2 *consuldp.ConsulConfig) { + if c2 == nil { + return + } + + if c2.Addresses != "" { + c1.Addresses = c2.Addresses + } + + if c2.GRPCPort != int(0) && c2.GRPCPort != DefaultGRPCPort { + c1.GRPCPort = c2.GRPCPort + } + + if c2.ServerWatchDisabled { + c1.ServerWatchDisabled = true + } + + if c2.Credentials != nil { + if c2.Credentials.Type != "" { + c1.Credentials.Type = c2.Credentials.Type + } + + if c2.Credentials.Login.AuthMethod != "" { + c1.Credentials.Login.AuthMethod = c2.Credentials.Login.AuthMethod + } + + if c2.Credentials.Login.BearerToken != "" { + c1.Credentials.Login.BearerToken = c2.Credentials.Login.BearerToken + } + + if c2.Credentials.Login.BearerTokenPath != "" { + c1.Credentials.Login.BearerTokenPath = c2.Credentials.Login.BearerTokenPath + } + + if c2.Credentials.Login.Datacenter != "" { + c1.Credentials.Login.Datacenter = c2.Credentials.Login.Datacenter + } + + if c2.Credentials.Login.Namespace != "" { + c1.Credentials.Login.Namespace = c2.Credentials.Login.Namespace + } + + if c2.Credentials.Login.Partition != "" { + c1.Credentials.Login.Partition = c2.Credentials.Login.Partition + } + + if c2.Credentials.Login.Meta != nil { + c1.Credentials.Login.Meta = c2.Credentials.Login.Meta + } + + if c2.Credentials.Static.Token != "" { + c1.Credentials.Static.Token = c2.Credentials.Static.Token + } + } + + if c2.TLS != nil { + if c2.TLS.Disabled { + c1.TLS.Disabled = true + } + + if c2.TLS.CACertsPath != "" { + c1.TLS.CACertsPath = c2.TLS.CACertsPath + } + + if c2.TLS.CertFile != "" { + c1.TLS.CertFile = c2.TLS.CertFile + } + + if c2.TLS.KeyFile != "" { + c1.TLS.KeyFile = c2.TLS.KeyFile + } + + if c2.TLS.ServerName != "" { + c1.TLS.ServerName = c2.TLS.ServerName + } + + if c2.TLS.InsecureSkipVerify { + c1.TLS.InsecureSkipVerify = true + } + } +} + +func mergeTelemetryConfigs(c1, c2 *consuldp.TelemetryConfig) { + if c2 == nil { + return + } + + if !c2.UseCentralConfig { + c1.UseCentralConfig = false + } + + if c2.Prometheus.RetentionTime != 0 && c2.Prometheus.RetentionTime != DefaultPromRetentionTime { + c1.Prometheus.RetentionTime = c2.Prometheus.RetentionTime + } + + if c2.Prometheus.CACertsPath != "" { + c1.Prometheus.CACertsPath = c2.Prometheus.CACertsPath + } + + if c2.Prometheus.CertFile != "" { + c1.Prometheus.CertFile = c2.Prometheus.CertFile + } + + if c2.Prometheus.KeyFile != "" { + c1.Prometheus.KeyFile = c2.Prometheus.KeyFile + } + + if c2.Prometheus.ScrapePath != "" { + c1.Prometheus.ScrapePath = c2.Prometheus.ScrapePath + } + + if c2.Prometheus.ServiceMetricsURL != "" { + c1.Prometheus.ServiceMetricsURL = c2.Prometheus.ServiceMetricsURL + } + + if c2.Prometheus.MergePort != int(0) && c2.Prometheus.MergePort != DefaultPromMergePort { + c1.Prometheus.MergePort = c2.Prometheus.MergePort + } +} + +func mergeServiceConfigs(c1, c2 *consuldp.ServiceConfig) { + if c2 == nil { + return + } + + if c2.NodeName != "" { + c1.NodeName = c2.NodeName + } + + if c2.NodeID != "" { + c1.NodeID = c2.NodeID + } + + if c2.Namespace != "" { + c1.Namespace = c2.Namespace + } + + if c2.Partition != "" { + c1.Partition = c2.Partition + } + + if c2.ServiceID != "" { + c1.ServiceID = c2.ServiceID + } +} + +func mergeLoggingConfigs(c1, c2 *consuldp.LoggingConfig) { + if c2 == nil { + return + } + + if c2.LogJSON { + c1.LogJSON = true + } + + if c2.LogLevel != "" && c2.LogLevel != DefaultLogLevel { + c1.LogLevel = strings.ToUpper(c2.LogLevel) + } +} + +func mergeEnvoyConfigs(c1, c2 *consuldp.EnvoyConfig) { + if c2 == nil { + return + } + + if c2.AdminBindAddress != "" && c2.AdminBindAddress != DefaultEnvoyAdminBindAddr { + c1.AdminBindAddress = c2.AdminBindAddress + } + + if c2.AdminBindPort != int(0) && c2.AdminBindPort != DefaultEnvoyAdminBindPort { + c1.AdminBindPort = c2.AdminBindPort + } + + if c2.ReadyBindAddress != "" { + c1.ReadyBindAddress = c2.ReadyBindAddress + } + + if c2.ReadyBindPort != int(0) && c2.ReadyBindPort != DefaultEnvoyReadyBindPort { + c1.ReadyBindPort = c2.ReadyBindPort + } + + if c2.EnvoyConcurrency != int(0) && c2.EnvoyConcurrency != DefaultEnvoyConcurrency { + c1.EnvoyConcurrency = c2.EnvoyConcurrency + } + + if c2.EnvoyDrainTimeSeconds != int(0) && c2.EnvoyDrainTimeSeconds != DefaultEnvoyDrainTimeSeconds { + c1.EnvoyDrainTimeSeconds = c2.EnvoyDrainTimeSeconds + } + + if c2.EnvoyDrainStrategy != "" && c2.EnvoyDrainStrategy != DefaultEnvoyDrainStrategy { + c1.EnvoyDrainStrategy = c2.EnvoyDrainStrategy + } + + if c2.ShutdownDrainListenersEnabled { + c1.ShutdownDrainListenersEnabled = true + } + + if c2.ShutdownGracePeriodSeconds != int(0) && c2.ShutdownGracePeriodSeconds != DefaultEnvoyShutdownGracePeriodSeconds { + c1.ShutdownGracePeriodSeconds = c2.ShutdownGracePeriodSeconds + } + + if c2.GracefulShutdownPath != "" && c2.GracefulShutdownPath != DefaultGracefulShutdownPath { + c1.GracefulShutdownPath = c2.GracefulShutdownPath + } + + if c2.GracefulPort != int(0) && c2.GracefulPort != DefaultGracefulPort { + c1.GracefulPort = c2.GracefulPort + } + + if c2.DumpEnvoyConfigOnExitEnabled { + c1.DumpEnvoyConfigOnExitEnabled = true + } + + if c2.ExtraArgs != nil && len(c2.ExtraArgs) > 0 { + c1.ExtraArgs = append(c1.ExtraArgs, c2.ExtraArgs...) + } +} + +func mergeXDSServerConfigs(c1, c2 *consuldp.XDSServer) { + if c2 == nil { + return + } + + if c2.BindAddress != "" && c2.BindAddress != DefaultXDSBindAddr { + c1.BindAddress = c2.BindAddress + } + + if c2.BindPort != int(0) && c2.BindPort != DefaultXDSBindPort { + c1.BindPort = c2.BindPort + } +} + +func mergeDNSServerConfigs(c1, c2 *consuldp.DNSServerConfig) { + if c2 == nil { + return + } + + if c2.BindAddr != "" && c2.BindAddr != DefaultDNSBindAddr { + c1.BindAddr = c2.BindAddr + } + + if c2.Port != int(0) && c2.Port != DefaultDNSBindPort { + c1.Port = c2.Port + } +} diff --git a/pkg/consuldp/config.go b/pkg/consuldp/config.go index 44d1d966..fee91b4d 100644 --- a/pkg/consuldp/config.go +++ b/pkg/consuldp/config.go @@ -18,26 +18,26 @@ type ConsulConfig struct { // Addresses are Consul server addresses. Value can be: // DNS name OR 'exec='. // Executable will be parsed by https://github.com/hashicorp/go-netaddrs. - Addresses string + Addresses string `json:"addresses"` // GRPCPort is the gRPC port on the Consul server. - GRPCPort int + GRPCPort int `json:"grpcPort"` // Credentials are the credentials used to authenticate requests and streams // to the Consul servers (e.g. static ACL token or auth method credentials). - Credentials *CredentialsConfig + Credentials *CredentialsConfig `json:"credentials,omitempty"` // ServerWatchDisabled opts-out of consuming the server update stream, for // cases where its addresses are incorrect (e.g. servers are behind a load // balancer). - ServerWatchDisabled bool + ServerWatchDisabled bool `json:"serverWatchDisabled"` // TLS contains the TLS settings for communicating with Consul servers. - TLS *TLSConfig + TLS *TLSConfig `json:"tls,omitempty"` } // DNSServerConfig is the configuration for the transparent DNS proxy that will forward requests to consul type DNSServerConfig struct { // BindAddr is the address the DNS server will bind to. Default will be 127.0.0.1 - BindAddr string + BindAddr string `json:"bindAddr"` // Port is the port which the DNS server will bind to. - Port int + Port int `json:"port"` } // TLSConfig contains the TLS settings for communicating with Consul servers. @@ -45,32 +45,32 @@ type TLSConfig struct { // Disabled causes consul-dataplane to communicate with Consul servers over // an insecure plaintext connection. This is useful for testing, but should // not be used in production. - Disabled bool + Disabled bool `json:"disabled"` // CACertsPath is a path to a file or directory containing CA certificates to // use to verify the server's certificate. This is only necessary if the server // presents a certificate that isn't signed by a trusted public CA. - CACertsPath string + CACertsPath string `json:"caCertsPath"` // ServerName is used to verify the server certificate's subject when it cannot // be inferred from Consul.Addresses (i.e. it is not a DNS name). - ServerName string + ServerName string `json:"serverName"` // CertFile is a path to the client certificate that will be presented to // Consul servers. // // Note: this is only required if servers have tls.grpc.verify_incoming enabled. // Generally, issuing consul-dataplane instances with client certificates isn't // necessary and creates significant operational burden. - CertFile string + CertFile string `json:"certFile"` // KeyFile is a path to the client private key that will be used to communicate // with Consul servers (when CertFile is provided). // // Note: this is only required if servers have tls.grpc.verify_incoming enabled. // Generally, issuing consul-dataplane instances with client certificates isn't // necessary and creates significant operational burden. - KeyFile string + KeyFile string `json:"keyFile"` // InsecureSkipVerify causes consul-dataplane not to verify the certificate // presented by the server. This is useful for testing, but should not be used // in production. - InsecureSkipVerify bool + InsecureSkipVerify bool `json:"insecureSkipVerify"` } // Load creates a *tls.Config, including loading the CA and client certificates. @@ -115,11 +115,11 @@ func (t *TLSConfig) Load() (*tls.Config, error) { // streams to the Consul servers. type CredentialsConfig struct { // Type identifies the type of credentials provided. - Type CredentialsType + Type CredentialsType `json:"type"` // Static contains the static ACL token. - Static StaticCredentialsConfig + Static StaticCredentialsConfig `json:"static"` // Login contains the credentials for logging in with an auth method. - Login LoginCredentialsConfig + Login LoginCredentialsConfig `json:"login"` } // CredentialsType identifies the type of credentials provided. @@ -139,26 +139,26 @@ const ( // authenticate requests and streams to the Consul servers. type StaticCredentialsConfig struct { // Token is the static ACL token. - Token string + Token string `json:"token"` } // LoginCredentialsConfig contains credentials for logging in with an auth method. type LoginCredentialsConfig struct { // AuthMethod is the name of the Consul auth method. - AuthMethod string + AuthMethod string `json:"authmethod"` // Namespace is the namespace containing the auth method. - Namespace string + Namespace string `json:"namespace"` // Partition is the partition containing the auth method. - Partition string + Partition string `json:"partition"` // Datacenter is the datacenter containing the auth method. - Datacenter string + Datacenter string `json:"datacenter"` // BearerToken is the bearer token presented to the auth method. - BearerToken string + BearerToken string `json:"bearerToken"` // BearerTokenPath is the path to a file containing a bearer token. - BearerTokenPath string + BearerTokenPath string `json:"bearerTokenPath"` // Meta is the arbitrary set of key-value pairs to attach to the // token. These are included in the Description field of the token. - Meta map[string]string + Meta map[string]string `json:"meta,omitempty"` } // ToDiscoveryCredentials creates a discovery.Credentials, including loading a @@ -204,76 +204,76 @@ type LoggingConfig struct { // Name of the subsystem to prefix logs with Name string // LogLevel is the logging level. Valid values - TRACE, DEBUG, INFO, WARN, ERROR - LogLevel string + LogLevel string `json:"logLevel"` // LogJSON controls if the output should be in JSON. - LogJSON bool + LogJSON bool `json:"logJSON"` } // ServiceConfig contains details of the proxy service instance. type ServiceConfig struct { // NodeName is the name of the node to which the proxy service instance is // registered. - NodeName string + NodeName string `json:"nodeName"` // NodeName is the ID of the node to which the proxy service instance is // registered. - NodeID string + NodeID string `json:"nodeId"` // ServiceID is the ID of the proxy service instance. - ServiceID string + ServiceID string `json:"serviceId"` // Namespace is the Consul Enterprise namespace in which the proxy service // instance is registered. - Namespace string + Namespace string `json:"namespace"` // Partition is the Consul Enterprise partition in which the proxy service // instance is registered. - Partition string + Partition string `json:"partition"` } // TelemetryConfig contains configuration for telemetry. type TelemetryConfig struct { // UseCentralConfig controls whether the proxy will apply the central telemetry // configuration. - UseCentralConfig bool + UseCentralConfig bool `json:"useCentralConfig"` // Prometheus contains Prometheus-specific configuration that cannot be // determined from central telemetry configuration. - Prometheus PrometheusTelemetryConfig + Prometheus PrometheusTelemetryConfig `json:"prometheus"` } // PrometheusTelemetryConfig contains Prometheus-specific telemetry config. type PrometheusTelemetryConfig struct { // RetentionTime controls the duration that metrics are aggregated for. - RetentionTime time.Duration + RetentionTime time.Duration `json:"retentionTime"` // CACertsPath is a path to a file or directory containing CA certificates // to use to verify the Prometheus server's certificate. This is only // necessary if the server presents a certificate that isn't signed by a // trusted public CA. - CACertsPath string + CACertsPath string `json:"caCertsPath"` // KeyFile is a path to the client private key used for serving Prometheus // metrics. - KeyFile string + KeyFile string `json:"keyFile"` // CertFile is a path to the client certificate used for serving Prometheus // metrics. - CertFile string + CertFile string `json:"certFile"` // ServiceMetricsURL is an optional URL that must serve Prometheus metrics. // The metrics at this URL are scraped and merged into Consul Dataplane's // main Prometheus metrics. - ServiceMetricsURL string + ServiceMetricsURL string `json:"serviceMetricsURL"` // ScrapePath is the URL path where Envoy serves Prometheus metrics. - ScrapePath string + ScrapePath string `json:"scrapePath"` // MergePort is the port to server merged metrics. - MergePort int + MergePort int `json:"mergePort"` } // EnvoyConfig contains configuration for the Envoy process. type EnvoyConfig struct { // AdminBindAddress is the address on which the Envoy admin server will be available. - AdminBindAddress string + AdminBindAddress string `json:"adminBindAddress"` // AdminBindPort is the port on which the Envoy admin server will be available. - AdminBindPort int + AdminBindPort int `json:"adminBindPort"` // ReadyBindAddress is the address on which the Envoy readiness probe will be available. - ReadyBindAddress string + ReadyBindAddress string `json:"readyBindAddress"` // ReadyBindPort is the port on which the Envoy readiness probe will be available. - ReadyBindPort int + ReadyBindPort int `json:"readyBindPort"` // EnvoyConcurrency is the envoy concurrency https://www.envoyproxy.io/docs/envoy/latest/operations/cli#cmdoption-concurrency - EnvoyConcurrency int + EnvoyConcurrency int `json:"envoyConcurrency"` // EnvoyDrainTime is the time in seconds for which Envoy will drain connections // during a hot restart, when listeners are modified or removed via LDS, or when // initiated manually via a request to the Envoy admin API. @@ -281,42 +281,42 @@ type EnvoyConfig struct { // requests, send HTTP2 GOAWAY, and terminate connections on request completion // (after the delayed close period). // https://www.envoyproxy.io/docs/envoy/latest/operations/cli#cmdoption-drain-time-s - EnvoyDrainTimeSeconds int + EnvoyDrainTimeSeconds int `json:"envoyDrainTimeSeconds"` // EnvoyDrainStrategy is the behaviour of Envoy during the drain sequence. // Determines whether all open connections should be encouraged to drain // immediately or to increase the percentage gradually as the drain time elapses. // https://www.envoyproxy.io/docs/envoy/latest/operations/cli#cmdoption-drain-strategy - EnvoyDrainStrategy string + EnvoyDrainStrategy string `json:"envoyDrainStrategy"` // ShutdownDrainListenersEnabled configures whether to start draining proxy listeners before terminating the proxy container. Drain time defaults to the value of ShutdownGracePeriodSeconds, but may be set explicitly with EnvoyDrainTimeSeconds. - ShutdownDrainListenersEnabled bool + ShutdownDrainListenersEnabled bool `json:"shutdownDrainListenersEnabled"` // ShutdownGracePeriodSeconds is the amount of time to wait after receiving a SIGTERM before terminating the proxy container. - ShutdownGracePeriodSeconds int + ShutdownGracePeriodSeconds int `json:"shutdownGracePeriodSeconds"` // GracefulShutdownPath is the path on which the HTTP endpoint to initiate a graceful shutdown of Envoy is served - GracefulShutdownPath string + GracefulShutdownPath string `json:"gracefulShutdownPath"` // GracefulPort is the port on which the HTTP server for graceful shutdown endpoints will be available. - GracefulPort int + GracefulPort int `json:"gracefulPort"` // DumpEnvoyConfigOnExitEnabled configures whether to call Envoy's /config_dump endpoint during consul-dataplane controlled shutdown. - DumpEnvoyConfigOnExitEnabled bool + DumpEnvoyConfigOnExitEnabled bool `json:"dumpEnvoyConfigOnExitEnabled"` // ExtraArgs are the extra arguments passed to envoy at startup of the proxy - ExtraArgs []string + ExtraArgs []string `json:"extraArgs,omitempty"` } // XDSServer contains the configuration of the xDS server. type XDSServer struct { // BindAddress is the address on which the Envoy xDS server will be available. - BindAddress string + BindAddress string `json:"bindAddress"` // BindPort is the address on which the Envoy xDS port will be available. - BindPort int + BindPort int `json:"bindPort"` } // Config is the configuration used by consul-dataplane, consolidated // from various sources - CLI flags, env vars, config file settings. type Config struct { - DNSServer *DNSServerConfig - Consul *ConsulConfig - Service *ServiceConfig - Logging *LoggingConfig - Telemetry *TelemetryConfig - Envoy *EnvoyConfig - XDSServer *XDSServer + DNSServer *DNSServerConfig `json:"dnsServerConfig,omitempty"` + Consul *ConsulConfig `json:"consul,omitempty"` + Service *ServiceConfig `json:"service,omitempty"` + Logging *LoggingConfig `json:"logging,omitempty"` + Telemetry *TelemetryConfig `json:"telemetry,omitempty"` + Envoy *EnvoyConfig `json:"envoy,omitempty"` + XDSServer *XDSServer `json:"xdsServer,omitempty"` } From dee2eed4eb9a589f84717de07914eac70e83734f Mon Sep 17 00:00:00 2001 From: Ganeshrockz Date: Tue, 20 Jun 2023 08:32:01 +0530 Subject: [PATCH 2/8] Added changelog --- .changelog/164.tx | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .changelog/164.tx diff --git a/.changelog/164.tx b/.changelog/164.tx new file mode 100644 index 00000000..f9229a35 --- /dev/null +++ b/.changelog/164.tx @@ -0,0 +1,4 @@ +```release-note:improvement +Adds a flag to the existing consul-dataplane command `config-file` to support reading configurations +from a JSON file. +``` \ No newline at end of file From f5e02aefdaafb2c0daadf4a8fba92d310ac42c61 Mon Sep 17 00:00:00 2001 From: Ganeshrockz Date: Tue, 20 Jun 2023 08:37:53 +0530 Subject: [PATCH 3/8] Fix changelog --- .changelog/{164.tx => 164.txt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .changelog/{164.tx => 164.txt} (100%) diff --git a/.changelog/164.tx b/.changelog/164.txt similarity index 100% rename from .changelog/164.tx rename to .changelog/164.txt From 3a11e4b6d440d54c00599be2c8a133f69596e09d Mon Sep 17 00:00:00 2001 From: Ganeshrockz Date: Tue, 20 Jun 2023 08:50:10 +0530 Subject: [PATCH 4/8] Fix slice override --- cmd/consul-dataplane/merge.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/consul-dataplane/merge.go b/cmd/consul-dataplane/merge.go index b625ec9d..f049f793 100644 --- a/cmd/consul-dataplane/merge.go +++ b/cmd/consul-dataplane/merge.go @@ -236,7 +236,7 @@ func mergeEnvoyConfigs(c1, c2 *consuldp.EnvoyConfig) { } if c2.ExtraArgs != nil && len(c2.ExtraArgs) > 0 { - c1.ExtraArgs = append(c1.ExtraArgs, c2.ExtraArgs...) + c1.ExtraArgs = c2.ExtraArgs } } From ec47672c6c389dccecd3d31b53e88e187992d0d6 Mon Sep 17 00:00:00 2001 From: Ganeshrockz Date: Tue, 20 Jun 2023 21:19:44 +0530 Subject: [PATCH 5/8] Fix issues --- cmd/consul-dataplane/config.go | 453 ++++++++++++++------------ cmd/consul-dataplane/config_test.go | 297 ++++++++++------- cmd/consul-dataplane/duration.go | 37 +++ cmd/consul-dataplane/duration_test.go | 63 ++++ cmd/consul-dataplane/env.go | 60 +++- cmd/consul-dataplane/flags.go | 196 ++++++++++- cmd/consul-dataplane/main.go | 148 +++++---- cmd/consul-dataplane/merge.go | 267 +++++++-------- pkg/consuldp/config.go | 124 +++---- 9 files changed, 1019 insertions(+), 626 deletions(-) create mode 100644 cmd/consul-dataplane/duration.go create mode 100644 cmd/consul-dataplane/duration_test.go diff --git a/cmd/consul-dataplane/config.go b/cmd/consul-dataplane/config.go index a30a0911..de23d83a 100644 --- a/cmd/consul-dataplane/config.go +++ b/cmd/consul-dataplane/config.go @@ -2,114 +2,125 @@ package main import ( "encoding/json" - "flag" "os" "strings" - "time" "github.com/hashicorp/consul-dataplane/pkg/consuldp" ) type FlagOpts struct { + dataplaneConfig DataplaneConfigFlags + printVersion bool + configFile string +} + +type DataplaneConfigFlags struct { + Consul ConsulFlags `json:"consul,omitempty"` + Service ServiceFlags `json:"service,omitempty"` + Logging LogFlags `json:"logging,omitempty"` + XDSServer XDSServerFlags `json:"xdsServer,omitempty"` + DNSServer DNSServerFlags `json:"dnsServer,omitempty"` + Telemetry TelemetryFlags `json:"telemetry,omitempty"` + Envoy EnvoyFlags `json:"envoy,omitempty"` +} + +type ConsulFlags struct { + Addresses *string `json:"addresses,omitempty"` + GRPCPort *int `json:"grpcPort,omitempty"` + ServerWatchDisabled *bool `json:"serverWatchDisabled,omitempty"` + + TLS TLSFlags `json:"tls,omitempty"` + Credentials CredentialsFlags `json:"credentials,omitempty"` +} + +type TLSFlags struct { + Disabled *bool `json:"disabled,omitempty"` + CACertsPath *string `json:"caCertsPath,omitempty"` + ServerName *string `json:"serverName,omitempty"` + CertFile *string `json:"certFile,omitempty"` + KeyFile *string `json:"keyFile,omitempty"` + InsecureSkipVerify *bool `json:"insecureSkipVerify,omitempty"` +} + +type CredentialsFlags struct { + Type *string `json:"type,omitempty"` + Static StaticCredentialsFlags `json:"static,omitempty"` + Login LoginCredentialsFlags `json:"login,omitempty"` +} + +type StaticCredentialsFlags struct { + Token *string `json:"token,omitempty"` +} + +type LoginCredentialsFlags struct { + AuthMethod *string `json:"authMethod,omitempty"` + Namespace *string `json:"namespace,omitempty"` + Partition *string `json:"partition,omitempty"` + Datacenter *string `json:"datacenter,omitempty"` + BearerToken *string `json:"bearerToken,omitempty"` + BearerTokenPath *string `json:"bearerTokenPath,omitempty"` + Meta map[string]string `json:"meta,omitempty"` +} + +type ServiceFlags struct { + NodeName *string `json:"nodeName,omitempty"` + NodeID *string `json:"nodeID,omitempty"` + ServiceID *string `json:"serviceID,omitempty"` + ServiceIDPath *string `json:"serviceIDPath,omitempty"` + Namespace *string `json:"namespace,omitempty"` + Partition *string `json:"partition,omitempty"` +} + +type XDSServerFlags struct { + BindAddr *string `json:"bindAddress,omitempty"` + BindPort *int `json:"bindPort,omitempty"` +} - addresses string - grpcPort int - serverWatchDisabled bool - - tlsDisabled bool - tlsCACertsPath string - tlsServerName string - tlsCertFile string - tlsKeyFile string - tlsInsecureSkipVerify bool - - logLevel string - logJSON bool - - nodeName string - nodeID string - serviceID string - serviceIDPath string - namespace string - partition string - - credentialType string - token string - loginAuthMethod string - loginNamespace string - loginPartition string - loginDatacenter string - loginBearerToken string - loginBearerTokenPath string - loginMeta map[string]string - - useCentralTelemetryConfig bool - - promRetentionTime time.Duration - promCACertsPath string - promKeyFile string - promCertFile string - promServiceMetricsURL string - promScrapePath string - promMergePort int - - adminBindAddr string - adminBindPort int - readyBindAddr string - readyBindPort int - envoyConcurrency int - envoyDrainTimeSeconds int - envoyDrainStrategy string - - xdsBindAddr string - xdsBindPort int - - consulDNSBindAddr string - consulDNSPort int - - shutdownDrainListenersEnabled bool - shutdownGracePeriodSeconds int - gracefulShutdownPath string - gracefulPort int - - dumpEnvoyConfigOnExitEnabled bool - - configFile string +type DNSServerFlags struct { + BindAddr *string `json:"bindAddress,omitempty"` + BindPort *int `json:"bindPort,omitempty"` +} + +type LogFlags struct { + Name string + LogLevel *string `json:"logLevel,omitempty"` + LogJSON *bool `json:"logJSON,omitempty"` +} + +type TelemetryFlags struct { + UseCentralConfig *bool `json:"useCentralConfig"` + Prometheus PrometheusTelemetryFlags `json:"prometheus,omitempty"` +} + +type PrometheusTelemetryFlags struct { + RetentionTime *Duration `json:"retentionTime,omitempty"` + CACertsPath *string `json:"caCertsPath,omitempty"` + KeyFile *string `json:"keyFile,omitempty"` + CertFile *string `json:"certFile,omitempty"` + ServiceMetricsURL *string `json:"serviceMetricsURL,omitempty"` + ScrapePath *string `json:"scrapePath,omitempty"` + MergePort *int `json:"mergePort,omitempty"` +} + +type EnvoyFlags struct { + AdminBindAddr *string `json:"adminBindAddress,omitempty"` + AdminBindPort *int `json:"adminBindPort,omitempty"` + ReadyBindAddr *string `json:"readyBindAddress,omitempty"` + ReadyBindPort *int `json:"readyBindPort,omitempty"` + Concurrency *int `json:"concurrency,omitempty"` + DrainTimeSeconds *int `json:"drainTimeSeconds,omitempty"` + DrainStrategy *string `json:"drainStrategy,omitempty"` + + ShutdownDrainListenersEnabled *bool `json:"shutdownDrainListenersEnabled,omitempty"` + ShutdownGracePeriodSeconds *int `json:"shutdownGracePeriodSeconds,omitempty"` + GracefulShutdownPath *string `json:"gracefulShutdownPath,omitempty"` + GracefulPort *int `json:"gracefulPort,omitempty"` + DumpEnvoyConfigOnExitEnabled *bool `json:"dumpEnvoyConfigOnExitEnabled,omitempty"` } const ( - DefaultGRPCPort = 8502 - DefaultServerWatchDisabled = false - - DefaultTLSDisabled = false - DefaultTLSInsecureSkipVerify = false - - DefaultDNSBindAddr = "127.0.0.1" - DefaultDNSBindPort = -1 - - DefaultXDSBindAddr = "127.0.0.1" - DefaultXDSBindPort = 0 - - DefaultLogLevel = "info" - DefaultLogJSON = false - - DefaultEnvoyAdminBindAddr = "127.0.0.1" - DefaultEnvoyAdminBindPort = 19000 - DefaultEnvoyReadyBindPort = 0 - DefaultEnvoyConcurrency = 2 - DefaultEnvoyDrainTimeSeconds = 30 - DefaultEnvoyDrainStrategy = "immediate" - DefaultEnvoyShutdownDrainListenersEnabled = false - DefaultEnvoyShutdownGracePeriodSeconds = 0 - DefaultGracefulShutdownPath = "/graceful_shutdown" - DefaultGracefulPort = 20300 - DefaultDumpEnvoyConfigOnExitEnabled = false - - DefaultUseCentralTelemetryConfig = true - DefaultPromRetentionTime = 60 * time.Second - DefaultPromScrapePath = "/metrics" - DefaultPromMergePort = 20100 + DefaultLogName = "consul-dataplane" ) // buildDataplaneConfig builds the necessary config needed for the @@ -119,29 +130,35 @@ const ( // Since values given via CLI flags take the most precedence, we finally // merge the config generated from the flags into the previously // generated/merged config -func (f *FlagOpts) buildDataplaneConfig() (*consuldp.Config, error) { - var consuldpConfig, consuldpCfgFromFlags *consuldp.Config - - consuldpConfig = buildDefaultConsulDPConfig() - consuldpCfgFromFlags = f.buildConfig() +func (f *FlagOpts) buildDataplaneConfig(extraArgs []string) (*consuldp.Config, error) { + consulDPDefaultFlags, err := buildDefaultConsulDPFlags() + if err != nil { + return nil, err + } if f.configFile != "" { - consuldpCfgFromFile, err := f.buildConfigFromFile() + consulDPFileBasedFlags, err := f.buildConfigFromFile() if err != nil { return nil, err } - mergeConfigs(consuldpConfig, consuldpCfgFromFile) + mergeConfigs(consulDPDefaultFlags, consulDPFileBasedFlags) } - mergeConfigs(consuldpConfig, consuldpCfgFromFlags) + consulDPCLIFlags := &f.dataplaneConfig + mergeConfigs(consulDPDefaultFlags, consulDPCLIFlags) - return consuldpConfig, nil + consuldpRuntimeConfig, err := constructRuntimeConfig(consulDPDefaultFlags, extraArgs) + if err != nil { + return nil, err + } + + return consuldpRuntimeConfig, nil } // Constructs a config based on the values present in the config json file -func (f *FlagOpts) buildConfigFromFile() (*consuldp.Config, error) { - var cfg *consuldp.Config +func (f *FlagOpts) buildConfigFromFile() (*DataplaneConfigFlags, error) { + var cfg *DataplaneConfigFlags data, err := os.ReadFile(f.configFile) if err != nil { return nil, err @@ -155,140 +172,144 @@ func (f *FlagOpts) buildConfigFromFile() (*consuldp.Config, error) { return cfg, nil } -// Constructs a config based on the values given via the CLI flags -func (f *FlagOpts) buildConfig() *consuldp.Config { - return &consuldp.Config{ - Consul: &consuldp.ConsulConfig{ - Addresses: f.addresses, - GRPCPort: f.grpcPort, - Credentials: &consuldp.CredentialsConfig{ - Type: consuldp.CredentialsType(f.credentialType), - Static: consuldp.StaticCredentialsConfig{ - Token: f.token, - }, - Login: consuldp.LoginCredentialsConfig{ - AuthMethod: f.loginAuthMethod, - Namespace: f.loginNamespace, - Partition: f.loginPartition, - Datacenter: f.loginDatacenter, - BearerToken: f.loginBearerToken, - BearerTokenPath: f.loginBearerTokenPath, - Meta: f.loginMeta, - }, - }, - ServerWatchDisabled: f.serverWatchDisabled, - TLS: &consuldp.TLSConfig{ - Disabled: f.tlsDisabled, - CACertsPath: f.tlsCACertsPath, - ServerName: f.tlsServerName, - CertFile: f.tlsCertFile, - KeyFile: f.tlsKeyFile, - InsecureSkipVerify: f.tlsInsecureSkipVerify, - }, - }, - Service: &consuldp.ServiceConfig{ - NodeName: f.nodeName, - NodeID: f.nodeID, - ServiceID: f.serviceID, - Namespace: f.namespace, - Partition: f.partition, +// Constructs a config with the default values +func buildDefaultConsulDPFlags() (*DataplaneConfigFlags, error) { + data := ` + { + "consul": { + "grpcPort": 8502, + "serverWatchDisabled": false, + "tls": { + "disabled": false, + "insecureSkipVerify": false + } }, - Logging: &consuldp.LoggingConfig{ - Name: "consul-dataplane", - LogLevel: strings.ToUpper(f.logLevel), - LogJSON: f.logJSON, + "logging": { + "name": "consul-dataplane", + "logJSON": false, + "logLevel": "info" }, - Telemetry: &consuldp.TelemetryConfig{ - UseCentralConfig: f.useCentralTelemetryConfig, - Prometheus: consuldp.PrometheusTelemetryConfig{ - RetentionTime: f.promRetentionTime, - CACertsPath: f.promCACertsPath, - KeyFile: f.promKeyFile, - CertFile: f.promCertFile, - ServiceMetricsURL: f.promServiceMetricsURL, - ScrapePath: f.promScrapePath, - MergePort: f.promMergePort, - }, - }, - Envoy: &consuldp.EnvoyConfig{ - AdminBindAddress: f.adminBindAddr, - AdminBindPort: f.adminBindPort, - ReadyBindAddress: f.readyBindAddr, - ReadyBindPort: f.readyBindPort, - EnvoyConcurrency: f.envoyConcurrency, - EnvoyDrainTimeSeconds: f.envoyDrainTimeSeconds, - EnvoyDrainStrategy: f.envoyDrainStrategy, - ShutdownDrainListenersEnabled: f.shutdownDrainListenersEnabled, - ShutdownGracePeriodSeconds: f.shutdownGracePeriodSeconds, - GracefulShutdownPath: f.gracefulShutdownPath, - GracefulPort: f.gracefulPort, - DumpEnvoyConfigOnExitEnabled: f.dumpEnvoyConfigOnExitEnabled, - ExtraArgs: flag.Args(), + "telemetry": { + "useCentralConfig": true, + "prometheus": { + "retentionTime": "60s", + "scrapePath": "/metrics", + "mergePort": 20100 + } }, - XDSServer: &consuldp.XDSServer{ - BindAddress: f.xdsBindAddr, - BindPort: f.xdsBindPort, + "envoy": { + "adminBindAddress": "127.0.0.1", + "adminBindPort": 19000, + "readyBindPort": 0, + "concurrency": 2, + "drainTimeSeconds": 30, + "drainStrategy": "immediate", + "shutdownDrainListenersEnabled": false, + "shutdownGracePeriodSeconds": 0, + "gracefulShutdownPath": "/graceful_shutdown", + "gracefulPort": 20300, + "dumpEnvoyConfigOnExitEnabled": false }, - DNSServer: &consuldp.DNSServerConfig{ - BindAddr: f.consulDNSBindAddr, - Port: f.consulDNSPort, + "xdsServer": { + "bindAddress": "127.0.0.1", + "bindPort": 0 }, + "dnsServer": { + "bindAddress": "127.0.0.1", + "bindPort": -1 + } + }` + + var defaultCfgFlags *DataplaneConfigFlags + err := json.Unmarshal([]byte(data), &defaultCfgFlags) + if err != nil { + return nil, err } + + return defaultCfgFlags, nil } -// Constructs a config with the default values -func buildDefaultConsulDPConfig() *consuldp.Config { +// constructRuntimeConfig constructs the final config needed for dataplane to start +// itself after substituting all the user provided inputs +func constructRuntimeConfig(cfg *DataplaneConfigFlags, extraArgs []string) (*consuldp.Config, error) { + if cfg == nil { + return &consuldp.Config{}, nil + } + return &consuldp.Config{ Consul: &consuldp.ConsulConfig{ - GRPCPort: DefaultGRPCPort, + Addresses: stringVal(cfg.Consul.Addresses), + GRPCPort: intVal(cfg.Consul.GRPCPort), + ServerWatchDisabled: boolVal(cfg.Consul.ServerWatchDisabled), Credentials: &consuldp.CredentialsConfig{ - Type: consuldp.CredentialsType(""), - Static: consuldp.StaticCredentialsConfig{}, + Type: consuldp.CredentialsType(stringVal(cfg.Consul.Credentials.Type)), + Static: consuldp.StaticCredentialsConfig{ + Token: stringVal(cfg.Consul.Credentials.Static.Token), + }, Login: consuldp.LoginCredentialsConfig{ - Meta: map[string]string{}, + AuthMethod: stringVal(cfg.Consul.Credentials.Login.AuthMethod), + Namespace: stringVal(cfg.Consul.Credentials.Login.Namespace), + Partition: stringVal(cfg.Consul.Credentials.Login.Partition), + Datacenter: stringVal(cfg.Consul.Credentials.Login.Datacenter), + BearerToken: stringVal(cfg.Consul.Credentials.Login.BearerToken), + BearerTokenPath: stringVal(cfg.Consul.Credentials.Login.BearerTokenPath), + Meta: cfg.Consul.Credentials.Login.Meta, }, }, - ServerWatchDisabled: DefaultServerWatchDisabled, TLS: &consuldp.TLSConfig{ - Disabled: DefaultTLSDisabled, - InsecureSkipVerify: DefaultTLSInsecureSkipVerify, + Disabled: boolVal(cfg.Consul.TLS.Disabled), + CACertsPath: stringVal(cfg.Consul.TLS.CACertsPath), + CertFile: stringVal(cfg.Consul.TLS.CertFile), + KeyFile: stringVal(cfg.Consul.TLS.KeyFile), + ServerName: stringVal(cfg.Consul.TLS.ServerName), + InsecureSkipVerify: boolVal(cfg.Consul.TLS.InsecureSkipVerify), }, }, - Service: &consuldp.ServiceConfig{}, + Service: &consuldp.ServiceConfig{ + NodeName: stringVal(cfg.Service.NodeName), + NodeID: stringVal(cfg.Service.NodeID), + ServiceID: stringVal(cfg.Service.ServiceID), + Namespace: stringVal(cfg.Service.Namespace), + Partition: stringVal(cfg.Service.Partition), + }, Logging: &consuldp.LoggingConfig{ - Name: "consul-dataplane", - LogLevel: strings.ToUpper(DefaultLogLevel), - LogJSON: DefaultLogJSON, + Name: DefaultLogName, + LogJSON: boolVal(cfg.Logging.LogJSON), + LogLevel: strings.ToUpper(stringVal(cfg.Logging.LogLevel)), + }, + Envoy: &consuldp.EnvoyConfig{ + AdminBindAddress: stringVal(cfg.Envoy.AdminBindAddr), + AdminBindPort: intVal(cfg.Envoy.AdminBindPort), + ReadyBindAddress: stringVal(cfg.Envoy.ReadyBindAddr), + ReadyBindPort: intVal(cfg.Envoy.ReadyBindPort), + EnvoyConcurrency: intVal(cfg.Envoy.Concurrency), + EnvoyDrainTimeSeconds: intVal(cfg.Envoy.DrainTimeSeconds), + EnvoyDrainStrategy: stringVal(cfg.Envoy.DrainStrategy), + ShutdownDrainListenersEnabled: boolVal(cfg.Envoy.ShutdownDrainListenersEnabled), + DumpEnvoyConfigOnExitEnabled: boolVal(cfg.Envoy.DumpEnvoyConfigOnExitEnabled), + GracefulShutdownPath: stringVal(cfg.Envoy.GracefulShutdownPath), + GracefulPort: intVal(cfg.Envoy.GracefulPort), + ExtraArgs: extraArgs, }, Telemetry: &consuldp.TelemetryConfig{ - UseCentralConfig: DefaultUseCentralTelemetryConfig, + UseCentralConfig: boolVal(cfg.Telemetry.UseCentralConfig), Prometheus: consuldp.PrometheusTelemetryConfig{ - RetentionTime: DefaultPromRetentionTime, - ScrapePath: DefaultPromScrapePath, - MergePort: DefaultPromMergePort, + RetentionTime: durationVal(cfg.Telemetry.Prometheus.RetentionTime), + CACertsPath: stringVal(cfg.Telemetry.Prometheus.CACertsPath), + CertFile: stringVal(cfg.Telemetry.Prometheus.CertFile), + KeyFile: stringVal(cfg.Telemetry.Prometheus.KeyFile), + ServiceMetricsURL: stringVal(cfg.Telemetry.Prometheus.ServiceMetricsURL), + ScrapePath: stringVal(cfg.Telemetry.Prometheus.ScrapePath), + MergePort: intVal(cfg.Telemetry.Prometheus.MergePort), }, }, - Envoy: &consuldp.EnvoyConfig{ - AdminBindAddress: DefaultEnvoyAdminBindAddr, - AdminBindPort: DefaultEnvoyAdminBindPort, - ReadyBindPort: DefaultEnvoyReadyBindPort, - EnvoyConcurrency: DefaultEnvoyConcurrency, - EnvoyDrainTimeSeconds: DefaultEnvoyDrainTimeSeconds, - EnvoyDrainStrategy: DefaultEnvoyDrainStrategy, - ShutdownDrainListenersEnabled: DefaultEnvoyShutdownDrainListenersEnabled, - ShutdownGracePeriodSeconds: DefaultEnvoyShutdownGracePeriodSeconds, - GracefulShutdownPath: DefaultGracefulShutdownPath, - GracefulPort: DefaultGracefulPort, - DumpEnvoyConfigOnExitEnabled: DefaultDumpEnvoyConfigOnExitEnabled, - ExtraArgs: []string{}, - }, XDSServer: &consuldp.XDSServer{ - BindAddress: DefaultXDSBindAddr, - BindPort: DefaultXDSBindPort, + BindAddress: stringVal(cfg.XDSServer.BindAddr), + BindPort: intVal(cfg.XDSServer.BindPort), }, DNSServer: &consuldp.DNSServerConfig{ - BindAddr: DefaultDNSBindAddr, - Port: DefaultDNSBindPort, + BindAddr: stringVal(cfg.DNSServer.BindAddr), + Port: intVal(cfg.DNSServer.BindPort), }, - } + }, nil } diff --git a/cmd/consul-dataplane/config_test.go b/cmd/consul-dataplane/config_test.go index 20d9814c..0dc8ffed 100644 --- a/cmd/consul-dataplane/config_test.go +++ b/cmd/consul-dataplane/config_test.go @@ -1,6 +1,7 @@ package main import ( + "encoding/json" "fmt" "math/rand" "os" @@ -15,7 +16,7 @@ import ( func TestConfigGeneration(t *testing.T) { type testCase struct { desc string - flagOpts func() *FlagOpts + flagOpts func() (*FlagOpts, error) writeConfigFile func() error assertConfig func(cfg *consuldp.Config, f *FlagOpts) bool cleanup func() @@ -25,14 +26,14 @@ func TestConfigGeneration(t *testing.T) { testCases := []testCase{ { desc: "able to generate config properly when the config file input is empty", - flagOpts: func() *FlagOpts { + flagOpts: func() (*FlagOpts, error) { return generateFlagOpts() }, assertConfig: func(cfg *consuldp.Config, flagOpts *FlagOpts) bool { expectedCfg := &consuldp.Config{ Consul: &consuldp.ConsulConfig{ - Addresses: flagOpts.addresses, - GRPCPort: flagOpts.grpcPort, + Addresses: stringVal(flagOpts.dataplaneConfig.Consul.Addresses), + GRPCPort: intVal(flagOpts.dataplaneConfig.Consul.GRPCPort), ServerWatchDisabled: true, Credentials: &consuldp.CredentialsConfig{ Type: "static", @@ -40,7 +41,6 @@ func TestConfigGeneration(t *testing.T) { Token: "test-token-123", }, Login: consuldp.LoginCredentialsConfig{ - Meta: make(map[string]string), AuthMethod: "test-iam-auth", BearerToken: "bearer-login", }, @@ -62,7 +62,7 @@ func TestConfigGeneration(t *testing.T) { Partition: "default", }, Logging: &consuldp.LoggingConfig{ - Name: "consul-dataplane", + Name: DefaultLogName, LogJSON: true, LogLevel: "WARN", }, @@ -85,7 +85,6 @@ func TestConfigGeneration(t *testing.T) { GracefulShutdownPath: "/graceful_shutdown", EnvoyDrainTimeSeconds: 30, GracefulPort: 20300, - ExtraArgs: []string{}, }, Telemetry: &consuldp.TelemetryConfig{ UseCentralConfig: true, @@ -106,28 +105,31 @@ func TestConfigGeneration(t *testing.T) { }, { desc: "able to override all the config fields with CLI flags", - flagOpts: func() *FlagOpts { - opts := generateFlagOpts() - opts.loginBearerTokenPath = "/consul/bearertokenpath/" - opts.loginDatacenter = "dc100" - opts.loginMeta = map[string]string{ + flagOpts: func() (*FlagOpts, error) { + opts, err := generateFlagOpts() + if err != nil { + return nil, err + } + opts.dataplaneConfig.Consul.Credentials.Login.BearerTokenPath = strReference("/consul/bearertokenpath/") + opts.dataplaneConfig.Consul.Credentials.Login.Datacenter = strReference("dc100") + opts.dataplaneConfig.Consul.Credentials.Login.Meta = map[string]string{ "key-1": "value-1", "key-2": "value-2", } - opts.loginNamespace = "default" - opts.loginPartition = "default" + opts.dataplaneConfig.Consul.Credentials.Login.Namespace = strReference("default") + opts.dataplaneConfig.Consul.Credentials.Login.Partition = strReference("default") - opts.logJSON = false - opts.consulDNSBindAddr = "127.0.0.2" - opts.xdsBindPort = 6060 - opts.dumpEnvoyConfigOnExitEnabled = true - return opts + opts.dataplaneConfig.Logging.LogJSON = boolReference(false) + opts.dataplaneConfig.DNSServer.BindAddr = strReference("127.0.0.2") + opts.dataplaneConfig.XDSServer.BindPort = intReference(6060) + opts.dataplaneConfig.Envoy.DumpEnvoyConfigOnExitEnabled = boolReference(true) + return opts, nil }, assertConfig: func(cfg *consuldp.Config, flagOpts *FlagOpts) bool { expectedCfg := &consuldp.Config{ Consul: &consuldp.ConsulConfig{ - Addresses: flagOpts.addresses, - GRPCPort: flagOpts.grpcPort, + Addresses: stringVal(flagOpts.dataplaneConfig.Consul.Addresses), + GRPCPort: intVal(flagOpts.dataplaneConfig.Consul.GRPCPort), ServerWatchDisabled: true, Credentials: &consuldp.CredentialsConfig{ Type: "static", @@ -164,7 +166,7 @@ func TestConfigGeneration(t *testing.T) { Partition: "default", }, Logging: &consuldp.LoggingConfig{ - Name: "consul-dataplane", + Name: DefaultLogName, LogJSON: false, LogLevel: "WARN", }, @@ -188,7 +190,6 @@ func TestConfigGeneration(t *testing.T) { EnvoyDrainTimeSeconds: 30, GracefulPort: 20300, DumpEnvoyConfigOnExitEnabled: true, - ExtraArgs: []string{}, }, Telemetry: &consuldp.TelemetryConfig{ UseCentralConfig: true, @@ -209,10 +210,10 @@ func TestConfigGeneration(t *testing.T) { }, { desc: "able to generate config properly when config file is given without flag inputs", - flagOpts: func() *FlagOpts { + flagOpts: func() (*FlagOpts, error) { opts := &FlagOpts{} opts.configFile = "test.json" - return opts + return opts, nil }, writeConfigFile: func() error { inputJson := `{ @@ -244,21 +245,59 @@ func TestConfigGeneration(t *testing.T) { return nil }, assertConfig: func(cfg *consuldp.Config, flagOpts *FlagOpts) bool { - expectedCfg := buildDefaultConsulDPConfig() - expectedCfg.Consul.Addresses = "consul_server.dc1" - expectedCfg.Consul.GRPCPort = 8502 - expectedCfg.Consul.ServerWatchDisabled = false - expectedCfg.Service.NodeName = "test-node-1" - expectedCfg.Service.ServiceID = "frontend-service-sidecar-proxy" - expectedCfg.Service.Namespace = "default" - expectedCfg.Service.Partition = "default" - expectedCfg.Envoy.AdminBindAddress = "127.0.0.1" - expectedCfg.Envoy.AdminBindPort = 19000 - expectedCfg.Logging.LogJSON = false - expectedCfg.Logging.LogLevel = "INFO" - expectedCfg.Telemetry.UseCentralConfig = false + expectedCfg := &consuldp.Config{ + Consul: &consuldp.ConsulConfig{ + Addresses: "consul_server.dc1", + GRPCPort: 8502, + ServerWatchDisabled: false, + Credentials: &consuldp.CredentialsConfig{ + Static: consuldp.StaticCredentialsConfig{}, + Login: consuldp.LoginCredentialsConfig{}, + }, + TLS: &consuldp.TLSConfig{}, + }, + Service: &consuldp.ServiceConfig{ + NodeName: "test-node-1", + Namespace: "default", + ServiceID: "frontend-service-sidecar-proxy", + Partition: "default", + }, + Logging: &consuldp.LoggingConfig{ + Name: DefaultLogName, + LogJSON: false, + LogLevel: "INFO", + }, + DNSServer: &consuldp.DNSServerConfig{ + BindAddr: "127.0.0.1", + Port: -1, + }, + XDSServer: &consuldp.XDSServer{ + BindAddress: "127.0.0.1", + BindPort: 0, + }, + Envoy: &consuldp.EnvoyConfig{ + AdminBindAddress: "127.0.0.1", + AdminBindPort: 19000, + ReadyBindPort: 0, + EnvoyConcurrency: 2, + EnvoyDrainStrategy: "immediate", + ShutdownDrainListenersEnabled: false, + GracefulShutdownPath: "/graceful_shutdown", + EnvoyDrainTimeSeconds: 30, + GracefulPort: 20300, + DumpEnvoyConfigOnExitEnabled: false, + }, + Telemetry: &consuldp.TelemetryConfig{ + UseCentralConfig: true, + Prometheus: consuldp.PrometheusTelemetryConfig{ + RetentionTime: 60 * time.Second, + ScrapePath: "/metrics", + MergePort: 20100, + }, + }, + } - return reflect.DeepEqual(cfg.Telemetry, expectedCfg.Telemetry) + return reflect.DeepEqual(cfg, expectedCfg) }, cleanup: func() { os.Remove("test.json") @@ -267,14 +306,20 @@ func TestConfigGeneration(t *testing.T) { }, { desc: "test whether CLI flag values override the file values", - flagOpts: func() *FlagOpts { - opts := generateFlagOpts() + flagOpts: func() (*FlagOpts, error) { + opts, err := generateFlagOpts() + if err != nil { + return nil, err + } opts.configFile = "test.json" - opts.logLevel = "info" - opts.logJSON = false + opts.dataplaneConfig.Logging.LogLevel = strReference("info") + opts.dataplaneConfig.Logging.LogJSON = boolReference(false) + opts.dataplaneConfig.Consul.Credentials.Login.Meta = map[string]string{ + "key1": "value1", + } - return opts + return opts, nil }, writeConfigFile: func() error { inputJson := `{ @@ -308,8 +353,8 @@ func TestConfigGeneration(t *testing.T) { assertConfig: func(cfg *consuldp.Config, flagOpts *FlagOpts) bool { expectedCfg := &consuldp.Config{ Consul: &consuldp.ConsulConfig{ - Addresses: flagOpts.addresses, - GRPCPort: flagOpts.grpcPort, + Addresses: stringVal(flagOpts.dataplaneConfig.Consul.Addresses), + GRPCPort: intVal(flagOpts.dataplaneConfig.Consul.GRPCPort), ServerWatchDisabled: true, Credentials: &consuldp.CredentialsConfig{ Type: "static", @@ -317,16 +362,11 @@ func TestConfigGeneration(t *testing.T) { Token: "test-token-123", }, Login: consuldp.LoginCredentialsConfig{ + AuthMethod: "test-iam-auth", + BearerToken: "bearer-login", Meta: map[string]string{ - "key-1": "value-1", - "key-2": "value-2", + "key1": "value1", }, - AuthMethod: "test-iam-auth", - BearerToken: "bearer-login", - BearerTokenPath: "/consul/bearertokenpath/", - Namespace: "default", - Partition: "default", - Datacenter: "dc100", }, }, TLS: &consuldp.TLSConfig{ @@ -346,17 +386,17 @@ func TestConfigGeneration(t *testing.T) { Partition: "default", }, Logging: &consuldp.LoggingConfig{ - Name: "consul-dataplane", - LogJSON: true, + Name: DefaultLogName, + LogJSON: false, LogLevel: "INFO", }, DNSServer: &consuldp.DNSServerConfig{ - BindAddr: "127.0.0.2", + BindAddr: "127.0.0.1", Port: 8604, }, XDSServer: &consuldp.XDSServer{ BindAddress: "127.0.1.0", - BindPort: 6060, + BindPort: 0, }, Envoy: &consuldp.EnvoyConfig{ AdminBindAddress: "127.0.1.0", @@ -369,8 +409,7 @@ func TestConfigGeneration(t *testing.T) { GracefulShutdownPath: "/graceful_shutdown", EnvoyDrainTimeSeconds: 30, GracefulPort: 20300, - DumpEnvoyConfigOnExitEnabled: true, - ExtraArgs: []string{}, + DumpEnvoyConfigOnExitEnabled: false, }, Telemetry: &consuldp.TelemetryConfig{ UseCentralConfig: true, @@ -385,7 +424,7 @@ func TestConfigGeneration(t *testing.T) { }, } - return reflect.DeepEqual(cfg.Telemetry, expectedCfg.Telemetry) + return reflect.DeepEqual(cfg, expectedCfg) }, cleanup: func() { os.Remove("test.json") @@ -396,13 +435,18 @@ func TestConfigGeneration(t *testing.T) { for _, tc := range testCases { t.Run(tc.desc, func(t *testing.T) { - opts := tc.flagOpts() + if tc.cleanup != nil { + t.Cleanup(tc.cleanup) + } + + opts, err := tc.flagOpts() + require.NoError(t, err) if tc.writeConfigFile != nil { require.NoError(t, tc.writeConfigFile()) } - cfg, err := opts.buildDataplaneConfig() + cfg, err := opts.buildDataplaneConfig(nil) if tc.wantErr { require.Error(t, err) @@ -410,59 +454,94 @@ func TestConfigGeneration(t *testing.T) { require.NoError(t, err) require.True(t, tc.assertConfig(cfg, opts)) } - - if tc.cleanup != nil { - tc.cleanup() - } }) } } -func generateFlagOpts() *FlagOpts { - return &FlagOpts{ - addresses: fmt.Sprintf("consul.address.server_%d", rand.Int()), - grpcPort: rand.Int(), - serverWatchDisabled: true, - - tlsDisabled: false, - tlsCACertsPath: "/consul/", - tlsCertFile: "ca-cert.pem", - tlsKeyFile: "key.pem", - tlsServerName: "tls-server-name", - tlsInsecureSkipVerify: true, - - logLevel: "warn", - logJSON: true, - - nodeName: "test-node-dc1", - nodeID: "dc1.node.id", - namespace: "default", - serviceID: "node1.service1", - serviceIDPath: "/consul/service-id", - partition: "default", +func generateFlagOpts() (*FlagOpts, error) { + data := ` + { + "consul": { + "addresses": "` + fmt.Sprintf("consul.address.server_%d", rand.Int()) + `", + "grpcPort": ` + fmt.Sprintf("%d", rand.Int()) + `, + "serverWatchDisabled": true, + "tls": { + "disabled": false, + "caCertsPath": "/consul/", + "certFile": "ca-cert.pem", + "keyFile": "key.pem", + "serverName": "tls-server-name", + "insecureSkipVerify": true + }, + "credentials": { + "type": "static", + "static": { + "token": "test-token-123" + }, + "login": { + "authMethod": "test-iam-auth", + "bearerToken": "bearer-login" + } + } + }, + "service": { + "nodeName": "test-node-dc1", + "nodeID": "dc1.node.id", + "namespace": "default", + "serviceID": "node1.service1", + "partition": "default" + }, + "logging": { + "logJSON": true, + "logLevel": "warn" + }, + "telemetry": { + "useCentralConfig": true, + "prometheus": { + "retentionTime": "10s", + "scrapePath": "/metrics", + "mergePort": 12000, + "caCertsPath": "/consul/", + "certFile": "prom-ca-cert.pem", + "keyFile": "prom-key.pem" + } + }, + "envoy": { + "adminBindAddress": "127.0.1.0", + "adminBindPort": 18000, + "readyBindAddress": "127.0.1.0", + "readyBindPort": 18003, + "concurrency": 4, + "drainStrategy": "test-strategy", + "shutdownDrainListenersEnabled": true + }, + "xdsServer": { + "bindAddress": "127.0.1.0" + }, + "dnsServer": { + "bindPort": 8604 + } + }` - credentialType: "static", - token: "test-token-123", - loginAuthMethod: "test-iam-auth", - loginBearerToken: "bearer-login", + var configFlags *DataplaneConfigFlags + err := json.Unmarshal([]byte(data), &configFlags) + if err != nil { + return nil, err + } - adminBindAddr: "127.0.1.0", - adminBindPort: 18000, - readyBindAddr: "127.0.1.0", - readyBindPort: 18003, - envoyConcurrency: 4, - envoyDrainStrategy: "test-strategy", - shutdownDrainListenersEnabled: true, + return &FlagOpts{ + dataplaneConfig: *configFlags, + }, nil +} - xdsBindAddr: "127.0.1.0", - consulDNSPort: 8604, +func strReference(s string) *string { + return &s +} - promMergePort: 12000, +func boolReference(b bool) *bool { + return &b +} - useCentralTelemetryConfig: true, - promRetentionTime: 10 * time.Second, - promCACertsPath: "/consul/", - promCertFile: "prom-ca-cert.pem", - promKeyFile: "prom-key.pem", - } +func intReference(i int) *int { + return &i } diff --git a/cmd/consul-dataplane/duration.go b/cmd/consul-dataplane/duration.go new file mode 100644 index 00000000..a4cff925 --- /dev/null +++ b/cmd/consul-dataplane/duration.go @@ -0,0 +1,37 @@ +package main + +import ( + "encoding/json" + "fmt" + "time" +) + +// Duration wraps the time.duration field to support +// unmarshalling JSON values to the time.duration fields +// in destination structs +type Duration struct { + Duration time.Duration +} + +func (duration *Duration) UnmarshalJSON(b []byte) error { + var unmarshalledJson interface{} + + err := json.Unmarshal(b, &unmarshalledJson) + if err != nil { + return err + } + + switch value := unmarshalledJson.(type) { + case float64: + duration.Duration = time.Duration(value) + case string: + duration.Duration, err = time.ParseDuration(value) + if err != nil { + return err + } + default: + return fmt.Errorf("invalid duration: %#v", unmarshalledJson) + } + + return nil +} diff --git a/cmd/consul-dataplane/duration_test.go b/cmd/consul-dataplane/duration_test.go new file mode 100644 index 00000000..2f9961d6 --- /dev/null +++ b/cmd/consul-dataplane/duration_test.go @@ -0,0 +1,63 @@ +package main + +import ( + "encoding/json" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +type DurationTestInput struct { + Key1 string `json:"key1"` + Key2 string `json:"key2"` + Duration *Duration `json:"duration,omitempty"` +} + +func TestUnmarshalForStringBasedDurationInput(t *testing.T) { + data := ` + { + "key1": "value1", + "key2": "value2", + "duration": "100s" + } + ` + + d, err := time.ParseDuration("100s") + require.NoError(t, err) + + expectedDuration := &Duration{ + Duration: d, + } + + var durationTestInput *DurationTestInput + err = json.Unmarshal([]byte(data), &durationTestInput) + require.NoError(t, err) + + require.NotNil(t, durationTestInput) + require.NotNil(t, durationTestInput.Duration) + require.Equal(t, expectedDuration.Duration, durationTestInput.Duration.Duration) +} + +func TestUnmarshalForFloatBasedDurationInput(t *testing.T) { + data := ` + { + "key1": "value1", + "key2": "value2", + "duration": 4.5 + } + ` + + in := 4.5 + expectedDuration := &Duration{ + Duration: time.Duration(in), + } + + var durationTestInput *DurationTestInput + err := json.Unmarshal([]byte(data), &durationTestInput) + require.NoError(t, err) + + require.NotNil(t, durationTestInput) + require.NotNil(t, durationTestInput.Duration) + require.Equal(t, expectedDuration.Duration, durationTestInput.Duration.Duration) +} diff --git a/cmd/consul-dataplane/env.go b/cmd/consul-dataplane/env.go index 128c7ba4..982dca96 100644 --- a/cmd/consul-dataplane/env.go +++ b/cmd/consul-dataplane/env.go @@ -12,29 +12,71 @@ import ( ) var ( - asInt = strconv.Atoi - asBool = strconv.ParseBool - asDuration = time.ParseDuration - asString = func(s string) (string, error) { return s, nil } + asInt = func(s string) (*int, error) { + if s == "" { + return nil, nil + } + + n, err := strconv.Atoi(s) + if err != nil { + return nil, err + } + + return &n, nil + } + + asBool = func(s string) (*bool, error) { + if s == "" { + return nil, nil + } + + b, err := strconv.ParseBool(s) + if err != nil { + return nil, err + } + + return &b, nil + } + + asDuration = func(s string) (*Duration, error) { + if s == "" { + return nil, nil + } + + t, err := time.ParseDuration(s) + if err != nil { + return nil, err + } + + return &Duration{Duration: t}, nil + } + + asString = func(s string) (*string, error) { + if s == "" { + return nil, nil + } + + return &s, nil + } ) -func parseEnv[T any](name string, defaultVal T, parseFn func(string) (T, error)) T { - val, err := parseEnvError(name, defaultVal, parseFn) +func parseEnv[T any](name string, parseFn func(string) (*T, error)) *T { + val, err := parseEnvError(name, parseFn) if err != nil { log.Fatal(err) } return val } -func parseEnvError[T any](name string, defaultVal T, parseFn func(string) (T, error)) (T, error) { +func parseEnvError[T any](name string, parseFn func(string) (*T, error)) (*T, error) { valStr, ok := os.LookupEnv(name) if !ok { // Env var is not present in the environment. - return defaultVal, nil + return nil, nil } valT, err := parseFn(valStr) if err != nil { - return defaultVal, fmt.Errorf("unable to parse environment variable %s=%s as %T", name, valStr, valT) + return nil, fmt.Errorf("unable to parse environment variable %s=%s as %T", name, valStr, valT) } return valT, nil } diff --git a/cmd/consul-dataplane/flags.go b/cmd/consul-dataplane/flags.go index 5fb2b59d..640d8a78 100644 --- a/cmd/consul-dataplane/flags.go +++ b/cmd/consul-dataplane/flags.go @@ -12,40 +12,41 @@ import ( "flag" "fmt" "log" + "strconv" "time" ) -func StringVar(p *string, name, defaultVal, env, usage string) { +func StringVar(fs *flag.FlagSet, p **string, name, env, usage string) { usage = includeEnvUsage(env, usage) // The order here is important. The flag will sets the value to the default // value, prior to flag parsing. So after the flag is created, we override // the value to the env var, if it is set, or otherwise the defaultVal. - flag.StringVar(p, name, defaultVal, usage) - *p = parseEnv(env, defaultVal, asString) + fs.Var(newStringPtrValue(p), name, usage) + *p = parseEnv(env, asString) } -func IntVar(p *int, name string, defaultVal int, env, usage string) { +func IntVar(fs *flag.FlagSet, p **int, name, env, usage string) { usage = includeEnvUsage(env, usage) - flag.IntVar(p, name, defaultVal, usage) - *p = parseEnv(env, defaultVal, asInt) + fs.Var(newIntPtrValue(p), name, usage) + *p = parseEnv(env, asInt) } -func BoolVar(p *bool, name string, defaultVal bool, env, usage string) { +func BoolVar(fs *flag.FlagSet, p **bool, name, env, usage string) { usage = includeEnvUsage(env, usage) - flag.BoolVar(p, name, defaultVal, usage) - *p = parseEnv(env, defaultVal, asBool) + fs.Var(newBoolPtrValue(p), name, usage) + *p = parseEnv(env, asBool) } -func DurationVar(p *time.Duration, name string, defaultVal time.Duration, env, usage string) { +func DurationVar(fs *flag.FlagSet, p **Duration, name, env, usage string) { usage = includeEnvUsage(env, usage) - flag.DurationVar(p, name, defaultVal, usage) - *p = parseEnv(env, defaultVal, asDuration) + fs.Var(newDurationPtrValue(p), name, usage) + *p = parseEnv(env, asDuration) } // MapVar supports repeated flags and the environment variables numbered {1,9}. -func MapVar(v flag.Value, name, env, usage string) { +func MapVar(fs *flag.FlagSet, v flag.Value, name, env, usage string) { usage = includeEnvUsage(fmt.Sprintf("%s{1,9}", env), usage) - flag.Var(v, name, usage) + fs.Var(v, name, usage) for varName, value := range multiValueEnv(env) { err := v.Set(value) if err != nil { @@ -57,3 +58,170 @@ func MapVar(v flag.Value, name, env, usage string) { func includeEnvUsage(env, usage string) string { return fmt.Sprintf("%s Environment variable: %s.", usage, env) } + +// stringPtrValue is a flag.Value which stores the value in a *string. +// If the value was not set the pointer is nil. +type stringPtrValue struct { + v **string + b bool +} + +func newStringPtrValue(p **string) *stringPtrValue { + return &stringPtrValue{p, false} +} + +func (s *stringPtrValue) Set(val string) error { + *s.v, s.b = &val, true + return nil +} + +func (s *stringPtrValue) Get() interface{} { + if s.b { + return *s.v + } + return (*string)(nil) +} + +func (s *stringPtrValue) String() string { + if s.b { + return **s.v + } + return "" +} + +// intPtrValue is a flag.Value which stores the value in a *int if it +// can be parsed with strconv.Atoi. If the value was not set the pointer +// is nil. +type intPtrValue struct { + v **int + b bool +} + +func newIntPtrValue(p **int) *intPtrValue { + return &intPtrValue{p, false} +} + +func (s *intPtrValue) Set(val string) error { + n, err := strconv.Atoi(val) + if err != nil { + return err + } + *s.v, s.b = &n, true + return nil +} + +func (s *intPtrValue) Get() interface{} { + if s.b { + return *s.v + } + return (*int)(nil) +} + +func (s *intPtrValue) String() string { + if s.b { + return strconv.Itoa(**s.v) + } + return "" +} + +// boolPtrValue is a flag.Value which stores the value in a *bool if it +// can be parsed with strconv.ParseBool. If the value was not set the +// pointer is nil. +type boolPtrValue struct { + v **bool + b bool +} + +func newBoolPtrValue(p **bool) *boolPtrValue { + return &boolPtrValue{p, false} +} + +func (s *boolPtrValue) IsBoolFlag() bool { return true } + +func (s *boolPtrValue) Set(val string) error { + b, err := strconv.ParseBool(val) + if err != nil { + return err + } + *s.v, s.b = &b, true + return nil +} + +func (s *boolPtrValue) Get() interface{} { + if s.b { + return *s.v + } + return (*bool)(nil) +} + +func (s *boolPtrValue) String() string { + if s.b { + return strconv.FormatBool(**s.v) + } + return "" +} + +// durationPtrValue is a flag.Value which stores the value in a +// *time.Duration if it can be parsed with time.ParseDuration. If the +// value was not set the pointer is nil. +type durationPtrValue struct { + v **Duration + b bool +} + +func newDurationPtrValue(p **Duration) *durationPtrValue { + return &durationPtrValue{p, false} +} + +func (s *durationPtrValue) Set(val string) error { + d, err := time.ParseDuration(val) + if err != nil { + return err + } + *s.v, s.b = &Duration{Duration: d}, true + return nil +} + +func (s *durationPtrValue) Get() interface{} { + if s.b { + return *s.v + } + return (*time.Duration)(nil) +} + +func (s *durationPtrValue) String() string { + if s.b { + return (*(*s).v).Duration.String() + } + return "" +} + +func durationVal(t *Duration) time.Duration { + if t == nil { + return 0 + } + + return t.Duration +} + +func stringVal(s *string) string { + if s == nil { + return "" + } + + return *s +} + +func intVal(v *int) int { + if v == nil { + return 0 + } + return *v +} + +func boolVal(v *bool) bool { + if v == nil { + return false + } + return *v +} diff --git a/cmd/consul-dataplane/main.go b/cmd/consul-dataplane/main.go index 1de7bb1e..5988d2ca 100644 --- a/cmd/consul-dataplane/main.go +++ b/cmd/consul-dataplane/main.go @@ -19,97 +19,101 @@ import ( var ( flagOpts *FlagOpts + flags *flag.FlagSet ) func init() { + flags = flag.NewFlagSet("", flag.ContinueOnError) flagOpts = &FlagOpts{} - flag.BoolVar(&flagOpts.printVersion, "version", false, "Prints the current version of consul-dataplane.") + flags.BoolVar(&flagOpts.printVersion, "version", false, "Prints the current version of consul-dataplane.") - StringVar(&flagOpts.addresses, "addresses", "", "DP_CONSUL_ADDRESSES", "Consul server gRPC addresses. Value can be:\n"+ + StringVar(flags, &flagOpts.dataplaneConfig.Consul.Addresses, "addresses", "DP_CONSUL_ADDRESSES", "Consul server gRPC addresses. Value can be:\n"+ "1. A DNS name that resolves to server addresses or the DNS name of a load balancer in front of the Consul servers; OR\n"+ "2. An executable command in the format, 'exec='. The executable\n"+ " a) on success - should exit 0 and print to stdout whitespace delimited IP (v4/v6) addresses\n"+ " b) on failure - exit with a non-zero code and optionally print an error message of up to 1024 bytes to stderr.\n"+ " Refer to https://github.com/hashicorp/go-netaddrs#summary for more details and examples.\n") - IntVar(&flagOpts.grpcPort, "grpc-port", DefaultGRPCPort, "DP_CONSUL_GRPC_PORT", "The Consul server gRPC port to which consul-dataplane connects.") + IntVar(flags, &flagOpts.dataplaneConfig.Consul.GRPCPort, "grpc-port", "DP_CONSUL_GRPC_PORT", "The Consul server gRPC port to which consul-dataplane connects.") - BoolVar(&flagOpts.serverWatchDisabled, "server-watch-disabled", DefaultServerWatchDisabled, "DP_SERVER_WATCH_DISABLED", "Setting this prevents consul-dataplane from consuming the server update stream. This is useful for situations where Consul servers are behind a load balancer.") + BoolVar(flags, &flagOpts.dataplaneConfig.Consul.ServerWatchDisabled, "server-watch-disabled", "DP_SERVER_WATCH_DISABLED", "Setting this prevents consul-dataplane from consuming the server update stream. This is useful for situations where Consul servers are behind a load balancer.") - StringVar(&flagOpts.logLevel, "log-level", DefaultLogLevel, "DP_LOG_LEVEL", "Log level of the messages to print. "+ + StringVar(flags, &flagOpts.dataplaneConfig.Logging.LogLevel, "log-level", "DP_LOG_LEVEL", "Log level of the messages to print. "+ "Available log levels are \"trace\", \"debug\", \"info\", \"warn\", and \"error\".") - BoolVar(&flagOpts.logJSON, "log-json", DefaultLogJSON, "DP_LOG_JSON", "Enables log messages in JSON format.") - - StringVar(&flagOpts.nodeName, "service-node-name", "", "DP_SERVICE_NODE_NAME", "The name of the Consul node to which the proxy service instance is registered.") - StringVar(&flagOpts.nodeID, "service-node-id", "", "DP_SERVICE_NODE_ID", "The ID of the Consul node to which the proxy service instance is registered.") - StringVar(&flagOpts.serviceID, "proxy-service-id", "", "DP_PROXY_SERVICE_ID", "The proxy service instance's ID.") - StringVar(&flagOpts.serviceIDPath, "proxy-service-id-path", "", "DP_PROXY_SERVICE_ID_PATH", "The path to a file containing the proxy service instance's ID.") - StringVar(&flagOpts.namespace, "service-namespace", "", "DP_SERVICE_NAMESPACE", "The Consul Enterprise namespace in which the proxy service instance is registered.") - StringVar(&flagOpts.partition, "service-partition", "", "DP_SERVICE_PARTITION", "The Consul Enterprise partition in which the proxy service instance is registered.") - - StringVar(&flagOpts.credentialType, "credential-type", "", "DP_CREDENTIAL_TYPE", "The type of credentials, either static or login, used to authenticate with Consul servers.") - StringVar(&flagOpts.token, "static-token", "", "DP_CREDENTIAL_STATIC_TOKEN", "The ACL token used to authenticate requests to Consul servers when -credential-type is set to static.") - StringVar(&flagOpts.loginAuthMethod, "login-auth-method", "", "DP_CREDENTIAL_LOGIN_AUTH_METHOD", "The auth method used to log in.") - StringVar(&flagOpts.loginNamespace, "login-namespace", "", "DP_CREDENTIAL_LOGIN_NAMESPACE", "The Consul Enterprise namespace containing the auth method.") - StringVar(&flagOpts.loginPartition, "login-partition", "", "DP_CREDENTIAL_LOGIN_PARTITION", "The Consul Enterprise partition containing the auth method.") - StringVar(&flagOpts.loginDatacenter, "login-datacenter", "", "DP_CREDENTIAL_LOGIN_DATACENTER", "The datacenter containing the auth method.") - StringVar(&flagOpts.loginBearerToken, "login-bearer-token", "", "DP_CREDENTIAL_LOGIN_BEARER_TOKEN", "The bearer token presented to the auth method.") - StringVar(&flagOpts.loginBearerTokenPath, "login-bearer-token-path", "", "DP_CREDENTIAL_LOGIN_BEARER_TOKEN_PATH", "The path to a file containing the bearer token presented to the auth method.") - MapVar((*FlagMapValue)(&flagOpts.loginMeta), "login-meta", "DP_CREDENTIAL_LOGIN_META", `A set of key/value pairs to attach to the ACL token. Each pair is formatted as "=". This flag may be passed multiple times.`) - - BoolVar(&flagOpts.useCentralTelemetryConfig, "telemetry-use-central-config", DefaultUseCentralTelemetryConfig, "DP_TELEMETRY_USE_CENTRAL_CONFIG", "Controls whether the proxy applies the central telemetry configuration.") - - DurationVar(&flagOpts.promRetentionTime, "telemetry-prom-retention-time", DefaultPromRetentionTime, "DP_TELEMETRY_PROM_RETENTION_TIME", "The duration for prometheus metrics aggregation.") - StringVar(&flagOpts.promCACertsPath, "telemetry-prom-ca-certs-path", "", "DP_TELEMETRY_PROM_CA_CERTS_PATH", "The path to a file or directory containing CA certificates used to verify the Prometheus server's certificate.") - StringVar(&flagOpts.promKeyFile, "telemetry-prom-key-file", "", "DP_TELEMETRY_PROM_KEY_FILE", "The path to the client private key used to serve Prometheus metrics.") - StringVar(&flagOpts.promCertFile, "telemetry-prom-cert-file", "", "DP_TELEMETRY_PROM_CERT_FILE", "The path to the client certificate used to serve Prometheus metrics.") - StringVar(&flagOpts.promServiceMetricsURL, "telemetry-prom-service-metrics-url", "", "DP_TELEMETRY_PROM_SERVICE_METRICS_URL", "Prometheus metrics at this URL are scraped and included in Consul Dataplane's main Prometheus metrics.") - StringVar(&flagOpts.promScrapePath, "telemetry-prom-scrape-path", DefaultPromScrapePath, "DP_TELEMETRY_PROM_SCRAPE_PATH", "The URL path where Envoy serves Prometheus metrics.") - IntVar(&flagOpts.promMergePort, "telemetry-prom-merge-port", DefaultPromMergePort, "DP_TELEMETRY_PROM_MERGE_PORT", "The port to serve merged Prometheus metrics.") - - StringVar(&flagOpts.adminBindAddr, "envoy-admin-bind-address", DefaultEnvoyAdminBindAddr, "DP_ENVOY_ADMIN_BIND_ADDRESS", "The address on which the Envoy admin server is available.") - IntVar(&flagOpts.adminBindPort, "envoy-admin-bind-port", DefaultEnvoyAdminBindPort, "DP_ENVOY_ADMIN_BIND_PORT", "The port on which the Envoy admin server is available.") - StringVar(&flagOpts.readyBindAddr, "envoy-ready-bind-address", "", "DP_ENVOY_READY_BIND_ADDRESS", "The address on which Envoy's readiness probe is available.") - IntVar(&flagOpts.readyBindPort, "envoy-ready-bind-port", DefaultEnvoyReadyBindPort, "DP_ENVOY_READY_BIND_PORT", "The port on which Envoy's readiness probe is available.") - IntVar(&flagOpts.envoyConcurrency, "envoy-concurrency", DefaultEnvoyConcurrency, "DP_ENVOY_CONCURRENCY", "The number of worker threads that Envoy uses.") - IntVar(&flagOpts.envoyDrainTimeSeconds, "envoy-drain-time-seconds", DefaultEnvoyDrainTimeSeconds, "DP_ENVOY_DRAIN_TIME", "The time in seconds for which Envoy will drain connections.") - StringVar(&flagOpts.envoyDrainStrategy, "envoy-drain-strategy", DefaultEnvoyDrainStrategy, "DP_ENVOY_DRAIN_STRATEGY", "The behaviour of Envoy during the drain sequence. Determines whether all open connections should be encouraged to drain immediately or to increase the percentage gradually as the drain time elapses.") - - StringVar(&flagOpts.xdsBindAddr, "xds-bind-addr", DefaultXDSBindAddr, "DP_XDS_BIND_ADDR", "The address on which the Envoy xDS server is available.") - IntVar(&flagOpts.xdsBindPort, "xds-bind-port", DefaultXDSBindPort, "DP_XDS_BIND_PORT", "The port on which the Envoy xDS server is available.") - - BoolVar(&flagOpts.tlsDisabled, "tls-disabled", DefaultTLSDisabled, "DP_TLS_DISABLED", "Communicate with Consul servers over a plaintext connection. Useful for testing, but not recommended for production.") - StringVar(&flagOpts.tlsCACertsPath, "ca-certs", "", "DP_CA_CERTS", "The path to a file or directory containing CA certificates used to verify the server's certificate.") - StringVar(&flagOpts.tlsCertFile, "tls-cert", "", "DP_TLS_CERT", "The path to a client certificate file. This is required if tls.grpc.verify_incoming is enabled on the server.") - StringVar(&flagOpts.tlsKeyFile, "tls-key", "", "DP_TLS_KEY", "The path to a client private key file. This is required if tls.grpc.verify_incoming is enabled on the server.") - StringVar(&flagOpts.tlsServerName, "tls-server-name", "", "DP_TLS_SERVER_NAME", "The hostname to expect in the server certificate's subject. This is required if -addresses is not a DNS name.") - BoolVar(&flagOpts.tlsInsecureSkipVerify, "tls-insecure-skip-verify", DefaultTLSInsecureSkipVerify, "DP_TLS_INSECURE_SKIP_VERIFY", "Do not verify the server's certificate. Useful for testing, but not recommended for production.") - - StringVar(&flagOpts.consulDNSBindAddr, "consul-dns-bind-addr", DefaultDNSBindAddr, "DP_CONSUL_DNS_BIND_ADDR", "The address that will be bound to the consul dns proxy.") - IntVar(&flagOpts.consulDNSPort, "consul-dns-bind-port", DefaultDNSBindPort, "DP_CONSUL_DNS_BIND_PORT", "The port the consul dns proxy will listen on. By default -1 disables the dns proxy") + BoolVar(flags, &flagOpts.dataplaneConfig.Logging.LogJSON, "log-json", "DP_LOG_JSON", "Enables log messages in JSON format.") + + StringVar(flags, &flagOpts.dataplaneConfig.Service.NodeName, "service-node-name", "DP_SERVICE_NODE_NAME", "The name of the Consul node to which the proxy service instance is registered.") + StringVar(flags, &flagOpts.dataplaneConfig.Service.NodeID, "service-node-id", "DP_SERVICE_NODE_ID", "The ID of the Consul node to which the proxy service instance is registered.") + StringVar(flags, &flagOpts.dataplaneConfig.Service.ServiceID, "proxy-service-id", "DP_PROXY_SERVICE_ID", "The proxy service instance's ID.") + StringVar(flags, &flagOpts.dataplaneConfig.Service.ServiceIDPath, "proxy-service-id-path", "DP_PROXY_SERVICE_ID_PATH", "The path to a file containing the proxy service instance's ID.") + StringVar(flags, &flagOpts.dataplaneConfig.Service.Namespace, "service-namespace", "DP_SERVICE_NAMESPACE", "The Consul Enterprise namespace in which the proxy service instance is registered.") + StringVar(flags, &flagOpts.dataplaneConfig.Service.Partition, "service-partition", "DP_SERVICE_PARTITION", "The Consul Enterprise partition in which the proxy service instance is registered.") + + StringVar(flags, &flagOpts.dataplaneConfig.Consul.Credentials.Type, "credential-type", "DP_CREDENTIAL_TYPE", "The type of credentials, either static or login, used to authenticate with Consul servers.") + StringVar(flags, &flagOpts.dataplaneConfig.Consul.Credentials.Static.Token, "static-token", "DP_CREDENTIAL_STATIC_TOKEN", "The ACL token used to authenticate requests to Consul servers when -credential-type is set to static.") + StringVar(flags, &flagOpts.dataplaneConfig.Consul.Credentials.Login.AuthMethod, "login-auth-method", "DP_CREDENTIAL_LOGIN_AUTH_METHOD", "The auth method used to log in.") + StringVar(flags, &flagOpts.dataplaneConfig.Consul.Credentials.Login.Namespace, "login-namespace", "DP_CREDENTIAL_LOGIN_NAMESPACE", "The Consul Enterprise namespace containing the auth method.") + StringVar(flags, &flagOpts.dataplaneConfig.Consul.Credentials.Login.Partition, "login-partition", "DP_CREDENTIAL_LOGIN_PARTITION", "The Consul Enterprise partition containing the auth method.") + StringVar(flags, &flagOpts.dataplaneConfig.Consul.Credentials.Login.Datacenter, "login-datacenter", "DP_CREDENTIAL_LOGIN_DATACENTER", "The datacenter containing the auth method.") + StringVar(flags, &flagOpts.dataplaneConfig.Consul.Credentials.Login.BearerToken, "login-bearer-token", "DP_CREDENTIAL_LOGIN_BEARER_TOKEN", "The bearer token presented to the auth method.") + StringVar(flags, &flagOpts.dataplaneConfig.Consul.Credentials.Login.BearerTokenPath, "login-bearer-token-path", "DP_CREDENTIAL_LOGIN_BEARER_TOKEN_PATH", "The path to a file containing the bearer token presented to the auth method.") + MapVar(flags, (*FlagMapValue)(&flagOpts.dataplaneConfig.Consul.Credentials.Login.Meta), "login-meta", "DP_CREDENTIAL_LOGIN_META", `A set of key/value pairs to attach to the ACL token. Each pair is formatted as "=". This flag may be passed multiple times.`) + + BoolVar(flags, &flagOpts.dataplaneConfig.Telemetry.UseCentralConfig, "telemetry-use-central-config", "DP_TELEMETRY_USE_CENTRAL_CONFIG", "Controls whether the proxy applies the central telemetry configuration.") + + DurationVar(flags, &flagOpts.dataplaneConfig.Telemetry.Prometheus.RetentionTime, "telemetry-prom-retention-time", "DP_TELEMETRY_PROM_RETENTION_TIME", "The duration for prometheus metrics aggregation.") + StringVar(flags, &flagOpts.dataplaneConfig.Telemetry.Prometheus.CACertsPath, "telemetry-prom-ca-certs-path", "DP_TELEMETRY_PROM_CA_CERTS_PATH", "The path to a file or directory containing CA certificates used to verify the Prometheus server's certificate.") + StringVar(flags, &flagOpts.dataplaneConfig.Telemetry.Prometheus.KeyFile, "telemetry-prom-key-file", "DP_TELEMETRY_PROM_KEY_FILE", "The path to the client private key used to serve Prometheus metrics.") + StringVar(flags, &flagOpts.dataplaneConfig.Telemetry.Prometheus.CertFile, "telemetry-prom-cert-file", "DP_TELEMETRY_PROM_CERT_FILE", "The path to the client certificate used to serve Prometheus metrics.") + StringVar(flags, &flagOpts.dataplaneConfig.Telemetry.Prometheus.ServiceMetricsURL, "telemetry-prom-service-metrics-url", "DP_TELEMETRY_PROM_SERVICE_METRICS_URL", "Prometheus metrics at this URL are scraped and included in Consul Dataplane's main Prometheus metrics.") + StringVar(flags, &flagOpts.dataplaneConfig.Telemetry.Prometheus.ScrapePath, "telemetry-prom-scrape-path", "DP_TELEMETRY_PROM_SCRAPE_PATH", "The URL path where Envoy serves Prometheus metrics.") + IntVar(flags, &flagOpts.dataplaneConfig.Telemetry.Prometheus.MergePort, "telemetry-prom-merge-port", "DP_TELEMETRY_PROM_MERGE_PORT", "The port to serve merged Prometheus metrics.") + + StringVar(flags, &flagOpts.dataplaneConfig.Envoy.AdminBindAddr, "envoy-admin-bind-address", "DP_ENVOY_ADMIN_BIND_ADDRESS", "The address on which the Envoy admin server is available.") + IntVar(flags, &flagOpts.dataplaneConfig.Envoy.AdminBindPort, "envoy-admin-bind-port", "DP_ENVOY_ADMIN_BIND_PORT", "The port on which the Envoy admin server is available.") + StringVar(flags, &flagOpts.dataplaneConfig.Envoy.ReadyBindAddr, "envoy-ready-bind-address", "DP_ENVOY_READY_BIND_ADDRESS", "The address on which Envoy's readiness probe is available.") + IntVar(flags, &flagOpts.dataplaneConfig.Envoy.ReadyBindPort, "envoy-ready-bind-port", "DP_ENVOY_READY_BIND_PORT", "The port on which Envoy's readiness probe is available.") + IntVar(flags, &flagOpts.dataplaneConfig.Envoy.Concurrency, "envoy-concurrency", "DP_ENVOY_CONCURRENCY", "The number of worker threads that Envoy uses.") + IntVar(flags, &flagOpts.dataplaneConfig.Envoy.DrainTimeSeconds, "envoy-drain-time-seconds", "DP_ENVOY_DRAIN_TIME", "The time in seconds for which Envoy will drain connections.") + StringVar(flags, &flagOpts.dataplaneConfig.Envoy.DrainStrategy, "envoy-drain-strategy", "DP_ENVOY_DRAIN_STRATEGY", "The behaviour of Envoy during the drain sequence. Determines whether all open connections should be encouraged to drain immediately or to increase the percentage gradually as the drain time elapses.") + + StringVar(flags, &flagOpts.dataplaneConfig.XDSServer.BindAddr, "xds-bind-addr", "DP_XDS_BIND_ADDR", "The address on which the Envoy xDS server is available.") + IntVar(flags, &flagOpts.dataplaneConfig.XDSServer.BindPort, "xds-bind-port", "DP_XDS_BIND_PORT", "The port on which the Envoy xDS server is available.") + + BoolVar(flags, &flagOpts.dataplaneConfig.Consul.TLS.Disabled, "tls-disabled", "DP_TLS_DISABLED", "Communicate with Consul servers over a plaintext connection. Useful for testing, but not recommended for production.") + StringVar(flags, &flagOpts.dataplaneConfig.Consul.TLS.CACertsPath, "ca-certs", "DP_CA_CERTS", "The path to a file or directory containing CA certificates used to verify the server's certificate.") + StringVar(flags, &flagOpts.dataplaneConfig.Consul.TLS.CertFile, "tls-cert", "DP_TLS_CERT", "The path to a client certificate file. This is required if tls.grpc.verify_incoming is enabled on the server.") + StringVar(flags, &flagOpts.dataplaneConfig.Consul.TLS.KeyFile, "tls-key", "DP_TLS_KEY", "The path to a client private key file. This is required if tls.grpc.verify_incoming is enabled on the server.") + StringVar(flags, &flagOpts.dataplaneConfig.Consul.TLS.ServerName, "tls-server-name", "DP_TLS_SERVER_NAME", "The hostname to expect in the server certificate's subject. This is required if -addresses is not a DNS name.") + BoolVar(flags, &flagOpts.dataplaneConfig.Consul.TLS.InsecureSkipVerify, "tls-insecure-skip-verify", "DP_TLS_INSECURE_SKIP_VERIFY", "Do not verify the server's certificate. Useful for testing, but not recommended for production.") + + StringVar(flags, &flagOpts.dataplaneConfig.DNSServer.BindAddr, "consul-dns-bind-addr", "DP_CONSUL_DNS_BIND_ADDR", "The address that will be bound to the consul dns proxy.") + IntVar(flags, &flagOpts.dataplaneConfig.DNSServer.BindPort, "consul-dns-bind-port", "DP_CONSUL_DNS_BIND_PORT", "The port the consul dns proxy will listen on. By default -1 disables the dns proxy") // Default is false because it will generally be configured appropriately by Helm // configuration or pod annotation. - BoolVar(&flagOpts.shutdownDrainListenersEnabled, "shutdown-drain-listeners", DefaultEnvoyShutdownDrainListenersEnabled, "DP_SHUTDOWN_DRAIN_LISTENERS", "Wait for proxy listeners to drain before terminating the proxy container.") + BoolVar(flags, &flagOpts.dataplaneConfig.Envoy.ShutdownDrainListenersEnabled, "shutdown-drain-listeners", "DP_SHUTDOWN_DRAIN_LISTENERS", "Wait for proxy listeners to drain before terminating the proxy container.") // Default is 0 because it will generally be configured appropriately by Helm // configuration or pod annotation. - IntVar(&flagOpts.shutdownGracePeriodSeconds, "shutdown-grace-period-seconds", DefaultEnvoyShutdownGracePeriodSeconds, "DP_SHUTDOWN_GRACE_PERIOD_SECONDS", "Amount of time to wait after receiving a SIGTERM signal before terminating the proxy.") - StringVar(&flagOpts.gracefulShutdownPath, "graceful-shutdown-path", DefaultGracefulShutdownPath, "DP_GRACEFUL_SHUTDOWN_PATH", "An HTTP path to serve the graceful shutdown endpoint.") - IntVar(&flagOpts.gracefulPort, "graceful-port", DefaultGracefulPort, "DP_GRACEFUL_PORT", "A port to serve HTTP endpoints for graceful shutdown.") + IntVar(flags, &flagOpts.dataplaneConfig.Envoy.ShutdownGracePeriodSeconds, "shutdown-grace-period-seconds", "DP_SHUTDOWN_GRACE_PERIOD_SECONDS", "Amount of time to wait after receiving a SIGTERM signal before terminating the proxy.") + StringVar(flags, &flagOpts.dataplaneConfig.Envoy.GracefulShutdownPath, "graceful-shutdown-path", "DP_GRACEFUL_SHUTDOWN_PATH", "An HTTP path to serve the graceful shutdown endpoint.") + IntVar(flags, &flagOpts.dataplaneConfig.Envoy.GracefulPort, "graceful-port", "DP_GRACEFUL_PORT", "A port to serve HTTP endpoints for graceful shutdown.") // Default is false, may be useful for debugging unexpected termination. - BoolVar(&flagOpts.dumpEnvoyConfigOnExitEnabled, "dump-envoy-config-on-exit", DefaultDumpEnvoyConfigOnExitEnabled, "DP_DUMP_ENVOY_CONFIG_ON_EXIT", "Call the Envoy /config_dump endpoint during consul-dataplane controlled shutdown.") + BoolVar(flags, &flagOpts.dataplaneConfig.Envoy.DumpEnvoyConfigOnExitEnabled, "dump-envoy-config-on-exit", "DP_DUMP_ENVOY_CONFIG_ON_EXIT", "Call the Envoy /config_dump endpoint during consul-dataplane controlled shutdown.") - StringVar(&flagOpts.configFile, "config-file", "", "DP_CONFIG_FILE", "The json config file for configuring consul data plane") + flags.StringVar(&flagOpts.configFile, "config-file", "", "The json config file for configuring consul data plane") } // validateFlags performs semantic validation of the flag values func validateFlags() { - switch strings.ToUpper(flagOpts.logLevel) { - case "TRACE", "DEBUG", "INFO", "WARN", "ERROR": - default: - log.Fatal("invalid log level. valid values - TRACE, DEBUG, INFO, WARN, ERROR") + if flagOpts.dataplaneConfig.Logging.LogLevel != nil { + switch strings.ToUpper(*flagOpts.dataplaneConfig.Logging.LogLevel) { + case "TRACE", "DEBUG", "INFO", "WARN", "ERROR": + default: + log.Fatal("invalid log level. valid values - TRACE, DEBUG, INFO, WARN, ERROR") + } } if flagOpts.configFile != "" && !strings.HasSuffix(flagOpts.configFile, ".json") { @@ -118,7 +122,10 @@ func validateFlags() { } func run() error { - flag.Parse() + err := flags.Parse(os.Args[1:]) + if err != nil { + return err + } if flagOpts.printVersion { fmt.Printf("Consul Dataplane v%s\n", version.GetHumanVersion()) @@ -129,7 +136,7 @@ func run() error { readServiceIDFromFile() validateFlags() - consuldpCfg, err := flagOpts.buildDataplaneConfig() + consuldpCfg, err := flagOpts.buildDataplaneConfig(flags.Args()) if err != nil { return err } @@ -169,11 +176,14 @@ func main() { // because this option only really makes sense as a CLI flag (and we handle // all flag parsing here). func readServiceIDFromFile() { - if flagOpts.serviceID == "" && flagOpts.serviceIDPath != "" { - id, err := os.ReadFile(flagOpts.serviceIDPath) + if flagOpts.dataplaneConfig.Service.ServiceID == nil && + flagOpts.dataplaneConfig.Service.ServiceIDPath != nil && + *flagOpts.dataplaneConfig.Service.ServiceIDPath != "" { + id, err := os.ReadFile(*flagOpts.dataplaneConfig.Service.ServiceIDPath) if err != nil { log.Fatalf("failed to read given -proxy-service-id-path: %v", err) } - flagOpts.serviceID = string(id) + s := string(id) + flagOpts.dataplaneConfig.Service.ServiceID = &s } } diff --git a/cmd/consul-dataplane/merge.go b/cmd/consul-dataplane/merge.go index f049f793..f4ce551d 100644 --- a/cmd/consul-dataplane/merge.go +++ b/cmd/consul-dataplane/merge.go @@ -1,269 +1,242 @@ package main -import ( - "strings" - - "github.com/hashicorp/consul-dataplane/pkg/consuldp" -) - // mergeConfigs takes in a couple of configs(c1&c2) and applies // c2 on to c1 based on the following conditions // // 1. If an attribute of c2 is a string/integer/bool, then it is applied to the -// same field in c1 if it holds non default value for the field -// 2. If an attribute of c2 is a slice/map, then it overrides the same field in c1 -func mergeConfigs(c1, c2 *consuldp.Config) { - mergeConsulConfigs(c1.Consul, c2.Consul) - mergeServiceConfigs(c1.Service, c2.Service) - mergeTelemetryConfigs(c1.Telemetry, c2.Telemetry) - mergeLoggingConfigs(c1.Logging, c2.Logging) - mergeEnvoyConfigs(c1.Envoy, c2.Envoy) - mergeXDSServerConfigs(c1.XDSServer, c2.XDSServer) - mergeDNSServerConfigs(c1.DNSServer, c2.DNSServer) +// same field in c1 if it is non nil indicating that the user provided the input +// 2. If an attribute of c2 is a slice/map and it is non nil then it overrides +// the same field in c1 +func mergeConfigs(c1, c2 *DataplaneConfigFlags) { + c1.Consul = mergeConsulConfigs(c1.Consul, c2.Consul) + c1.Service = mergeServiceConfigs(c1.Service, c2.Service) + c1.Telemetry = mergeTelemetryConfigs(c1.Telemetry, c2.Telemetry) + c1.Logging = mergeLoggingConfigs(c1.Logging, c2.Logging) + c1.Envoy = mergeEnvoyConfigs(c1.Envoy, c2.Envoy) + c1.XDSServer = mergeXDSServerConfigs(c1.XDSServer, c2.XDSServer) + c1.DNSServer = mergeDNSServerConfigs(c1.DNSServer, c2.DNSServer) } -func mergeConsulConfigs(c1, c2 *consuldp.ConsulConfig) { - if c2 == nil { - return - } - - if c2.Addresses != "" { +func mergeConsulConfigs(c1, c2 ConsulFlags) ConsulFlags { + if c2.Addresses != nil { c1.Addresses = c2.Addresses } - if c2.GRPCPort != int(0) && c2.GRPCPort != DefaultGRPCPort { + if c2.GRPCPort != nil { c1.GRPCPort = c2.GRPCPort } - if c2.ServerWatchDisabled { - c1.ServerWatchDisabled = true + if c2.ServerWatchDisabled != nil { + c1.ServerWatchDisabled = c2.ServerWatchDisabled } - if c2.Credentials != nil { - if c2.Credentials.Type != "" { - c1.Credentials.Type = c2.Credentials.Type - } - - if c2.Credentials.Login.AuthMethod != "" { - c1.Credentials.Login.AuthMethod = c2.Credentials.Login.AuthMethod - } + if c2.Credentials.Type != nil { + c1.Credentials.Type = c2.Credentials.Type + } - if c2.Credentials.Login.BearerToken != "" { - c1.Credentials.Login.BearerToken = c2.Credentials.Login.BearerToken - } + if c2.Credentials.Login.AuthMethod != nil { + c1.Credentials.Login.AuthMethod = c2.Credentials.Login.AuthMethod + } - if c2.Credentials.Login.BearerTokenPath != "" { - c1.Credentials.Login.BearerTokenPath = c2.Credentials.Login.BearerTokenPath - } + if c2.Credentials.Login.BearerToken != nil { + c1.Credentials.Login.BearerToken = c2.Credentials.Login.BearerToken + } - if c2.Credentials.Login.Datacenter != "" { - c1.Credentials.Login.Datacenter = c2.Credentials.Login.Datacenter - } + if c2.Credentials.Login.BearerTokenPath != nil { + c1.Credentials.Login.BearerTokenPath = c2.Credentials.Login.BearerTokenPath + } - if c2.Credentials.Login.Namespace != "" { - c1.Credentials.Login.Namespace = c2.Credentials.Login.Namespace - } + if c2.Credentials.Login.Datacenter != nil { + c1.Credentials.Login.Datacenter = c2.Credentials.Login.Datacenter + } - if c2.Credentials.Login.Partition != "" { - c1.Credentials.Login.Partition = c2.Credentials.Login.Partition - } + if c2.Credentials.Login.Namespace != nil { + c1.Credentials.Login.Namespace = c2.Credentials.Login.Namespace + } - if c2.Credentials.Login.Meta != nil { - c1.Credentials.Login.Meta = c2.Credentials.Login.Meta - } + if c2.Credentials.Login.Partition != nil { + c1.Credentials.Login.Partition = c2.Credentials.Login.Partition + } - if c2.Credentials.Static.Token != "" { - c1.Credentials.Static.Token = c2.Credentials.Static.Token - } + if c2.Credentials.Login.Meta != nil { + c1.Credentials.Login.Meta = c2.Credentials.Login.Meta } - if c2.TLS != nil { - if c2.TLS.Disabled { - c1.TLS.Disabled = true - } + if c2.Credentials.Static.Token != nil { + c1.Credentials.Static.Token = c2.Credentials.Static.Token + } - if c2.TLS.CACertsPath != "" { - c1.TLS.CACertsPath = c2.TLS.CACertsPath - } + if c2.TLS.Disabled != nil { + c1.TLS.Disabled = c2.TLS.Disabled + } - if c2.TLS.CertFile != "" { - c1.TLS.CertFile = c2.TLS.CertFile - } + if c2.TLS.CACertsPath != nil { + c1.TLS.CACertsPath = c2.TLS.CACertsPath + } - if c2.TLS.KeyFile != "" { - c1.TLS.KeyFile = c2.TLS.KeyFile - } + if c2.TLS.CertFile != nil { + c1.TLS.CertFile = c2.TLS.CertFile + } - if c2.TLS.ServerName != "" { - c1.TLS.ServerName = c2.TLS.ServerName - } + if c2.TLS.KeyFile != nil { + c1.TLS.KeyFile = c2.TLS.KeyFile + } - if c2.TLS.InsecureSkipVerify { - c1.TLS.InsecureSkipVerify = true - } + if c2.TLS.ServerName != nil { + c1.TLS.ServerName = c2.TLS.ServerName } -} -func mergeTelemetryConfigs(c1, c2 *consuldp.TelemetryConfig) { - if c2 == nil { - return + if c2.TLS.InsecureSkipVerify != nil { + c1.TLS.InsecureSkipVerify = c2.TLS.InsecureSkipVerify } - if !c2.UseCentralConfig { - c1.UseCentralConfig = false + return c1 +} + +func mergeTelemetryConfigs(c1, c2 TelemetryFlags) TelemetryFlags { + if c2.UseCentralConfig != nil { + c1.UseCentralConfig = c2.UseCentralConfig } - if c2.Prometheus.RetentionTime != 0 && c2.Prometheus.RetentionTime != DefaultPromRetentionTime { + if c2.Prometheus.RetentionTime != nil { c1.Prometheus.RetentionTime = c2.Prometheus.RetentionTime } - if c2.Prometheus.CACertsPath != "" { + if c2.Prometheus.CACertsPath != nil { c1.Prometheus.CACertsPath = c2.Prometheus.CACertsPath } - if c2.Prometheus.CertFile != "" { + if c2.Prometheus.CertFile != nil { c1.Prometheus.CertFile = c2.Prometheus.CertFile } - if c2.Prometheus.KeyFile != "" { + if c2.Prometheus.KeyFile != nil { c1.Prometheus.KeyFile = c2.Prometheus.KeyFile } - if c2.Prometheus.ScrapePath != "" { + if c2.Prometheus.ScrapePath != nil { c1.Prometheus.ScrapePath = c2.Prometheus.ScrapePath } - if c2.Prometheus.ServiceMetricsURL != "" { + if c2.Prometheus.ServiceMetricsURL != nil { c1.Prometheus.ServiceMetricsURL = c2.Prometheus.ServiceMetricsURL } - if c2.Prometheus.MergePort != int(0) && c2.Prometheus.MergePort != DefaultPromMergePort { + if c2.Prometheus.MergePort != nil { c1.Prometheus.MergePort = c2.Prometheus.MergePort } -} -func mergeServiceConfigs(c1, c2 *consuldp.ServiceConfig) { - if c2 == nil { - return - } + return c1 +} - if c2.NodeName != "" { +func mergeServiceConfigs(c1, c2 ServiceFlags) ServiceFlags { + if c2.NodeName != nil { c1.NodeName = c2.NodeName } - if c2.NodeID != "" { + if c2.NodeID != nil { c1.NodeID = c2.NodeID } - if c2.Namespace != "" { + if c2.Namespace != nil { c1.Namespace = c2.Namespace } - if c2.Partition != "" { + if c2.Partition != nil { c1.Partition = c2.Partition } - if c2.ServiceID != "" { + if c2.ServiceID != nil { c1.ServiceID = c2.ServiceID } + + return c1 } -func mergeLoggingConfigs(c1, c2 *consuldp.LoggingConfig) { - if c2 == nil { - return +func mergeLoggingConfigs(c1, c2 LogFlags) LogFlags { + if c2.LogJSON != nil { + c1.LogJSON = c2.LogJSON } - if c2.LogJSON { - c1.LogJSON = true + if c2.LogLevel != nil { + c1.LogLevel = c2.LogLevel } - if c2.LogLevel != "" && c2.LogLevel != DefaultLogLevel { - c1.LogLevel = strings.ToUpper(c2.LogLevel) - } + return c1 } -func mergeEnvoyConfigs(c1, c2 *consuldp.EnvoyConfig) { - if c2 == nil { - return - } - - if c2.AdminBindAddress != "" && c2.AdminBindAddress != DefaultEnvoyAdminBindAddr { - c1.AdminBindAddress = c2.AdminBindAddress +func mergeEnvoyConfigs(c1, c2 EnvoyFlags) EnvoyFlags { + if c2.AdminBindAddr != nil { + c1.AdminBindAddr = c2.AdminBindAddr } - if c2.AdminBindPort != int(0) && c2.AdminBindPort != DefaultEnvoyAdminBindPort { + if c2.AdminBindPort != nil { c1.AdminBindPort = c2.AdminBindPort } - if c2.ReadyBindAddress != "" { - c1.ReadyBindAddress = c2.ReadyBindAddress + if c2.ReadyBindAddr != nil { + c1.ReadyBindAddr = c2.ReadyBindAddr } - if c2.ReadyBindPort != int(0) && c2.ReadyBindPort != DefaultEnvoyReadyBindPort { + if c2.ReadyBindPort != nil { c1.ReadyBindPort = c2.ReadyBindPort } - if c2.EnvoyConcurrency != int(0) && c2.EnvoyConcurrency != DefaultEnvoyConcurrency { - c1.EnvoyConcurrency = c2.EnvoyConcurrency + if c2.Concurrency != nil { + c1.Concurrency = c2.Concurrency } - if c2.EnvoyDrainTimeSeconds != int(0) && c2.EnvoyDrainTimeSeconds != DefaultEnvoyDrainTimeSeconds { - c1.EnvoyDrainTimeSeconds = c2.EnvoyDrainTimeSeconds + if c2.DrainTimeSeconds != nil { + c1.DrainTimeSeconds = c2.DrainTimeSeconds } - if c2.EnvoyDrainStrategy != "" && c2.EnvoyDrainStrategy != DefaultEnvoyDrainStrategy { - c1.EnvoyDrainStrategy = c2.EnvoyDrainStrategy + if c2.DrainStrategy != nil { + c1.DrainStrategy = c2.DrainStrategy } - if c2.ShutdownDrainListenersEnabled { - c1.ShutdownDrainListenersEnabled = true + if c2.ShutdownDrainListenersEnabled != nil { + c1.ShutdownDrainListenersEnabled = c2.ShutdownDrainListenersEnabled } - if c2.ShutdownGracePeriodSeconds != int(0) && c2.ShutdownGracePeriodSeconds != DefaultEnvoyShutdownGracePeriodSeconds { + if c2.ShutdownGracePeriodSeconds != nil { c1.ShutdownGracePeriodSeconds = c2.ShutdownGracePeriodSeconds } - if c2.GracefulShutdownPath != "" && c2.GracefulShutdownPath != DefaultGracefulShutdownPath { + if c2.GracefulShutdownPath != nil { c1.GracefulShutdownPath = c2.GracefulShutdownPath } - if c2.GracefulPort != int(0) && c2.GracefulPort != DefaultGracefulPort { + if c2.GracefulPort != nil { c1.GracefulPort = c2.GracefulPort } - if c2.DumpEnvoyConfigOnExitEnabled { - c1.DumpEnvoyConfigOnExitEnabled = true + if c2.DumpEnvoyConfigOnExitEnabled != nil { + c1.DumpEnvoyConfigOnExitEnabled = c2.DumpEnvoyConfigOnExitEnabled } - if c2.ExtraArgs != nil && len(c2.ExtraArgs) > 0 { - c1.ExtraArgs = c2.ExtraArgs - } + return c1 } -func mergeXDSServerConfigs(c1, c2 *consuldp.XDSServer) { - if c2 == nil { - return - } - - if c2.BindAddress != "" && c2.BindAddress != DefaultXDSBindAddr { - c1.BindAddress = c2.BindAddress +func mergeXDSServerConfigs(c1, c2 XDSServerFlags) XDSServerFlags { + if c2.BindAddr != nil { + c1.BindAddr = c2.BindAddr } - if c2.BindPort != int(0) && c2.BindPort != DefaultXDSBindPort { + if c2.BindPort != nil { c1.BindPort = c2.BindPort } -} -func mergeDNSServerConfigs(c1, c2 *consuldp.DNSServerConfig) { - if c2 == nil { - return - } + return c1 +} - if c2.BindAddr != "" && c2.BindAddr != DefaultDNSBindAddr { +func mergeDNSServerConfigs(c1, c2 DNSServerFlags) DNSServerFlags { + if c2.BindAddr != nil { c1.BindAddr = c2.BindAddr } - if c2.Port != int(0) && c2.Port != DefaultDNSBindPort { - c1.Port = c2.Port + if c2.BindPort != nil { + c1.BindPort = c2.BindPort } + + return c1 } diff --git a/pkg/consuldp/config.go b/pkg/consuldp/config.go index fee91b4d..44d1d966 100644 --- a/pkg/consuldp/config.go +++ b/pkg/consuldp/config.go @@ -18,26 +18,26 @@ type ConsulConfig struct { // Addresses are Consul server addresses. Value can be: // DNS name OR 'exec='. // Executable will be parsed by https://github.com/hashicorp/go-netaddrs. - Addresses string `json:"addresses"` + Addresses string // GRPCPort is the gRPC port on the Consul server. - GRPCPort int `json:"grpcPort"` + GRPCPort int // Credentials are the credentials used to authenticate requests and streams // to the Consul servers (e.g. static ACL token or auth method credentials). - Credentials *CredentialsConfig `json:"credentials,omitempty"` + Credentials *CredentialsConfig // ServerWatchDisabled opts-out of consuming the server update stream, for // cases where its addresses are incorrect (e.g. servers are behind a load // balancer). - ServerWatchDisabled bool `json:"serverWatchDisabled"` + ServerWatchDisabled bool // TLS contains the TLS settings for communicating with Consul servers. - TLS *TLSConfig `json:"tls,omitempty"` + TLS *TLSConfig } // DNSServerConfig is the configuration for the transparent DNS proxy that will forward requests to consul type DNSServerConfig struct { // BindAddr is the address the DNS server will bind to. Default will be 127.0.0.1 - BindAddr string `json:"bindAddr"` + BindAddr string // Port is the port which the DNS server will bind to. - Port int `json:"port"` + Port int } // TLSConfig contains the TLS settings for communicating with Consul servers. @@ -45,32 +45,32 @@ type TLSConfig struct { // Disabled causes consul-dataplane to communicate with Consul servers over // an insecure plaintext connection. This is useful for testing, but should // not be used in production. - Disabled bool `json:"disabled"` + Disabled bool // CACertsPath is a path to a file or directory containing CA certificates to // use to verify the server's certificate. This is only necessary if the server // presents a certificate that isn't signed by a trusted public CA. - CACertsPath string `json:"caCertsPath"` + CACertsPath string // ServerName is used to verify the server certificate's subject when it cannot // be inferred from Consul.Addresses (i.e. it is not a DNS name). - ServerName string `json:"serverName"` + ServerName string // CertFile is a path to the client certificate that will be presented to // Consul servers. // // Note: this is only required if servers have tls.grpc.verify_incoming enabled. // Generally, issuing consul-dataplane instances with client certificates isn't // necessary and creates significant operational burden. - CertFile string `json:"certFile"` + CertFile string // KeyFile is a path to the client private key that will be used to communicate // with Consul servers (when CertFile is provided). // // Note: this is only required if servers have tls.grpc.verify_incoming enabled. // Generally, issuing consul-dataplane instances with client certificates isn't // necessary and creates significant operational burden. - KeyFile string `json:"keyFile"` + KeyFile string // InsecureSkipVerify causes consul-dataplane not to verify the certificate // presented by the server. This is useful for testing, but should not be used // in production. - InsecureSkipVerify bool `json:"insecureSkipVerify"` + InsecureSkipVerify bool } // Load creates a *tls.Config, including loading the CA and client certificates. @@ -115,11 +115,11 @@ func (t *TLSConfig) Load() (*tls.Config, error) { // streams to the Consul servers. type CredentialsConfig struct { // Type identifies the type of credentials provided. - Type CredentialsType `json:"type"` + Type CredentialsType // Static contains the static ACL token. - Static StaticCredentialsConfig `json:"static"` + Static StaticCredentialsConfig // Login contains the credentials for logging in with an auth method. - Login LoginCredentialsConfig `json:"login"` + Login LoginCredentialsConfig } // CredentialsType identifies the type of credentials provided. @@ -139,26 +139,26 @@ const ( // authenticate requests and streams to the Consul servers. type StaticCredentialsConfig struct { // Token is the static ACL token. - Token string `json:"token"` + Token string } // LoginCredentialsConfig contains credentials for logging in with an auth method. type LoginCredentialsConfig struct { // AuthMethod is the name of the Consul auth method. - AuthMethod string `json:"authmethod"` + AuthMethod string // Namespace is the namespace containing the auth method. - Namespace string `json:"namespace"` + Namespace string // Partition is the partition containing the auth method. - Partition string `json:"partition"` + Partition string // Datacenter is the datacenter containing the auth method. - Datacenter string `json:"datacenter"` + Datacenter string // BearerToken is the bearer token presented to the auth method. - BearerToken string `json:"bearerToken"` + BearerToken string // BearerTokenPath is the path to a file containing a bearer token. - BearerTokenPath string `json:"bearerTokenPath"` + BearerTokenPath string // Meta is the arbitrary set of key-value pairs to attach to the // token. These are included in the Description field of the token. - Meta map[string]string `json:"meta,omitempty"` + Meta map[string]string } // ToDiscoveryCredentials creates a discovery.Credentials, including loading a @@ -204,76 +204,76 @@ type LoggingConfig struct { // Name of the subsystem to prefix logs with Name string // LogLevel is the logging level. Valid values - TRACE, DEBUG, INFO, WARN, ERROR - LogLevel string `json:"logLevel"` + LogLevel string // LogJSON controls if the output should be in JSON. - LogJSON bool `json:"logJSON"` + LogJSON bool } // ServiceConfig contains details of the proxy service instance. type ServiceConfig struct { // NodeName is the name of the node to which the proxy service instance is // registered. - NodeName string `json:"nodeName"` + NodeName string // NodeName is the ID of the node to which the proxy service instance is // registered. - NodeID string `json:"nodeId"` + NodeID string // ServiceID is the ID of the proxy service instance. - ServiceID string `json:"serviceId"` + ServiceID string // Namespace is the Consul Enterprise namespace in which the proxy service // instance is registered. - Namespace string `json:"namespace"` + Namespace string // Partition is the Consul Enterprise partition in which the proxy service // instance is registered. - Partition string `json:"partition"` + Partition string } // TelemetryConfig contains configuration for telemetry. type TelemetryConfig struct { // UseCentralConfig controls whether the proxy will apply the central telemetry // configuration. - UseCentralConfig bool `json:"useCentralConfig"` + UseCentralConfig bool // Prometheus contains Prometheus-specific configuration that cannot be // determined from central telemetry configuration. - Prometheus PrometheusTelemetryConfig `json:"prometheus"` + Prometheus PrometheusTelemetryConfig } // PrometheusTelemetryConfig contains Prometheus-specific telemetry config. type PrometheusTelemetryConfig struct { // RetentionTime controls the duration that metrics are aggregated for. - RetentionTime time.Duration `json:"retentionTime"` + RetentionTime time.Duration // CACertsPath is a path to a file or directory containing CA certificates // to use to verify the Prometheus server's certificate. This is only // necessary if the server presents a certificate that isn't signed by a // trusted public CA. - CACertsPath string `json:"caCertsPath"` + CACertsPath string // KeyFile is a path to the client private key used for serving Prometheus // metrics. - KeyFile string `json:"keyFile"` + KeyFile string // CertFile is a path to the client certificate used for serving Prometheus // metrics. - CertFile string `json:"certFile"` + CertFile string // ServiceMetricsURL is an optional URL that must serve Prometheus metrics. // The metrics at this URL are scraped and merged into Consul Dataplane's // main Prometheus metrics. - ServiceMetricsURL string `json:"serviceMetricsURL"` + ServiceMetricsURL string // ScrapePath is the URL path where Envoy serves Prometheus metrics. - ScrapePath string `json:"scrapePath"` + ScrapePath string // MergePort is the port to server merged metrics. - MergePort int `json:"mergePort"` + MergePort int } // EnvoyConfig contains configuration for the Envoy process. type EnvoyConfig struct { // AdminBindAddress is the address on which the Envoy admin server will be available. - AdminBindAddress string `json:"adminBindAddress"` + AdminBindAddress string // AdminBindPort is the port on which the Envoy admin server will be available. - AdminBindPort int `json:"adminBindPort"` + AdminBindPort int // ReadyBindAddress is the address on which the Envoy readiness probe will be available. - ReadyBindAddress string `json:"readyBindAddress"` + ReadyBindAddress string // ReadyBindPort is the port on which the Envoy readiness probe will be available. - ReadyBindPort int `json:"readyBindPort"` + ReadyBindPort int // EnvoyConcurrency is the envoy concurrency https://www.envoyproxy.io/docs/envoy/latest/operations/cli#cmdoption-concurrency - EnvoyConcurrency int `json:"envoyConcurrency"` + EnvoyConcurrency int // EnvoyDrainTime is the time in seconds for which Envoy will drain connections // during a hot restart, when listeners are modified or removed via LDS, or when // initiated manually via a request to the Envoy admin API. @@ -281,42 +281,42 @@ type EnvoyConfig struct { // requests, send HTTP2 GOAWAY, and terminate connections on request completion // (after the delayed close period). // https://www.envoyproxy.io/docs/envoy/latest/operations/cli#cmdoption-drain-time-s - EnvoyDrainTimeSeconds int `json:"envoyDrainTimeSeconds"` + EnvoyDrainTimeSeconds int // EnvoyDrainStrategy is the behaviour of Envoy during the drain sequence. // Determines whether all open connections should be encouraged to drain // immediately or to increase the percentage gradually as the drain time elapses. // https://www.envoyproxy.io/docs/envoy/latest/operations/cli#cmdoption-drain-strategy - EnvoyDrainStrategy string `json:"envoyDrainStrategy"` + EnvoyDrainStrategy string // ShutdownDrainListenersEnabled configures whether to start draining proxy listeners before terminating the proxy container. Drain time defaults to the value of ShutdownGracePeriodSeconds, but may be set explicitly with EnvoyDrainTimeSeconds. - ShutdownDrainListenersEnabled bool `json:"shutdownDrainListenersEnabled"` + ShutdownDrainListenersEnabled bool // ShutdownGracePeriodSeconds is the amount of time to wait after receiving a SIGTERM before terminating the proxy container. - ShutdownGracePeriodSeconds int `json:"shutdownGracePeriodSeconds"` + ShutdownGracePeriodSeconds int // GracefulShutdownPath is the path on which the HTTP endpoint to initiate a graceful shutdown of Envoy is served - GracefulShutdownPath string `json:"gracefulShutdownPath"` + GracefulShutdownPath string // GracefulPort is the port on which the HTTP server for graceful shutdown endpoints will be available. - GracefulPort int `json:"gracefulPort"` + GracefulPort int // DumpEnvoyConfigOnExitEnabled configures whether to call Envoy's /config_dump endpoint during consul-dataplane controlled shutdown. - DumpEnvoyConfigOnExitEnabled bool `json:"dumpEnvoyConfigOnExitEnabled"` + DumpEnvoyConfigOnExitEnabled bool // ExtraArgs are the extra arguments passed to envoy at startup of the proxy - ExtraArgs []string `json:"extraArgs,omitempty"` + ExtraArgs []string } // XDSServer contains the configuration of the xDS server. type XDSServer struct { // BindAddress is the address on which the Envoy xDS server will be available. - BindAddress string `json:"bindAddress"` + BindAddress string // BindPort is the address on which the Envoy xDS port will be available. - BindPort int `json:"bindPort"` + BindPort int } // Config is the configuration used by consul-dataplane, consolidated // from various sources - CLI flags, env vars, config file settings. type Config struct { - DNSServer *DNSServerConfig `json:"dnsServerConfig,omitempty"` - Consul *ConsulConfig `json:"consul,omitempty"` - Service *ServiceConfig `json:"service,omitempty"` - Logging *LoggingConfig `json:"logging,omitempty"` - Telemetry *TelemetryConfig `json:"telemetry,omitempty"` - Envoy *EnvoyConfig `json:"envoy,omitempty"` - XDSServer *XDSServer `json:"xdsServer,omitempty"` + DNSServer *DNSServerConfig + Consul *ConsulConfig + Service *ServiceConfig + Logging *LoggingConfig + Telemetry *TelemetryConfig + Envoy *EnvoyConfig + XDSServer *XDSServer } From 68b8265b5fe92bef61d27799a3fd9c29a3b01f9e Mon Sep 17 00:00:00 2001 From: Ganeshrockz Date: Tue, 20 Jun 2023 23:53:17 +0530 Subject: [PATCH 6/8] Fix comments --- .changelog/164.txt | 3 +- cmd/consul-dataplane/config.go | 41 ++++-- cmd/consul-dataplane/duration.go | 6 +- cmd/consul-dataplane/merge.go | 242 ------------------------------- go.mod | 1 + go.sum | 2 + 6 files changed, 33 insertions(+), 262 deletions(-) delete mode 100644 cmd/consul-dataplane/merge.go diff --git a/.changelog/164.txt b/.changelog/164.txt index f9229a35..03a978de 100644 --- a/.changelog/164.txt +++ b/.changelog/164.txt @@ -1,4 +1,3 @@ ```release-note:improvement -Adds a flag to the existing consul-dataplane command `config-file` to support reading configurations -from a JSON file. +Add the `-config-file` flag to support reading configuration options from a JSON file. ``` \ No newline at end of file diff --git a/cmd/consul-dataplane/config.go b/cmd/consul-dataplane/config.go index de23d83a..90fdc743 100644 --- a/cmd/consul-dataplane/config.go +++ b/cmd/consul-dataplane/config.go @@ -5,6 +5,7 @@ import ( "os" "strings" + "dario.cat/mergo" "github.com/hashicorp/consul-dataplane/pkg/consuldp" ) @@ -142,11 +143,16 @@ func (f *FlagOpts) buildDataplaneConfig(extraArgs []string) (*consuldp.Config, e return nil, err } - mergeConfigs(consulDPDefaultFlags, consulDPFileBasedFlags) + consulDPDefaultFlags, err = mergeConfigs(consulDPDefaultFlags, consulDPFileBasedFlags) + if err != nil { + return nil, err + } } - consulDPCLIFlags := &f.dataplaneConfig - mergeConfigs(consulDPDefaultFlags, consulDPCLIFlags) + consulDPDefaultFlags, err = mergeConfigs(consulDPDefaultFlags, f.dataplaneConfig) + if err != nil { + return nil, err + } consuldpRuntimeConfig, err := constructRuntimeConfig(consulDPDefaultFlags, extraArgs) if err != nil { @@ -157,23 +163,23 @@ func (f *FlagOpts) buildDataplaneConfig(extraArgs []string) (*consuldp.Config, e } // Constructs a config based on the values present in the config json file -func (f *FlagOpts) buildConfigFromFile() (*DataplaneConfigFlags, error) { - var cfg *DataplaneConfigFlags +func (f *FlagOpts) buildConfigFromFile() (DataplaneConfigFlags, error) { + var cfg DataplaneConfigFlags data, err := os.ReadFile(f.configFile) if err != nil { - return nil, err + return DataplaneConfigFlags{}, err } err = json.Unmarshal(data, &cfg) if err != nil { - return nil, err + return DataplaneConfigFlags{}, err } return cfg, nil } // Constructs a config with the default values -func buildDefaultConsulDPFlags() (*DataplaneConfigFlags, error) { +func buildDefaultConsulDPFlags() (DataplaneConfigFlags, error) { data := ` { "consul": { @@ -220,10 +226,10 @@ func buildDefaultConsulDPFlags() (*DataplaneConfigFlags, error) { } }` - var defaultCfgFlags *DataplaneConfigFlags + var defaultCfgFlags DataplaneConfigFlags err := json.Unmarshal([]byte(data), &defaultCfgFlags) if err != nil { - return nil, err + return DataplaneConfigFlags{}, err } return defaultCfgFlags, nil @@ -231,11 +237,7 @@ func buildDefaultConsulDPFlags() (*DataplaneConfigFlags, error) { // constructRuntimeConfig constructs the final config needed for dataplane to start // itself after substituting all the user provided inputs -func constructRuntimeConfig(cfg *DataplaneConfigFlags, extraArgs []string) (*consuldp.Config, error) { - if cfg == nil { - return &consuldp.Config{}, nil - } - +func constructRuntimeConfig(cfg DataplaneConfigFlags, extraArgs []string) (*consuldp.Config, error) { return &consuldp.Config{ Consul: &consuldp.ConsulConfig{ Addresses: stringVal(cfg.Consul.Addresses), @@ -313,3 +315,12 @@ func constructRuntimeConfig(cfg *DataplaneConfigFlags, extraArgs []string) (*con }, }, nil } + +func mergeConfigs(c1, c2 DataplaneConfigFlags) (DataplaneConfigFlags, error) { + err := mergo.Merge(&c1, c2, mergo.WithOverride, mergo.WithoutDereference) + if err != nil { + return DataplaneConfigFlags{}, err + } + + return c1, nil +} diff --git a/cmd/consul-dataplane/duration.go b/cmd/consul-dataplane/duration.go index a4cff925..b0096a60 100644 --- a/cmd/consul-dataplane/duration.go +++ b/cmd/consul-dataplane/duration.go @@ -13,7 +13,7 @@ type Duration struct { Duration time.Duration } -func (duration *Duration) UnmarshalJSON(b []byte) error { +func (d *Duration) UnmarshalJSON(b []byte) error { var unmarshalledJson interface{} err := json.Unmarshal(b, &unmarshalledJson) @@ -23,9 +23,9 @@ func (duration *Duration) UnmarshalJSON(b []byte) error { switch value := unmarshalledJson.(type) { case float64: - duration.Duration = time.Duration(value) + d.Duration = time.Duration(value) case string: - duration.Duration, err = time.ParseDuration(value) + d.Duration, err = time.ParseDuration(value) if err != nil { return err } diff --git a/cmd/consul-dataplane/merge.go b/cmd/consul-dataplane/merge.go deleted file mode 100644 index f4ce551d..00000000 --- a/cmd/consul-dataplane/merge.go +++ /dev/null @@ -1,242 +0,0 @@ -package main - -// mergeConfigs takes in a couple of configs(c1&c2) and applies -// c2 on to c1 based on the following conditions -// -// 1. If an attribute of c2 is a string/integer/bool, then it is applied to the -// same field in c1 if it is non nil indicating that the user provided the input -// 2. If an attribute of c2 is a slice/map and it is non nil then it overrides -// the same field in c1 -func mergeConfigs(c1, c2 *DataplaneConfigFlags) { - c1.Consul = mergeConsulConfigs(c1.Consul, c2.Consul) - c1.Service = mergeServiceConfigs(c1.Service, c2.Service) - c1.Telemetry = mergeTelemetryConfigs(c1.Telemetry, c2.Telemetry) - c1.Logging = mergeLoggingConfigs(c1.Logging, c2.Logging) - c1.Envoy = mergeEnvoyConfigs(c1.Envoy, c2.Envoy) - c1.XDSServer = mergeXDSServerConfigs(c1.XDSServer, c2.XDSServer) - c1.DNSServer = mergeDNSServerConfigs(c1.DNSServer, c2.DNSServer) -} - -func mergeConsulConfigs(c1, c2 ConsulFlags) ConsulFlags { - if c2.Addresses != nil { - c1.Addresses = c2.Addresses - } - - if c2.GRPCPort != nil { - c1.GRPCPort = c2.GRPCPort - } - - if c2.ServerWatchDisabled != nil { - c1.ServerWatchDisabled = c2.ServerWatchDisabled - } - - if c2.Credentials.Type != nil { - c1.Credentials.Type = c2.Credentials.Type - } - - if c2.Credentials.Login.AuthMethod != nil { - c1.Credentials.Login.AuthMethod = c2.Credentials.Login.AuthMethod - } - - if c2.Credentials.Login.BearerToken != nil { - c1.Credentials.Login.BearerToken = c2.Credentials.Login.BearerToken - } - - if c2.Credentials.Login.BearerTokenPath != nil { - c1.Credentials.Login.BearerTokenPath = c2.Credentials.Login.BearerTokenPath - } - - if c2.Credentials.Login.Datacenter != nil { - c1.Credentials.Login.Datacenter = c2.Credentials.Login.Datacenter - } - - if c2.Credentials.Login.Namespace != nil { - c1.Credentials.Login.Namespace = c2.Credentials.Login.Namespace - } - - if c2.Credentials.Login.Partition != nil { - c1.Credentials.Login.Partition = c2.Credentials.Login.Partition - } - - if c2.Credentials.Login.Meta != nil { - c1.Credentials.Login.Meta = c2.Credentials.Login.Meta - } - - if c2.Credentials.Static.Token != nil { - c1.Credentials.Static.Token = c2.Credentials.Static.Token - } - - if c2.TLS.Disabled != nil { - c1.TLS.Disabled = c2.TLS.Disabled - } - - if c2.TLS.CACertsPath != nil { - c1.TLS.CACertsPath = c2.TLS.CACertsPath - } - - if c2.TLS.CertFile != nil { - c1.TLS.CertFile = c2.TLS.CertFile - } - - if c2.TLS.KeyFile != nil { - c1.TLS.KeyFile = c2.TLS.KeyFile - } - - if c2.TLS.ServerName != nil { - c1.TLS.ServerName = c2.TLS.ServerName - } - - if c2.TLS.InsecureSkipVerify != nil { - c1.TLS.InsecureSkipVerify = c2.TLS.InsecureSkipVerify - } - - return c1 -} - -func mergeTelemetryConfigs(c1, c2 TelemetryFlags) TelemetryFlags { - if c2.UseCentralConfig != nil { - c1.UseCentralConfig = c2.UseCentralConfig - } - - if c2.Prometheus.RetentionTime != nil { - c1.Prometheus.RetentionTime = c2.Prometheus.RetentionTime - } - - if c2.Prometheus.CACertsPath != nil { - c1.Prometheus.CACertsPath = c2.Prometheus.CACertsPath - } - - if c2.Prometheus.CertFile != nil { - c1.Prometheus.CertFile = c2.Prometheus.CertFile - } - - if c2.Prometheus.KeyFile != nil { - c1.Prometheus.KeyFile = c2.Prometheus.KeyFile - } - - if c2.Prometheus.ScrapePath != nil { - c1.Prometheus.ScrapePath = c2.Prometheus.ScrapePath - } - - if c2.Prometheus.ServiceMetricsURL != nil { - c1.Prometheus.ServiceMetricsURL = c2.Prometheus.ServiceMetricsURL - } - - if c2.Prometheus.MergePort != nil { - c1.Prometheus.MergePort = c2.Prometheus.MergePort - } - - return c1 -} - -func mergeServiceConfigs(c1, c2 ServiceFlags) ServiceFlags { - if c2.NodeName != nil { - c1.NodeName = c2.NodeName - } - - if c2.NodeID != nil { - c1.NodeID = c2.NodeID - } - - if c2.Namespace != nil { - c1.Namespace = c2.Namespace - } - - if c2.Partition != nil { - c1.Partition = c2.Partition - } - - if c2.ServiceID != nil { - c1.ServiceID = c2.ServiceID - } - - return c1 -} - -func mergeLoggingConfigs(c1, c2 LogFlags) LogFlags { - if c2.LogJSON != nil { - c1.LogJSON = c2.LogJSON - } - - if c2.LogLevel != nil { - c1.LogLevel = c2.LogLevel - } - - return c1 -} - -func mergeEnvoyConfigs(c1, c2 EnvoyFlags) EnvoyFlags { - if c2.AdminBindAddr != nil { - c1.AdminBindAddr = c2.AdminBindAddr - } - - if c2.AdminBindPort != nil { - c1.AdminBindPort = c2.AdminBindPort - } - - if c2.ReadyBindAddr != nil { - c1.ReadyBindAddr = c2.ReadyBindAddr - } - - if c2.ReadyBindPort != nil { - c1.ReadyBindPort = c2.ReadyBindPort - } - - if c2.Concurrency != nil { - c1.Concurrency = c2.Concurrency - } - - if c2.DrainTimeSeconds != nil { - c1.DrainTimeSeconds = c2.DrainTimeSeconds - } - - if c2.DrainStrategy != nil { - c1.DrainStrategy = c2.DrainStrategy - } - - if c2.ShutdownDrainListenersEnabled != nil { - c1.ShutdownDrainListenersEnabled = c2.ShutdownDrainListenersEnabled - } - - if c2.ShutdownGracePeriodSeconds != nil { - c1.ShutdownGracePeriodSeconds = c2.ShutdownGracePeriodSeconds - } - - if c2.GracefulShutdownPath != nil { - c1.GracefulShutdownPath = c2.GracefulShutdownPath - } - - if c2.GracefulPort != nil { - c1.GracefulPort = c2.GracefulPort - } - - if c2.DumpEnvoyConfigOnExitEnabled != nil { - c1.DumpEnvoyConfigOnExitEnabled = c2.DumpEnvoyConfigOnExitEnabled - } - - return c1 -} - -func mergeXDSServerConfigs(c1, c2 XDSServerFlags) XDSServerFlags { - if c2.BindAddr != nil { - c1.BindAddr = c2.BindAddr - } - - if c2.BindPort != nil { - c1.BindPort = c2.BindPort - } - - return c1 -} - -func mergeDNSServerConfigs(c1, c2 DNSServerFlags) DNSServerFlags { - if c2.BindAddr != nil { - c1.BindAddr = c2.BindAddr - } - - if c2.BindPort != nil { - c1.BindPort = c2.BindPort - } - - return c1 -} diff --git a/go.mod b/go.mod index 15629857..8e2549d0 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/hashicorp/consul-dataplane go 1.20 require ( + dario.cat/mergo v1.0.0 github.com/adamthesax/grpc-proxy v0.0.0-20220525203857-13e92d14f87a github.com/armon/go-metrics v0.4.1 github.com/hashicorp/consul-server-connection-manager v0.1.2 diff --git a/go.sum b/go.sum index 9656fa6d..0aec9cdc 100644 --- a/go.sum +++ b/go.sum @@ -30,6 +30,8 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= From 666491ddf62cc695d0547101e5da7d3fcf696bcf Mon Sep 17 00:00:00 2001 From: Ganeshrockz Date: Wed, 21 Jun 2023 09:04:47 +0530 Subject: [PATCH 7/8] Fix comments --- cmd/consul-dataplane/config_test.go | 56 ++++++++++++----------------- 1 file changed, 22 insertions(+), 34 deletions(-) diff --git a/cmd/consul-dataplane/config_test.go b/cmd/consul-dataplane/config_test.go index 0dc8ffed..8ba246ac 100644 --- a/cmd/consul-dataplane/config_test.go +++ b/cmd/consul-dataplane/config_test.go @@ -5,7 +5,6 @@ import ( "fmt" "math/rand" "os" - "reflect" "testing" "time" @@ -17,9 +16,8 @@ func TestConfigGeneration(t *testing.T) { type testCase struct { desc string flagOpts func() (*FlagOpts, error) - writeConfigFile func() error - assertConfig func(cfg *consuldp.Config, f *FlagOpts) bool - cleanup func() + writeConfigFile func(t *testing.T) error + makeExpectedCfg func(f *FlagOpts) *consuldp.Config wantErr bool } @@ -29,8 +27,8 @@ func TestConfigGeneration(t *testing.T) { flagOpts: func() (*FlagOpts, error) { return generateFlagOpts() }, - assertConfig: func(cfg *consuldp.Config, flagOpts *FlagOpts) bool { - expectedCfg := &consuldp.Config{ + makeExpectedCfg: func(flagOpts *FlagOpts) *consuldp.Config { + return &consuldp.Config{ Consul: &consuldp.ConsulConfig{ Addresses: stringVal(flagOpts.dataplaneConfig.Consul.Addresses), GRPCPort: intVal(flagOpts.dataplaneConfig.Consul.GRPCPort), @@ -98,8 +96,6 @@ func TestConfigGeneration(t *testing.T) { }, }, } - - return reflect.DeepEqual(cfg, expectedCfg) }, wantErr: false, }, @@ -125,8 +121,8 @@ func TestConfigGeneration(t *testing.T) { opts.dataplaneConfig.Envoy.DumpEnvoyConfigOnExitEnabled = boolReference(true) return opts, nil }, - assertConfig: func(cfg *consuldp.Config, flagOpts *FlagOpts) bool { - expectedCfg := &consuldp.Config{ + makeExpectedCfg: func(flagOpts *FlagOpts) *consuldp.Config { + return &consuldp.Config{ Consul: &consuldp.ConsulConfig{ Addresses: stringVal(flagOpts.dataplaneConfig.Consul.Addresses), GRPCPort: intVal(flagOpts.dataplaneConfig.Consul.GRPCPort), @@ -203,8 +199,6 @@ func TestConfigGeneration(t *testing.T) { }, }, } - - return reflect.DeepEqual(cfg, expectedCfg) }, wantErr: false, }, @@ -215,7 +209,7 @@ func TestConfigGeneration(t *testing.T) { opts.configFile = "test.json" return opts, nil }, - writeConfigFile: func() error { + writeConfigFile: func(t *testing.T) error { inputJson := `{ "consul": { "addresses": "consul_server.dc1", @@ -242,10 +236,14 @@ func TestConfigGeneration(t *testing.T) { if err != nil { return err } + + t.Cleanup(func() { + _ = os.Remove("test.json") + }) return nil }, - assertConfig: func(cfg *consuldp.Config, flagOpts *FlagOpts) bool { - expectedCfg := &consuldp.Config{ + makeExpectedCfg: func(flagOpts *FlagOpts) *consuldp.Config { + return &consuldp.Config{ Consul: &consuldp.ConsulConfig{ Addresses: "consul_server.dc1", GRPCPort: 8502, @@ -296,11 +294,6 @@ func TestConfigGeneration(t *testing.T) { }, }, } - - return reflect.DeepEqual(cfg, expectedCfg) - }, - cleanup: func() { - os.Remove("test.json") }, wantErr: false, }, @@ -321,7 +314,7 @@ func TestConfigGeneration(t *testing.T) { return opts, nil }, - writeConfigFile: func() error { + writeConfigFile: func(t *testing.T) error { inputJson := `{ "consul": { "addresses": "consul_server.dc1", @@ -348,10 +341,14 @@ func TestConfigGeneration(t *testing.T) { if err != nil { return err } + + t.Cleanup(func() { + _ = os.Remove("test.json") + }) return nil }, - assertConfig: func(cfg *consuldp.Config, flagOpts *FlagOpts) bool { - expectedCfg := &consuldp.Config{ + makeExpectedCfg: func(flagOpts *FlagOpts) *consuldp.Config { + return &consuldp.Config{ Consul: &consuldp.ConsulConfig{ Addresses: stringVal(flagOpts.dataplaneConfig.Consul.Addresses), GRPCPort: intVal(flagOpts.dataplaneConfig.Consul.GRPCPort), @@ -423,11 +420,6 @@ func TestConfigGeneration(t *testing.T) { }, }, } - - return reflect.DeepEqual(cfg, expectedCfg) - }, - cleanup: func() { - os.Remove("test.json") }, wantErr: false, }, @@ -435,15 +427,11 @@ func TestConfigGeneration(t *testing.T) { for _, tc := range testCases { t.Run(tc.desc, func(t *testing.T) { - if tc.cleanup != nil { - t.Cleanup(tc.cleanup) - } - opts, err := tc.flagOpts() require.NoError(t, err) if tc.writeConfigFile != nil { - require.NoError(t, tc.writeConfigFile()) + require.NoError(t, tc.writeConfigFile(t)) } cfg, err := opts.buildDataplaneConfig(nil) @@ -452,7 +440,7 @@ func TestConfigGeneration(t *testing.T) { require.Error(t, err) } else { require.NoError(t, err) - require.True(t, tc.assertConfig(cfg, opts)) + require.Equal(t, tc.makeExpectedCfg(opts), cfg) } }) } From 6528c398248b380bce14314cd6d0716f6c84bd36 Mon Sep 17 00:00:00 2001 From: Ganeshrockz Date: Wed, 21 Jun 2023 13:43:23 +0530 Subject: [PATCH 8/8] Fix bug --- cmd/consul-dataplane/config.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/consul-dataplane/config.go b/cmd/consul-dataplane/config.go index 90fdc743..8d63b4e8 100644 --- a/cmd/consul-dataplane/config.go +++ b/cmd/consul-dataplane/config.go @@ -288,6 +288,7 @@ func constructRuntimeConfig(cfg DataplaneConfigFlags, extraArgs []string) (*cons EnvoyDrainTimeSeconds: intVal(cfg.Envoy.DrainTimeSeconds), EnvoyDrainStrategy: stringVal(cfg.Envoy.DrainStrategy), ShutdownDrainListenersEnabled: boolVal(cfg.Envoy.ShutdownDrainListenersEnabled), + ShutdownGracePeriodSeconds: intVal(cfg.Envoy.ShutdownGracePeriodSeconds), DumpEnvoyConfigOnExitEnabled: boolVal(cfg.Envoy.DumpEnvoyConfigOnExitEnabled), GracefulShutdownPath: stringVal(cfg.Envoy.GracefulShutdownPath), GracefulPort: intVal(cfg.Envoy.GracefulPort),