Add a new "vault monitor" command (#8477)

Add a new "vault monitor" command

Co-authored-by: ncabatoff <ncabatoff@hashicorp.com>
Co-authored-by: Calvin Leung Huang <cleung2010@gmail.com>
Co-authored-by: Jeff Mitchell <jeffrey.mitchell@gmail.com>
This commit is contained in:
Josh Black
2020-05-21 13:07:50 -07:00
committed by GitHub
parent 399eb357f4
commit af5338b485
95 changed files with 1957 additions and 445 deletions

View File

@@ -2,9 +2,11 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/armon/go-metrics v0.3.0/go.mod h1:zXjbSimjXTd7vOpY8B0/2LpvNvDoXBuplAD+gJD3GYs= github.com/armon/go-metrics v0.3.0/go.mod h1:zXjbSimjXTd7vOpY8B0/2LpvNvDoXBuplAD+gJD3GYs=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310 h1:BUAU3CGlLvorLI26FmByPp2eC2qla6E1Tw+scpcg/to=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/aws/aws-sdk-go v1.25.37/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.25.37/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
@@ -65,6 +67,7 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10= github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10=
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mitchellh/cli v1.0.0 h1:iGBIsUe3+HZ/AD/Vd7DErOt5sU9fa8Uj7A2s1aggv1Y=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
@@ -82,6 +85,7 @@ github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1 h1:ccV59UEOTzVDnDUEFdT95ZzHVZ+5+158q8+SJb2QV5w=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=

View File

@@ -116,7 +116,7 @@ func (c *Sys) DisableAudit(path string) error {
return err return err
} }
// Structures for the requests/resposne are all down here. They aren't // Structures for the requests/response are all down here. They aren't
// individually documented because the map almost directly to the raw HTTP API // individually documented because the map almost directly to the raw HTTP API
// documentation. Please refer to that documentation for more details. // documentation. Please refer to that documentation for more details.

64
api/sys_monitor.go Normal file
View File

@@ -0,0 +1,64 @@
package api
import (
"bufio"
"context"
"fmt"
)
// Monitor returns a channel that outputs strings containing the log messages
// coming from the server.
func (c *Sys) Monitor(ctx context.Context, logLevel string) (chan string, error) {
r := c.c.NewRequest("GET", "/v1/sys/monitor")
if logLevel == "" {
r.Params.Add("log_level", "info")
} else {
r.Params.Add("log_level", logLevel)
}
resp, err := c.c.RawRequestWithContext(ctx, r)
if err != nil {
return nil, err
}
logCh := make(chan string, 64)
go func() {
scanner := bufio.NewScanner(resp.Body)
droppedCount := 0
defer close(logCh)
defer resp.Body.Close()
for {
if ctx.Err() != nil {
return
}
if !scanner.Scan() {
return
}
logMessage := scanner.Text()
if droppedCount > 0 {
select {
case logCh <- fmt.Sprintf("Monitor dropped %d logs during monitor request\n", droppedCount):
droppedCount = 0
default:
droppedCount++
continue
}
}
select {
case logCh <- logMessage:
default:
droppedCount++
}
}
}()
return logCh, nil
}

View File

@@ -8,8 +8,8 @@ import (
"strings" "strings"
"time" "time"
"github.com/hashicorp/go-cleanhttp"
oktaold "github.com/chrismalek/oktasdk-go/okta" oktaold "github.com/chrismalek/oktasdk-go/okta"
"github.com/hashicorp/go-cleanhttp"
"github.com/hashicorp/vault/sdk/framework" "github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/helper/tokenutil" "github.com/hashicorp/vault/sdk/helper/tokenutil"
"github.com/hashicorp/vault/sdk/logical" "github.com/hashicorp/vault/sdk/logical"

View File

@@ -8,7 +8,6 @@ import (
"time" "time"
hclog "github.com/hashicorp/go-hclog" hclog "github.com/hashicorp/go-hclog"
log "github.com/hashicorp/go-hclog"
credCF "github.com/hashicorp/vault-plugin-auth-cf" credCF "github.com/hashicorp/vault-plugin-auth-cf"
"github.com/hashicorp/vault-plugin-auth-cf/testing/certificates" "github.com/hashicorp/vault-plugin-auth-cf/testing/certificates"
cfAPI "github.com/hashicorp/vault-plugin-auth-cf/testing/cf" cfAPI "github.com/hashicorp/vault-plugin-auth-cf/testing/cf"
@@ -29,7 +28,7 @@ func TestCFEndToEnd(t *testing.T) {
coreConfig := &vault.CoreConfig{ coreConfig := &vault.CoreConfig{
DisableMlock: true, DisableMlock: true,
DisableCache: true, DisableCache: true,
Logger: log.NewNullLogger(), Logger: hclog.NewNullLogger(),
CredentialBackends: map[string]logical.Factory{ CredentialBackends: map[string]logical.Factory{
"cf": credCF.Factory, "cf": credCF.Factory,
}, },

View File

@@ -17,6 +17,7 @@ import (
"github.com/hashicorp/vault/builtin/logical/ssh" "github.com/hashicorp/vault/builtin/logical/ssh"
"github.com/hashicorp/vault/builtin/logical/transit" "github.com/hashicorp/vault/builtin/logical/transit"
"github.com/hashicorp/vault/helper/builtinplugins" "github.com/hashicorp/vault/helper/builtinplugins"
"github.com/hashicorp/vault/sdk/helper/logging"
"github.com/hashicorp/vault/sdk/logical" "github.com/hashicorp/vault/sdk/logical"
"github.com/hashicorp/vault/sdk/physical/inmem" "github.com/hashicorp/vault/sdk/physical/inmem"
"github.com/hashicorp/vault/vault" "github.com/hashicorp/vault/vault"
@@ -84,11 +85,16 @@ func testVaultServerAllBackends(tb testing.TB) (*api.Client, func()) {
// API client, list of unseal keys (as strings), and a closer function. // API client, list of unseal keys (as strings), and a closer function.
func testVaultServerUnseal(tb testing.TB) (*api.Client, []string, func()) { func testVaultServerUnseal(tb testing.TB) (*api.Client, []string, func()) {
tb.Helper() tb.Helper()
logger := log.NewInterceptLogger(&log.LoggerOptions{
Output: log.DefaultOutput,
Level: log.Debug,
JSONFormat: logging.ParseEnvLogFormat() == logging.JSONFormat,
})
return testVaultServerCoreConfig(tb, &vault.CoreConfig{ return testVaultServerCoreConfig(tb, &vault.CoreConfig{
DisableMlock: true, DisableMlock: true,
DisableCache: true, DisableCache: true,
Logger: defaultVaultLogger, Logger: logger,
CredentialBackends: defaultVaultCredentialBackends, CredentialBackends: defaultVaultCredentialBackends,
AuditBackends: defaultVaultAuditBackends, AuditBackends: defaultVaultAuditBackends,
LogicalBackends: defaultVaultLogicalBackends, LogicalBackends: defaultVaultLogicalBackends,

View File

@@ -667,6 +667,12 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
BaseCommand: getBaseCommand(), BaseCommand: getBaseCommand(),
}, nil }, nil
}, },
"monitor": func() (cli.Command, error) {
return &MonitorCommand{
BaseCommand: getBaseCommand(),
ShutdownCh: MakeShutdownCh(),
}, nil
},
} }
} }

118
command/monitor.go Normal file
View File

@@ -0,0 +1,118 @@
package command
import (
"context"
"fmt"
"strings"
"github.com/hashicorp/vault/sdk/helper/strutil"
"github.com/mitchellh/cli"
"github.com/posener/complete"
)
var _ cli.Command = (*MonitorCommand)(nil)
var _ cli.CommandAutocomplete = (*MonitorCommand)(nil)
type MonitorCommand struct {
*BaseCommand
logLevel string
// ShutdownCh is used to capture interrupt signal and end streaming
ShutdownCh chan struct{}
}
func (c *MonitorCommand) Synopsis() string {
return "Stream log messages from a Vault server"
}
func (c *MonitorCommand) Help() string {
helpText := `
Usage: vault monitor [options]
Stream log messages of a Vault server. The monitor command lets you listen
for log levels that may be filtered out of the server logs. For example,
the server may be logging at the INFO level, but with the monitor command
you can set -log-level=DEBUG.
` + c.Flags().Help()
return strings.TrimSpace(helpText)
}
func (c *MonitorCommand) Flags() *FlagSets {
set := c.flagSet(FlagSetHTTP)
f := set.NewFlagSet("Monitor Options")
f.StringVar(&StringVar{
Name: "log-level",
Target: &c.logLevel,
Default: "info",
Completion: complete.PredictSet("trace", "debug", "info", "warn", "error"),
Usage: "If passed, the log level to monitor logs. Supported values" +
"(in order of detail) are \"trace\", \"debug\", \"info\", \"warn\"" +
" and \"error\". These are not case sensitive.",
})
return set
}
func (c *MonitorCommand) AutocompleteArgs() complete.Predictor {
return complete.PredictNothing
}
func (c *MonitorCommand) AutocompleteFlags() complete.Flags {
return c.Flags().Completions()
}
func (c *MonitorCommand) Run(args []string) int {
f := c.Flags()
if err := f.Parse(args); err != nil {
c.UI.Error(err.Error())
return 1
}
parsedArgs := f.Args()
if len(parsedArgs) > 0 {
c.UI.Error(fmt.Sprintf("Too many arguments (expected 0, got %d)", len(parsedArgs)))
return 1
}
c.logLevel = strings.ToLower(c.logLevel)
validLevels := []string{"trace", "debug", "info", "warn", "error"}
if !strutil.StrListContains(validLevels, c.logLevel) {
c.UI.Error(fmt.Sprintf("%s is an unknown log level. Valid log levels are: %s", c.logLevel, validLevels))
return 1
}
client, err := c.Client()
if err != nil {
c.UI.Error(err.Error())
return 2
}
// Remove the default 60 second timeout so we can stream indefinitely
client.SetClientTimeout(0)
var logCh chan string
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
logCh, err = client.Sys().Monitor(ctx, c.logLevel)
if err != nil {
c.UI.Error(fmt.Sprintf("Error starting monitor: %s", err))
return 1
}
for {
select {
case log, ok := <-logCh:
if !ok {
return 0
}
c.UI.Info(log)
case <-c.ShutdownCh:
return 0
}
}
}

99
command/monitor_test.go Normal file
View File

@@ -0,0 +1,99 @@
package command
import (
"strings"
"sync/atomic"
"testing"
"time"
"github.com/hashicorp/vault/helper/testhelpers"
"github.com/mitchellh/cli"
)
func testMonitorCommand(tb testing.TB) (*cli.MockUi, *MonitorCommand) {
tb.Helper()
ui := cli.NewMockUi()
return ui, &MonitorCommand{
BaseCommand: &BaseCommand{
UI: ui,
},
}
}
func TestMonitorCommand_Run(t *testing.T) {
t.Parallel()
cases := []struct {
name string
args []string
out string
code int64
}{
{
"valid",
[]string{
"-log-level=debug",
},
"",
0,
},
{
"too_many_args",
[]string{
"-log-level=debug",
"foo",
},
"Too many arguments",
1,
},
{
"unknown_log_level",
[]string{
"-log-level=haha",
},
"haha is an unknown log level",
1,
},
}
for _, tc := range cases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
client, closer := testVaultServer(t)
defer closer()
var code int64
shutdownCh := make(chan struct{})
ui, cmd := testMonitorCommand(t)
cmd.client = client
cmd.ShutdownCh = shutdownCh
stopCh := testhelpers.GenerateDebugLogs(t, client)
go func() {
atomic.StoreInt64(&code, int64(cmd.Run(tc.args)))
}()
select {
case <-time.After(3 * time.Second):
stopCh <- struct{}{}
close(shutdownCh)
}
if atomic.LoadInt64(&code) != tc.code {
t.Errorf("expected %d to be %d", code, tc.code)
}
combined := ui.OutputWriter.String() + ui.ErrorWriter.String()
if !strings.Contains(combined, tc.out) {
t.Fatalf("expected %q to contain %q", combined, tc.out)
}
<-stopCh
})
}
}

View File

@@ -412,7 +412,7 @@ func (c *ServerCommand) runRecoveryMode() int {
return 1 return 1
} }
c.logger = log.New(&log.LoggerOptions{ c.logger = log.NewInterceptLogger(&log.LoggerOptions{
Output: c.gatedWriter, Output: c.gatedWriter,
Level: level, Level: level,
// Note that if logFormat is either unspecified or standard, then // Note that if logFormat is either unspecified or standard, then
@@ -868,14 +868,16 @@ func (c *ServerCommand) Run(args []string) int {
return 1 return 1
} }
config.LogFormat = logFormat.String()
if c.flagDevThreeNode || c.flagDevFourCluster { if c.flagDevThreeNode || c.flagDevFourCluster {
c.logger = log.New(&log.LoggerOptions{ c.logger = log.NewInterceptLogger(&log.LoggerOptions{
Mutex: &sync.Mutex{}, Mutex: &sync.Mutex{},
Output: c.gatedWriter, Output: c.gatedWriter,
Level: log.Trace, Level: log.Trace,
}) })
} else { } else {
c.logger = log.New(&log.LoggerOptions{ c.logger = log.NewInterceptLogger(&log.LoggerOptions{
Output: c.gatedWriter, Output: c.gatedWriter,
Level: level, Level: level,
// Note that if logFormat is either unspecified or standard, then // Note that if logFormat is either unspecified or standard, then

2
go.mod
View File

@@ -101,7 +101,7 @@ require (
github.com/kr/pretty v0.2.0 github.com/kr/pretty v0.2.0
github.com/kr/text v0.1.0 github.com/kr/text v0.1.0
github.com/lib/pq v1.2.0 github.com/lib/pq v1.2.0
github.com/mattn/go-colorable v0.1.4 github.com/mattn/go-colorable v0.1.6
github.com/mholt/archiver v3.1.1+incompatible github.com/mholt/archiver v3.1.1+incompatible
github.com/michaelklishin/rabbit-hole v0.0.0-20191008194146-93d9988f0cd5 github.com/michaelklishin/rabbit-hole v0.0.0-20191008194146-93d9988f0cd5
github.com/mitchellh/cli v1.0.0 github.com/mitchellh/cli v1.0.0

17
go.sum
View File

@@ -191,9 +191,12 @@ github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHo
github.com/coreos/go-semver v0.2.0 h1:3Jm3tLmsgAYcjC+4Up7hJrFBPr+n7rAqYeSw/SZazuY= github.com/coreos/go-semver v0.2.0 h1:3Jm3tLmsgAYcjC+4Up7hJrFBPr+n7rAqYeSw/SZazuY=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd/v22 v22.0.0 h1:XJIw/+VlJ+87J+doOxznsAWIdmWuViOVhkQamW5YV28= github.com/coreos/go-systemd/v22 v22.0.0 h1:XJIw/+VlJ+87J+doOxznsAWIdmWuViOVhkQamW5YV28=
github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
@@ -234,7 +237,6 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/evanphx/json-patch v0.0.0-20190203023257-5858425f7550/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v0.0.0-20190203023257-5858425f7550/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
@@ -294,6 +296,7 @@ github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXP
github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A= github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef h1:veQD95Isof8w9/WXiA+pa3tz3fJXkt5B7QaRBrM62gk=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -406,6 +409,7 @@ github.com/hashicorp/go-hclog v0.8.0/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrj
github.com/hashicorp/go-hclog v0.9.1/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-hclog v0.9.1/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-hclog v0.13.0 h1:Do32YnDMnq7v7FU50AgH+1ExKCOkl9HBxvSI1JWr+rA= github.com/hashicorp/go-hclog v0.13.0 h1:Do32YnDMnq7v7FU50AgH+1ExKCOkl9HBxvSI1JWr+rA=
github.com/hashicorp/go-hclog v0.13.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-hclog v0.13.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
@@ -494,8 +498,6 @@ github.com/hashicorp/vault-plugin-database-mongodbatlas v0.1.1 h1:fA6cFH8lIPH2M4
github.com/hashicorp/vault-plugin-database-mongodbatlas v0.1.1/go.mod h1:MP3kfr0N+7miOTZFwKv952b9VkXM4S2Q6YtQCiNKWq8= github.com/hashicorp/vault-plugin-database-mongodbatlas v0.1.1/go.mod h1:MP3kfr0N+7miOTZFwKv952b9VkXM4S2Q6YtQCiNKWq8=
github.com/hashicorp/vault-plugin-secrets-ad v0.6.4-beta1.0.20200518124111-3dceeb3ce90e h1:0GK1BNBfglD2sydZ4XXMjJElhY8bC2TDdc0vk1Q9zbA= github.com/hashicorp/vault-plugin-secrets-ad v0.6.4-beta1.0.20200518124111-3dceeb3ce90e h1:0GK1BNBfglD2sydZ4XXMjJElhY8bC2TDdc0vk1Q9zbA=
github.com/hashicorp/vault-plugin-secrets-ad v0.6.4-beta1.0.20200518124111-3dceeb3ce90e/go.mod h1:SCsKcChP8yrtOHXOeTD7oRk0oflj3IxA9y9zTOGtQ8s= github.com/hashicorp/vault-plugin-secrets-ad v0.6.4-beta1.0.20200518124111-3dceeb3ce90e/go.mod h1:SCsKcChP8yrtOHXOeTD7oRk0oflj3IxA9y9zTOGtQ8s=
github.com/hashicorp/vault-plugin-secrets-ad v0.6.5 h1:wrHzXSD6qmKvkuHaQn+BNj89+HGhMNchxAckGnd7YTc=
github.com/hashicorp/vault-plugin-secrets-ad v0.6.5/go.mod h1:kk98nB+cwDbt3I7UGQq3ota7+eHZrGSTQZfSRGpluvA=
github.com/hashicorp/vault-plugin-secrets-alicloud v0.5.5 h1:BOOtSls+BQ1EtPmpE9LoqZztsEZ1fRWVSkHWtRIrCB4= github.com/hashicorp/vault-plugin-secrets-alicloud v0.5.5 h1:BOOtSls+BQ1EtPmpE9LoqZztsEZ1fRWVSkHWtRIrCB4=
github.com/hashicorp/vault-plugin-secrets-alicloud v0.5.5/go.mod h1:gAoReoUpBHaBwkxQqTK7FY8nQC0MuaZHLiW5WOSny5g= github.com/hashicorp/vault-plugin-secrets-alicloud v0.5.5/go.mod h1:gAoReoUpBHaBwkxQqTK7FY8nQC0MuaZHLiW5WOSny5g=
github.com/hashicorp/vault-plugin-secrets-azure v0.5.6 h1:4PgQ5rCT29wW5PMyebEhPkEYuR5s+SnInuZz3x2cP50= github.com/hashicorp/vault-plugin-secrets-azure v0.5.6 h1:4PgQ5rCT29wW5PMyebEhPkEYuR5s+SnInuZz3x2cP50=
@@ -589,12 +591,12 @@ github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czP
github.com/martini-contrib/render v0.0.0-20150707142108-ec18f8345a11 h1:YFh+sjyJTMQSYjKwM4dFKhJPJC/wfo98tPUc17HdoYw= github.com/martini-contrib/render v0.0.0-20150707142108-ec18f8345a11 h1:YFh+sjyJTMQSYjKwM4dFKhJPJC/wfo98tPUc17HdoYw=
github.com/martini-contrib/render v0.0.0-20150707142108-ec18f8345a11/go.mod h1:Ah2dBMoxZEqk118as2T4u4fjfXarE0pPnMJaArZQZsI= github.com/martini-contrib/render v0.0.0-20150707142108-ec18f8345a11/go.mod h1:Ah2dBMoxZEqk118as2T4u4fjfXarE0pPnMJaArZQZsI=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10=
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
@@ -978,15 +980,20 @@ golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

166
helper/monitor/monitor.go Normal file
View File

@@ -0,0 +1,166 @@
package monitor
import (
"fmt"
"time"
log "github.com/hashicorp/go-hclog"
"go.uber.org/atomic"
)
// Monitor provides a mechanism to stream logs using go-hclog
// InterceptLogger and SinkAdapter. It allows streaming of logs
// at a different log level than what is set on the logger.
type Monitor interface {
// Start returns a channel of log messages which are sent
// every time a log message occurs
Start() <-chan []byte
// Stop de-registers the sink from the InterceptLogger
// and closes the log channels
Stop()
}
// monitor implements the Monitor interface. Note that this
// struct is not threadsafe.
type monitor struct {
sink log.SinkAdapter
// logger is the logger we will be monitoring
logger log.InterceptLogger
// logCh is a buffered chan where we send logs when streaming
logCh chan []byte
// doneCh coordinates the shutdown of logCh
doneCh chan struct{}
// droppedCount is the current count of messages
// that were dropped from the logCh buffer.
droppedCount *atomic.Uint32
bufSize int
// dropCheckInterval is the amount of time we should
// wait to check for dropped messages. Defaults
// to 3 seconds
dropCheckInterval time.Duration
// started is whether the monitor has been started or not.
// This is to ensure that we don't start it again until
// it has been shut down.
started *atomic.Bool
}
// NewMonitor creates a new Monitor. Start must be called in order to actually start
// streaming logs. buf is the buffer size of the channel that sends log messages.
func NewMonitor(buf int, logger log.InterceptLogger, opts *log.LoggerOptions) (Monitor, error) {
return newMonitor(buf, logger, opts)
}
func newMonitor(buf int, logger log.InterceptLogger, opts *log.LoggerOptions) (*monitor, error) {
if buf <= 0 {
return nil, fmt.Errorf("buf must be greater than zero")
}
sw := &monitor{
logger: logger,
logCh: make(chan []byte, buf),
doneCh: make(chan struct{}),
bufSize: buf,
dropCheckInterval: 3 * time.Second,
droppedCount: atomic.NewUint32(0),
started: atomic.NewBool(false),
}
opts.Output = sw
sink := log.NewSinkAdapter(opts)
sw.sink = sink
return sw, nil
}
// Stop deregisters the sink and stops the monitoring process
func (d *monitor) Stop() {
d.logger.DeregisterSink(d.sink)
close(d.doneCh)
d.started.Store(false)
}
// Start registers a sink on the monitor's logger and starts sending
// received log messages over the returned channel.
func (d *monitor) Start() <-chan []byte {
// Check to see if this has already been started. If not, flag
// it and proceed. If so, bail out early.
if !d.started.CAS(false, true) {
return nil
}
// register our sink with the logger
d.logger.RegisterSink(d.sink)
streamCh := make(chan []byte, d.bufSize)
// Run a go routine that listens for streamed
// log messages and sends them to streamCh.
//
// It also periodically checks for dropped
// messages and makes room on the logCh to add
// a dropped message count warning
go func() {
defer close(streamCh)
ticker := time.NewTicker(d.dropCheckInterval)
defer ticker.Stop()
var logMessage []byte
for {
logMessage = nil
select {
case <-ticker.C:
// Check if there have been any dropped messages.
dc := d.droppedCount.Load()
if dc > 0 {
logMessage = []byte(fmt.Sprintf("Monitor dropped %d logs during monitor request\n", dc))
d.droppedCount.Swap(0)
}
case logMessage = <-d.logCh:
case <-d.doneCh:
return
}
if len(logMessage) > 0 {
select {
case <-d.doneCh:
return
case streamCh <- logMessage:
}
}
}
}()
return streamCh
}
// Write attempts to send latest log to logCh
// it drops the log if channel is unavailable to receive
func (d *monitor) Write(p []byte) (n int, err error) {
// ensure logCh is still open
select {
case <-d.doneCh:
return
default:
}
bytes := make([]byte, len(p))
copy(bytes, p)
select {
case d.logCh <- bytes:
default:
d.droppedCount.Add(1)
}
return len(p), nil
}

View File

@@ -0,0 +1,96 @@
package monitor
import (
"fmt"
"strings"
"testing"
"time"
log "github.com/hashicorp/go-hclog"
"github.com/stretchr/testify/require"
)
func TestMonitor_Start(t *testing.T) {
t.Parallel()
logger := log.NewInterceptLogger(&log.LoggerOptions{
Level: log.Error,
})
m, _ := NewMonitor(512, logger, &log.LoggerOptions{
Level: log.Debug,
})
logCh := m.Start()
defer m.Stop()
go func() {
logger.Debug("test log")
time.Sleep(10 * time.Millisecond)
}()
select {
case l := <-logCh:
require.Contains(t, string(l), "[DEBUG] test log")
return
case <-time.After(5 * time.Second):
t.Fatal("Expected to receive from log channel")
}
}
func TestMonitor_Start_Unbuffered(t *testing.T) {
t.Parallel()
logger := log.NewInterceptLogger(&log.LoggerOptions{
Level: log.Error,
})
_, err := NewMonitor(0, logger, &log.LoggerOptions{
Level: log.Debug,
})
if err == nil {
t.Fatal("expected to get an error, but didn't")
} else {
if !strings.Contains(err.Error(), "greater than zero") {
t.Fatal("expected an error about buf being greater than zero")
}
}
}
// Ensure number of dropped messages are logged
func TestMonitor_DroppedMessages(t *testing.T) {
t.Parallel()
logger := log.NewInterceptLogger(&log.LoggerOptions{
Level: log.Warn,
})
m, _ := newMonitor(5, logger, &log.LoggerOptions{
Level: log.Debug,
})
m.dropCheckInterval = 5 * time.Millisecond
logCh := m.Start()
defer m.Stop()
for i := 0; i <= 100; i++ {
logger.Debug(fmt.Sprintf("test message %d", i))
}
passed := make(chan struct{})
go func() {
for recv := range logCh {
if strings.Contains(string(recv), "Monitor dropped") {
close(passed)
return
}
}
}()
select {
case <-passed:
case <-time.After(2 * time.Second):
require.Fail(t, "expected to see warn dropped messages")
}
}

View File

@@ -493,3 +493,39 @@ func SetRaftAddressProviders(t testing.T, cluster *vault.TestCluster, provider r
core.UnderlyingRawStorage.(*raft.RaftBackend).SetServerAddressProvider(provider) core.UnderlyingRawStorage.(*raft.RaftBackend).SetServerAddressProvider(provider)
} }
} }
func GenerateDebugLogs(t testing.T, client *api.Client) chan struct{} {
t.Helper()
stopCh := make(chan struct{})
ticker := time.NewTicker(time.Second)
var err error
go func() {
for {
select {
case <-stopCh:
ticker.Stop()
stopCh <- struct{}{}
return
case <-ticker.C:
err = client.Sys().Mount("foo", &api.MountInput{
Type: "kv",
Options: map[string]string{
"version": "1",
},
})
if err != nil {
t.Fatal(err)
}
err = client.Sys().Unmount("foo")
if err != nil {
t.Fatal(err)
}
}
}
}()
return stopCh
}

View File

@@ -133,6 +133,7 @@ func Handler(props *vault.HandlerProperties) http.Handler {
mux.Handle("/v1/sys/unseal", handleSysUnseal(core)) mux.Handle("/v1/sys/unseal", handleSysUnseal(core))
mux.Handle("/v1/sys/leader", handleSysLeader(core)) mux.Handle("/v1/sys/leader", handleSysLeader(core))
mux.Handle("/v1/sys/health", handleSysHealth(core)) mux.Handle("/v1/sys/health", handleSysHealth(core))
mux.Handle("/v1/sys/monitor", handleLogicalNoForward(core))
mux.Handle("/v1/sys/generate-root/attempt", handleRequestForwarding(core, mux.Handle("/v1/sys/generate-root/attempt", handleRequestForwarding(core,
handleAuditNonLogical(core, handleSysGenerateRootAttempt(core, vault.GenerateStandardRootTokenStrategy)))) handleAuditNonLogical(core, handleSysGenerateRootAttempt(core, vault.GenerateStandardRootTokenStrategy))))
mux.Handle("/v1/sys/generate-root/update", handleRequestForwarding(core, mux.Handle("/v1/sys/generate-root/update", handleRequestForwarding(core,
@@ -274,8 +275,12 @@ func wrapGenericHandler(core *vault.Core, h http.Handler, props *vault.HandlerPr
// Start with the request context // Start with the request context
ctx := r.Context() ctx := r.Context()
var cancelFunc context.CancelFunc var cancelFunc context.CancelFunc
// Add our timeout // Add our timeout, but not for the monitor endpoint, as it's streaming
ctx, cancelFunc = context.WithTimeout(ctx, maxRequestDuration) if strings.HasSuffix(r.URL.Path, "sys/monitor") {
ctx, cancelFunc = context.WithCancel(ctx)
} else {
ctx, cancelFunc = context.WithTimeout(ctx, maxRequestDuration)
}
// Add a size limiter if desired // Add a size limiter if desired
if maxRequestSize > 0 { if maxRequestSize > 0 {
ctx = context.WithValue(ctx, "max_request_size", maxRequestSize) ctx = context.WithValue(ctx, "max_request_size", maxRequestSize)

View File

@@ -86,6 +86,9 @@ func buildLogicalRequestNoAuth(perfStandby bool, w http.ResponseWriter, r *http.
responseWriter = w responseWriter = w
case path == "sys/storage/raft/snapshot": case path == "sys/storage/raft/snapshot":
responseWriter = w responseWriter = w
case path == "sys/monitor":
passHTTPReq = true
responseWriter = w
} }
case "POST", "PUT": case "POST", "PUT":
@@ -142,13 +145,13 @@ func buildLogicalRequestNoAuth(perfStandby bool, w http.ResponseWriter, r *http.
return nil, nil, http.StatusMethodNotAllowed, nil return nil, nil, http.StatusMethodNotAllowed, nil
} }
request_id, err := uuid.GenerateUUID() requestId, err := uuid.GenerateUUID()
if err != nil { if err != nil {
return nil, nil, http.StatusBadRequest, errwrap.Wrapf("failed to generate identifier for the request: {{err}}", err) return nil, nil, http.StatusBadRequest, errwrap.Wrapf("failed to generate identifier for the request: {{err}}", err)
} }
req := &logical.Request{ req := &logical.Request{
ID: request_id, ID: requestId,
Operation: op, Operation: op,
Path: path, Path: path,
Data: data, Data: data,

View File

@@ -43,6 +43,7 @@ func fetchStatusCode(r *http.Request, field string) (int, bool, bool) {
func handleSysHealthGet(core *vault.Core, w http.ResponseWriter, r *http.Request) { func handleSysHealthGet(core *vault.Core, w http.ResponseWriter, r *http.Request) {
code, body, err := getSysHealth(core, r) code, body, err := getSysHealth(core, r)
if err != nil { if err != nil {
core.Logger().Error("error checking health", "error", err) core.Logger().Error("error checking health", "error", err)
respondError(w, code, nil) respondError(w, code, nil)

82
http/sys_monitor_test.go Normal file
View File

@@ -0,0 +1,82 @@
package http
import (
"context"
"strings"
"testing"
"time"
log "github.com/hashicorp/go-hclog"
"github.com/hashicorp/vault/helper/testhelpers"
"github.com/hashicorp/vault/sdk/helper/logging"
"github.com/hashicorp/vault/vault"
)
func TestSysMonitorUnknownLogLevel(t *testing.T) {
cluster := vault.NewTestCluster(t, nil, &vault.TestClusterOptions{HandlerFunc: Handler})
cluster.Start()
defer cluster.Cleanup()
client := cluster.Cores[0].Client
request := client.NewRequest("GET", "/v1/sys/monitor")
request.Params.Add("log_level", "haha")
_, err := client.RawRequest(request)
if err == nil {
t.Fatal("expected to get an error, but didn't")
} else {
if !strings.Contains(err.Error(), "Code: 400") {
t.Fatalf("expected to receive a 400 error, but got %s instead", err)
}
if !strings.Contains(err.Error(), "unknown log level") {
t.Fatalf("expected to receive a message indicating an unknown log level, but got %s instead", err)
}
}
}
func TestSysMonitorStreamingLogs(t *testing.T) {
logger := log.NewInterceptLogger(&log.LoggerOptions{
Output: log.DefaultOutput,
Level: log.Debug,
JSONFormat: logging.ParseEnvLogFormat() == logging.JSONFormat,
})
cluster := vault.NewTestCluster(t, nil, &vault.TestClusterOptions{HandlerFunc: Handler, Logger: logger})
cluster.Start()
defer cluster.Cleanup()
client := cluster.Cores[0].Client
stopCh := testhelpers.GenerateDebugLogs(t, client)
debugCount := 0
ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second)
defer cancel()
logCh, err := client.Sys().Monitor(ctx, "DEBUG")
if err != nil {
t.Fatal(err)
}
timeCh := time.After(5 * time.Second)
for {
select {
case log := <-logCh:
if strings.Contains(log, "[DEBUG]") {
debugCount++
}
case <-timeCh:
t.Fatal("Failed to get a DEBUG message after 5 seconds")
}
// If we've seen multiple lines that match what we want,
// it's probably safe to assume streaming is working
if debugCount > 3 {
stopCh <- struct{}{}
break
}
}
<-stopCh
}

View File

@@ -185,13 +185,13 @@ func RespondWithStatusCode(resp *Response, req *Request, code int) (*Response, e
} }
// HTTPResponseWriter is optionally added to a request object and can be used to // HTTPResponseWriter is optionally added to a request object and can be used to
// write directly to the HTTP response writter. // write directly to the HTTP response writer.
type HTTPResponseWriter struct { type HTTPResponseWriter struct {
http.ResponseWriter http.ResponseWriter
written *uint32 written *uint32
} }
// NewHTTPResponseWriter creates a new HTTPRepoinseWriter object that wraps the // NewHTTPResponseWriter creates a new HTTPResponseWriter object that wraps the
// provided io.Writer. // provided io.Writer.
func NewHTTPResponseWriter(w http.ResponseWriter) *HTTPResponseWriter { func NewHTTPResponseWriter(w http.ResponseWriter) *HTTPResponseWriter {
return &HTTPResponseWriter{ return &HTTPResponseWriter{

View File

@@ -726,22 +726,23 @@ func NewCore(conf *CoreConfig) (*Core, error) {
// Setup the core // Setup the core
c := &Core{ c := &Core{
entCore: entCore{}, entCore: entCore{},
devToken: conf.DevToken, devToken: conf.DevToken,
physical: conf.Physical, physical: conf.Physical,
serviceRegistration: conf.GetServiceRegistration(), serviceRegistration: conf.GetServiceRegistration(),
underlyingPhysical: conf.Physical, underlyingPhysical: conf.Physical,
storageType: conf.StorageType, storageType: conf.StorageType,
redirectAddr: conf.RedirectAddr, redirectAddr: conf.RedirectAddr,
clusterAddr: new(atomic.Value), clusterAddr: new(atomic.Value),
clusterListener: new(atomic.Value), clusterListener: new(atomic.Value),
seal: conf.Seal, seal: conf.Seal,
router: NewRouter(), router: NewRouter(),
sealed: new(uint32), sealed: new(uint32),
sealMigrated: new(uint32), sealMigrated: new(uint32),
standby: true, standby: true,
baseLogger: conf.Logger, baseLogger: conf.Logger,
logger: conf.Logger.Named("core"), logger: conf.Logger.Named("core"),
defaultLeaseTTL: conf.DefaultLeaseTTL, defaultLeaseTTL: conf.DefaultLeaseTTL,
maxLeaseTTL: conf.MaxLeaseTTL, maxLeaseTTL: conf.MaxLeaseTTL,
cachingDisabled: conf.DisableCache, cachingDisabled: conf.DisableCache,
@@ -2345,6 +2346,12 @@ func (c *Core) SanitizedConfig() map[string]interface{} {
return conf.(*server.Config).Sanitized() return conf.(*server.Config).Sanitized()
} }
// LogFormat returns the log format current in use.
func (c *Core) LogFormat() string {
conf := c.rawConfig.Load()
return conf.(*server.Config).LogFormat
}
// MetricsHelper returns the global metrics helper which allows external // MetricsHelper returns the global metrics helper which allows external
// packages to access Vault's internal metrics. // packages to access Vault's internal metrics.
func (c *Core) MetricsHelper() *metricsutil.MetricsHelper { func (c *Core) MetricsHelper() *metricsutil.MetricsHelper {

View File

@@ -24,6 +24,7 @@ import (
func TestKVv2_UpgradePaths(t *testing.T) { func TestKVv2_UpgradePaths(t *testing.T) {
m := new(sync.Mutex) m := new(sync.Mutex)
logOut := new(bytes.Buffer) logOut := new(bytes.Buffer)
logger := hclog.New(&hclog.LoggerOptions{ logger := hclog.New(&hclog.LoggerOptions{
Output: logOut, Output: logOut,
Mutex: m, Mutex: m,

View File

@@ -14,7 +14,6 @@ import (
// https://github.com/hashicorp/vault/pull/6920 // https://github.com/hashicorp/vault/pull/6920
func TestRecoverFromPanic(t *testing.T) { func TestRecoverFromPanic(t *testing.T) {
logger := hclog.New(nil) logger := hclog.New(nil)
coreConfig := &vault.CoreConfig{ coreConfig := &vault.CoreConfig{
LogicalBackends: map[string]logical.Factory{ LogicalBackends: map[string]logical.Factory{
"noop": vault.NoopBackendFactory, "noop": vault.NoopBackendFactory,

View File

@@ -21,11 +21,12 @@ import (
"github.com/hashicorp/errwrap" "github.com/hashicorp/errwrap"
log "github.com/hashicorp/go-hclog" log "github.com/hashicorp/go-hclog"
memdb "github.com/hashicorp/go-memdb" memdb "github.com/hashicorp/go-memdb"
multierror "github.com/hashicorp/go-multierror" "github.com/hashicorp/go-multierror"
uuid "github.com/hashicorp/go-uuid" uuid "github.com/hashicorp/go-uuid"
"github.com/hashicorp/vault/helper/hostutil" "github.com/hashicorp/vault/helper/hostutil"
"github.com/hashicorp/vault/helper/identity" "github.com/hashicorp/vault/helper/identity"
"github.com/hashicorp/vault/helper/metricsutil" "github.com/hashicorp/vault/helper/metricsutil"
"github.com/hashicorp/vault/helper/monitor"
"github.com/hashicorp/vault/helper/namespace" "github.com/hashicorp/vault/helper/namespace"
"github.com/hashicorp/vault/physical/raft" "github.com/hashicorp/vault/physical/raft"
"github.com/hashicorp/vault/sdk/framework" "github.com/hashicorp/vault/sdk/framework"
@@ -157,6 +158,7 @@ func NewSystemBackend(core *Core, logger log.Logger) *SystemBackend {
b.Backend.Paths = append(b.Backend.Paths, b.pprofPaths()...) b.Backend.Paths = append(b.Backend.Paths, b.pprofPaths()...)
b.Backend.Paths = append(b.Backend.Paths, b.remountPath()) b.Backend.Paths = append(b.Backend.Paths, b.remountPath())
b.Backend.Paths = append(b.Backend.Paths, b.metricsPath()) b.Backend.Paths = append(b.Backend.Paths, b.metricsPath())
b.Backend.Paths = append(b.Backend.Paths, b.monitorPath())
b.Backend.Paths = append(b.Backend.Paths, b.hostInfoPath()) b.Backend.Paths = append(b.Backend.Paths, b.hostInfoPath())
if core.rawEnabled { if core.rawEnabled {
@@ -2514,6 +2516,72 @@ func (b *SystemBackend) handleMetrics(ctx context.Context, req *logical.Request,
return b.Core.metricsHelper.ResponseForFormat(format), nil return b.Core.metricsHelper.ResponseForFormat(format), nil
} }
func (b *SystemBackend) handleMonitor(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
ll := data.Get("log_level").(string)
w := req.ResponseWriter
resp := &logical.Response{}
if ll == "" {
ll = "info"
}
logLevel := log.LevelFromString(ll)
if logLevel == log.NoLevel {
return logical.ErrorResponse("unknown log level"), nil
}
flusher, ok := w.ResponseWriter.(http.Flusher)
if !ok {
return logical.ErrorResponse("streaming not supported"), nil
}
isJson := b.Core.LogFormat() == "json"
logger := b.Core.Logger().(log.InterceptLogger)
mon, err := monitor.NewMonitor(512, logger, &log.LoggerOptions{
Level: logLevel,
JSONFormat: isJson,
})
defer mon.Stop()
if err != nil {
return resp, err
}
logCh := mon.Start()
if logCh == nil {
return resp, fmt.Errorf("error trying to start a monitor that's already been started")
}
w.WriteHeader(http.StatusOK)
// 0 byte write is needed before the Flush call so that if we are using
// a gzip stream it will go ahead and write out the HTTP response header
_, err = w.Write([]byte(""))
if err != nil {
return resp, fmt.Errorf("error seeding flusher: %w", err)
}
flusher.Flush()
// Stream logs until the connection is closed.
for {
select {
case <-ctx.Done():
return resp, nil
case l := <-logCh:
_, err = fmt.Fprint(w, string(l))
if err != nil {
return resp, err
}
flusher.Flush()
}
}
}
// handleHostInfo collects and returns host-related information, which includes // handleHostInfo collects and returns host-related information, which includes
// system information, cpu, disk, and memory usage. Any capture-related errors // system information, cpu, disk, and memory usage. Any capture-related errors
// returned by the collection method will be returned as response warnings. // returned by the collection method will be returned as response warnings.
@@ -3400,7 +3468,7 @@ This path responds to the following HTTP methods.
Sets the header value for the UI. Sets the header value for the UI.
DELETE /<header> DELETE /<header>
Clears the header value for UI. Clears the header value for UI.
LIST / LIST /
List the headers configured for the UI. List the headers configured for the UI.
`, `,

View File

@@ -1199,6 +1199,25 @@ func (b *SystemBackend) metricsPath() *framework.Path {
} }
func (b *SystemBackend) monitorPath() *framework.Path {
return &framework.Path{
Pattern: "monitor",
Fields: map[string]*framework.FieldSchema{
"log_level": &framework.FieldSchema{
Type: framework.TypeString,
Description: "Log level to view system logs at. Currently supported values are \"trace\", \"debug\", \"info\", \"warn\", \"error\".",
Query: true,
},
},
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.ReadOperation: b.handleMonitor,
},
HelpSynopsis: strings.TrimSpace(sysHelp["monitor"][0]),
HelpDescription: strings.TrimSpace(sysHelp["monitor"][1]),
}
}
func (b *SystemBackend) hostInfoPath() *framework.Path { func (b *SystemBackend) hostInfoPath() *framework.Path {
return &framework.Path{ return &framework.Path{
Pattern: "host-info/?", Pattern: "host-info/?",

View File

@@ -14,6 +14,7 @@ import (
"encoding/pem" "encoding/pem"
"errors" "errors"
"fmt" "fmt"
"github.com/hashicorp/vault/internalshared/configutil"
"io" "io"
"io/ioutil" "io/ioutil"
"math/big" "math/big"
@@ -1493,7 +1494,9 @@ func NewTestCluster(t testing.T, base *CoreConfig, opts *TestClusterOptions) *Te
} }
if coreConfig.RawConfig == nil { if coreConfig.RawConfig == nil {
coreConfig.RawConfig = new(server.Config) c := new(server.Config)
c.SharedConfig = &configutil.SharedConfig{LogFormat: logging.UnspecifiedFormat.String()}
coreConfig.RawConfig = c
} }
addAuditBackend := len(coreConfig.AuditBackends) == 0 addAuditBackend := len(coreConfig.AuditBackends) == 0

View File

@@ -1,5 +0,0 @@
language: go
go:
- 1.8.x
- tip

View File

@@ -1,27 +0,0 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
name = "github.com/mattn/go-colorable"
packages = ["."]
revision = "167de6bfdfba052fa6b2d3664c8f5272e23c9072"
version = "v0.0.9"
[[projects]]
name = "github.com/mattn/go-isatty"
packages = ["."]
revision = "0360b2af4f38e8d38c7fce2a9f4e702702d73a39"
version = "v0.0.3"
[[projects]]
branch = "master"
name = "golang.org/x/sys"
packages = ["unix"]
revision = "37707fdb30a5b38865cfb95e5aab41707daec7fd"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "e8a50671c3cb93ea935bf210b1cd20702876b9d9226129be581ef646d1565cdc"
solver-name = "gps-cdcl"
solver-version = 1

View File

@@ -1,30 +0,0 @@
# Gopkg.toml example
#
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
[[constraint]]
name = "github.com/mattn/go-colorable"
version = "0.0.9"
[[constraint]]
name = "github.com/mattn/go-isatty"
version = "0.0.3"

View File

@@ -1,6 +1,12 @@
# Color [![GoDoc](https://godoc.org/github.com/fatih/color?status.svg)](https://godoc.org/github.com/fatih/color) [![Build Status](https://img.shields.io/travis/fatih/color.svg?style=flat-square)](https://travis-ci.org/fatih/color) # Archived project. No maintenance.
This project is not maintained anymore and is archived. Feel free to fork and
make your own changes if needed. For more detail read my blog post: [Taking an indefinite sabbatical from my projects](https://arslan.io/2018/10/09/taking-an-indefinite-sabbatical-from-my-projects/)
Thanks to everyone for their valuable feedback and contributions.
# Color [![GoDoc](https://godoc.org/github.com/fatih/color?status.svg)](https://godoc.org/github.com/fatih/color)
Color lets you use colorized outputs in terms of [ANSI Escape Color lets you use colorized outputs in terms of [ANSI Escape
Codes](http://en.wikipedia.org/wiki/ANSI_escape_code#Colors) in Go (Golang). It Codes](http://en.wikipedia.org/wiki/ANSI_escape_code#Colors) in Go (Golang). It
@@ -17,9 +23,6 @@ suits you.
go get github.com/fatih/color go get github.com/fatih/color
``` ```
Note that the `vendor` folder is here for stability. Remove the folder if you
already have the dependencies in your GOPATH.
## Examples ## Examples
### Standard colors ### Standard colors

8
vendor/github.com/fatih/color/go.mod generated vendored Normal file
View File

@@ -0,0 +1,8 @@
module github.com/fatih/color
go 1.13
require (
github.com/mattn/go-colorable v0.1.4
github.com/mattn/go-isatty v0.0.11
)

8
vendor/github.com/fatih/color/go.sum generated vendored Normal file
View File

@@ -0,0 +1,8 @@
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

View File

@@ -290,14 +290,14 @@ func (c *Child) start() error {
// down the exit channel. // down the exit channel.
c.stopLock.RLock() c.stopLock.RLock()
defer c.stopLock.RUnlock() defer c.stopLock.RUnlock()
if c.stopped { if !c.stopped {
return select {
case <-c.stopCh:
case exitCh <- code:
}
} }
select { close(exitCh)
case <-c.stopCh:
case exitCh <- code:
}
}() }()
c.exitCh = exitCh c.exitCh = exitCh
@@ -365,16 +365,13 @@ func (c *Child) reload() error {
return c.signal(c.reloadSignal) return c.signal(c.reloadSignal)
} }
// kill sends the signal to kill the process using the configured signal
// if set, else the default system signal
func (c *Child) kill(immediately bool) { func (c *Child) kill(immediately bool) {
if !c.running() { if !c.running() {
return
}
exited := false
process := c.cmd.Process
if c.cmd.ProcessState != nil {
log.Printf("[DEBUG] (child) Kill() called but process dead; not waiting for splay.") log.Printf("[DEBUG] (child) Kill() called but process dead; not waiting for splay.")
return
} else if immediately { } else if immediately {
log.Printf("[DEBUG] (child) Kill() called but performing immediate shutdown; not waiting for splay.") log.Printf("[DEBUG] (child) Kill() called but performing immediate shutdown; not waiting for splay.")
} else { } else {
@@ -384,6 +381,9 @@ func (c *Child) kill(immediately bool) {
} }
} }
exited := false
process := c.cmd.Process
if c.killSignal != nil { if c.killSignal != nil {
if err := process.Signal(c.killSignal); err == nil { if err := process.Signal(c.killSignal); err == nil {
// Wait a few seconds for it to exit // Wait a few seconds for it to exit
@@ -410,6 +410,11 @@ func (c *Child) kill(immediately bool) {
} }
func (c *Child) running() bool { func (c *Child) running() bool {
select {
case <-c.exitCh:
return false
default:
}
return c.cmd != nil && c.cmd.Process != nil return c.cmd != nil && c.cmd.Process != nil
} }

View File

@@ -33,6 +33,9 @@ const (
// DefaultKillSignal is the default signal for termination. // DefaultKillSignal is the default signal for termination.
DefaultKillSignal = syscall.SIGINT DefaultKillSignal = syscall.SIGINT
// DefaultBlockQueryWaitTime is amount of time in seconds to do a blocking query for
DefaultBlockQueryWaitTime = 60 * time.Second
) )
var ( var (
@@ -48,6 +51,9 @@ type Config struct {
// Dedup is used to configure the dedup settings // Dedup is used to configure the dedup settings
Dedup *DedupConfig `mapstructure:"deduplicate"` Dedup *DedupConfig `mapstructure:"deduplicate"`
// DefaultDelims is used to configure the default delimiters for templates
DefaultDelims *DefaultDelims `mapstructure:"default_delimiters"`
// Exec is the configuration for exec/supervise mode. // Exec is the configuration for exec/supervise mode.
Exec *ExecConfig `mapstructure:"exec"` Exec *ExecConfig `mapstructure:"exec"`
@@ -84,6 +90,9 @@ type Config struct {
// Additional command line options // Additional command line options
// Run once, executing each template exactly once, and exit // Run once, executing each template exactly once, and exit
Once bool Once bool
// BlockQueryWaitTime is amount of time in seconds to do a blocking query for
BlockQueryWaitTime *time.Duration `mapstructure:"block_query_wait"`
} }
// Copy returns a deep copy of the current configuration. This is useful because // Copy returns a deep copy of the current configuration. This is useful because
@@ -104,6 +113,10 @@ func (c *Config) Copy() *Config {
o.Dedup = c.Dedup.Copy() o.Dedup = c.Dedup.Copy()
} }
if c.DefaultDelims != nil {
o.DefaultDelims = c.DefaultDelims.Copy()
}
if c.Exec != nil { if c.Exec != nil {
o.Exec = c.Exec.Copy() o.Exec = c.Exec.Copy()
} }
@@ -136,6 +149,8 @@ func (c *Config) Copy() *Config {
o.Once = c.Once o.Once = c.Once
o.BlockQueryWaitTime = c.BlockQueryWaitTime
return &o return &o
} }
@@ -163,6 +178,10 @@ func (c *Config) Merge(o *Config) *Config {
r.Dedup = r.Dedup.Merge(o.Dedup) r.Dedup = r.Dedup.Merge(o.Dedup)
} }
if o.DefaultDelims != nil {
r.DefaultDelims = r.DefaultDelims.Merge(o.DefaultDelims)
}
if o.Exec != nil { if o.Exec != nil {
r.Exec = r.Exec.Merge(o.Exec) r.Exec = r.Exec.Merge(o.Exec)
} }
@@ -205,6 +224,8 @@ func (c *Config) Merge(o *Config) *Config {
r.Once = o.Once r.Once = o.Once
r.BlockQueryWaitTime = o.BlockQueryWaitTime
return r return r
} }
@@ -229,6 +250,7 @@ func Parse(s string) (*Config, error) {
"consul.ssl", "consul.ssl",
"consul.transport", "consul.transport",
"deduplicate", "deduplicate",
"default_delimiters",
"env", "env",
"exec", "exec",
"exec.env", "exec.env",
@@ -383,6 +405,7 @@ func (c *Config) GoString() string {
return fmt.Sprintf("&Config{"+ return fmt.Sprintf("&Config{"+
"Consul:%#v, "+ "Consul:%#v, "+
"Dedup:%#v, "+ "Dedup:%#v, "+
"DefaultDelims:%#v, "+
"Exec:%#v, "+ "Exec:%#v, "+
"KillSignal:%s, "+ "KillSignal:%s, "+
"LogLevel:%s, "+ "LogLevel:%s, "+
@@ -394,9 +417,11 @@ func (c *Config) GoString() string {
"Vault:%#v, "+ "Vault:%#v, "+
"Wait:%#v,"+ "Wait:%#v,"+
"Once:%#v"+ "Once:%#v"+
"BlockQueryWaitTime:%#v"+
"}", "}",
c.Consul, c.Consul,
c.Dedup, c.Dedup,
c.DefaultDelims,
c.Exec, c.Exec,
SignalGoString(c.KillSignal), SignalGoString(c.KillSignal),
StringGoString(c.LogLevel), StringGoString(c.LogLevel),
@@ -408,6 +433,7 @@ func (c *Config) GoString() string {
c.Vault, c.Vault,
c.Wait, c.Wait,
c.Once, c.Once,
TimeDurationGoString(c.BlockQueryWaitTime),
) )
} }
@@ -436,13 +462,14 @@ func (expected *Config) Diff(actual *Config) string {
// variables may be set which control the values for the default configuration. // variables may be set which control the values for the default configuration.
func DefaultConfig() *Config { func DefaultConfig() *Config {
return &Config{ return &Config{
Consul: DefaultConsulConfig(), Consul: DefaultConsulConfig(),
Dedup: DefaultDedupConfig(), Dedup: DefaultDedupConfig(),
Exec: DefaultExecConfig(), DefaultDelims: DefaultDefaultDelims(),
Syslog: DefaultSyslogConfig(), Exec: DefaultExecConfig(),
Templates: DefaultTemplateConfigs(), Syslog: DefaultSyslogConfig(),
Vault: DefaultVaultConfig(), Templates: DefaultTemplateConfigs(),
Wait: DefaultWaitConfig(), Vault: DefaultVaultConfig(),
Wait: DefaultWaitConfig(),
} }
} }
@@ -465,6 +492,10 @@ func (c *Config) Finalize() {
} }
c.Dedup.Finalize() c.Dedup.Finalize()
if c.DefaultDelims == nil {
c.DefaultDelims = DefaultDefaultDelims()
}
if c.Exec == nil { if c.Exec == nil {
c.Exec = DefaultExecConfig() c.Exec = DefaultExecConfig()
} }
@@ -517,6 +548,11 @@ func (c *Config) Finalize() {
if c.Once { if c.Once {
c.Wait = &WaitConfig{Enabled: Bool(false)} c.Wait = &WaitConfig{Enabled: Bool(false)}
} }
// defaults WaitTime to 60 seconds
if c.BlockQueryWaitTime == nil {
c.BlockQueryWaitTime = TimeDuration(DefaultBlockQueryWaitTime)
}
} }
func stringFromEnv(list []string, def string) *string { func stringFromEnv(list []string, def string) *string {

View File

@@ -8,6 +8,10 @@ type ConsulConfig struct {
// Address is the address of the Consul server. It may be an IP or FQDN. // Address is the address of the Consul server. It may be an IP or FQDN.
Address *string Address *string
// Namespace is the Consul namespace to use for reading/writing. This can
// also be set via the CONSUL_NAMESPACE environment variable.
Namespace *string `mapstructure:"namespace"`
// Auth is the HTTP basic authentication for communicating with Consul. // Auth is the HTTP basic authentication for communicating with Consul.
Auth *AuthConfig `mapstructure:"auth"` Auth *AuthConfig `mapstructure:"auth"`
@@ -46,6 +50,8 @@ func (c *ConsulConfig) Copy() *ConsulConfig {
o.Address = c.Address o.Address = c.Address
o.Namespace = c.Namespace
if c.Auth != nil { if c.Auth != nil {
o.Auth = c.Auth.Copy() o.Auth = c.Auth.Copy()
} }
@@ -89,6 +95,10 @@ func (c *ConsulConfig) Merge(o *ConsulConfig) *ConsulConfig {
r.Address = o.Address r.Address = o.Address
} }
if o.Namespace != nil {
r.Namespace = o.Namespace
}
if o.Auth != nil { if o.Auth != nil {
r.Auth = r.Auth.Merge(o.Auth) r.Auth = r.Auth.Merge(o.Auth)
} }
@@ -120,6 +130,10 @@ func (c *ConsulConfig) Finalize() {
}, "") }, "")
} }
if c.Namespace == nil {
c.Namespace = stringFromEnv([]string{"CONSUL_NAMESPACE"}, "")
}
if c.Auth == nil { if c.Auth == nil {
c.Auth = DefaultAuthConfig() c.Auth = DefaultAuthConfig()
} }
@@ -156,6 +170,7 @@ func (c *ConsulConfig) GoString() string {
return fmt.Sprintf("&ConsulConfig{"+ return fmt.Sprintf("&ConsulConfig{"+
"Address:%s, "+ "Address:%s, "+
"Namespace:%s, "+
"Auth:%#v, "+ "Auth:%#v, "+
"Retry:%#v, "+ "Retry:%#v, "+
"SSL:%#v, "+ "SSL:%#v, "+
@@ -163,6 +178,7 @@ func (c *ConsulConfig) GoString() string {
"Transport:%#v"+ "Transport:%#v"+
"}", "}",
StringGoString(c.Address), StringGoString(c.Address),
StringGoString(c.Namespace),
c.Auth, c.Auth,
c.Retry, c.Retry,
c.SSL, c.SSL,

View File

@@ -15,6 +15,9 @@ const (
// DefaultDedupMaxStale is the default max staleness for the deduplication // DefaultDedupMaxStale is the default max staleness for the deduplication
// manager. // manager.
DefaultDedupMaxStale = DefaultMaxStale DefaultDedupMaxStale = DefaultMaxStale
// DefaultDedupBlockQueryWaitTime is the default amount of time to do a blocking query for the deduplication
DefaultDedupBlockQueryWaitTime = 60 * time.Second
) )
// DedupConfig is used to enable the de-duplication mode, which depends // DedupConfig is used to enable the de-duplication mode, which depends
@@ -32,6 +35,9 @@ type DedupConfig struct {
// TTL is the Session TTL used for lock acquisition, defaults to 15 seconds. // TTL is the Session TTL used for lock acquisition, defaults to 15 seconds.
TTL *time.Duration `mapstructure:"ttl"` TTL *time.Duration `mapstructure:"ttl"`
// BlockQueryWaitTime is amount of time to do a blocking query for, defaults to 60 seconds.
BlockQueryWaitTime *time.Duration `mapstructure:"block_query_wait"`
} }
// DefaultDedupConfig returns a configuration that is populated with the // DefaultDedupConfig returns a configuration that is populated with the
@@ -51,6 +57,7 @@ func (c *DedupConfig) Copy() *DedupConfig {
o.MaxStale = c.MaxStale o.MaxStale = c.MaxStale
o.Prefix = c.Prefix o.Prefix = c.Prefix
o.TTL = c.TTL o.TTL = c.TTL
o.BlockQueryWaitTime = c.BlockQueryWaitTime
return &o return &o
} }
@@ -88,6 +95,10 @@ func (c *DedupConfig) Merge(o *DedupConfig) *DedupConfig {
r.TTL = o.TTL r.TTL = o.TTL
} }
if o.BlockQueryWaitTime != nil {
r.BlockQueryWaitTime = o.BlockQueryWaitTime
}
return r return r
} }
@@ -97,7 +108,8 @@ func (c *DedupConfig) Finalize() {
c.Enabled = Bool(false || c.Enabled = Bool(false ||
TimeDurationPresent(c.MaxStale) || TimeDurationPresent(c.MaxStale) ||
StringPresent(c.Prefix) || StringPresent(c.Prefix) ||
TimeDurationPresent(c.TTL)) TimeDurationPresent(c.TTL) ||
TimeDurationPresent(c.BlockQueryWaitTime))
} }
if c.MaxStale == nil { if c.MaxStale == nil {
@@ -111,6 +123,10 @@ func (c *DedupConfig) Finalize() {
if c.TTL == nil { if c.TTL == nil {
c.TTL = TimeDuration(DefaultDedupTTL) c.TTL = TimeDuration(DefaultDedupTTL)
} }
if c.BlockQueryWaitTime == nil {
c.BlockQueryWaitTime = TimeDuration(DefaultDedupBlockQueryWaitTime)
}
} }
// GoString defines the printable version of this struct. // GoString defines the printable version of this struct.
@@ -122,11 +138,13 @@ func (c *DedupConfig) GoString() string {
"Enabled:%s, "+ "Enabled:%s, "+
"MaxStale:%s, "+ "MaxStale:%s, "+
"Prefix:%s, "+ "Prefix:%s, "+
"TTL:%s"+ "TTL:%s, "+
"BlockQueryWaitTime:%s"+
"}", "}",
BoolGoString(c.Enabled), BoolGoString(c.Enabled),
TimeDurationGoString(c.MaxStale), TimeDurationGoString(c.MaxStale),
StringGoString(c.Prefix), StringGoString(c.Prefix),
TimeDurationGoString(c.TTL), TimeDurationGoString(c.TTL),
TimeDurationGoString(c.BlockQueryWaitTime),
) )
} }

View File

@@ -0,0 +1,53 @@
package config
// DefaultDelims is used to configure the default delimiters used for all templates
type DefaultDelims struct {
// Left is the left delimiter for templating
Left *string `mapstructure:"left"`
// Right is the right delimiter for templating
Right *string `mapstructure:"right"`
}
// DefaultDefaultDelims returns the default DefaultDelims
func DefaultDefaultDelims() *DefaultDelims {
return &DefaultDelims{}
}
// Copy returns a copy of the DefaultDelims
func (c *DefaultDelims) Copy() *DefaultDelims {
if c == nil {
return nil
}
return &DefaultDelims{
Left: c.Left,
Right: c.Right,
}
}
// Merge merges the DefaultDelims
func (c *DefaultDelims) Merge(o *DefaultDelims) *DefaultDelims {
if c == nil {
if o == nil {
return nil
}
return o.Copy()
}
if o == nil {
return c.Copy()
}
r := c.Copy()
if o.Left != nil {
r.Left = o.Left
}
if o.Right != nil {
r.Right = o.Right
}
return r
}

View File

@@ -40,8 +40,7 @@ type ExecConfig struct {
// EnvConfig is the environmental customizations. // EnvConfig is the environmental customizations.
Env *EnvConfig `mapstructure:"env"` Env *EnvConfig `mapstructure:"env"`
// KillSignal is the signal to send to the command to kill it gracefully. The // KillSignal is the signal to send to the command to kill it gracefully.
// default value is "SIGTERM".
KillSignal *os.Signal `mapstructure:"kill_signal"` KillSignal *os.Signal `mapstructure:"kill_signal"`
// KillTimeout is the amount of time to give the process to cleanup before // KillTimeout is the amount of time to give the process to cleanup before

View File

@@ -1,16 +1,26 @@
package config package config
import "fmt" import (
"fmt"
"github.com/hashicorp/consul-template/version"
)
const ( const (
// DefaultSyslogFacility is the default facility to log to. // DefaultSyslogFacility is the default facility to log to.
DefaultSyslogFacility = "LOCAL0" DefaultSyslogFacility = "LOCAL0"
) )
var (
// DefaultSyslogName is the default app name in syslog.
DefaultSyslogName = version.Name
)
// SyslogConfig is the configuration for syslog. // SyslogConfig is the configuration for syslog.
type SyslogConfig struct { type SyslogConfig struct {
Enabled *bool `mapstructure:"enabled"` Enabled *bool `mapstructure:"enabled"`
Facility *string `mapstructure:"facility"` Facility *string `mapstructure:"facility"`
Name *string `mapstructure:"name"`
} }
// DefaultSyslogConfig returns a configuration that is populated with the // DefaultSyslogConfig returns a configuration that is populated with the
@@ -28,6 +38,7 @@ func (c *SyslogConfig) Copy() *SyslogConfig {
var o SyslogConfig var o SyslogConfig
o.Enabled = c.Enabled o.Enabled = c.Enabled
o.Facility = c.Facility o.Facility = c.Facility
o.Name = c.Name
return &o return &o
} }
@@ -57,18 +68,26 @@ func (c *SyslogConfig) Merge(o *SyslogConfig) *SyslogConfig {
r.Facility = o.Facility r.Facility = o.Facility
} }
if o.Name != nil {
r.Name = o.Name
}
return r return r
} }
// Finalize ensures there no nil pointers. // Finalize ensures there no nil pointers.
func (c *SyslogConfig) Finalize() { func (c *SyslogConfig) Finalize() {
if c.Enabled == nil { if c.Enabled == nil {
c.Enabled = Bool(StringPresent(c.Facility)) c.Enabled = Bool(StringPresent(c.Facility) || StringPresent(c.Name))
} }
if c.Facility == nil { if c.Facility == nil {
c.Facility = String(DefaultSyslogFacility) c.Facility = String(DefaultSyslogFacility)
} }
if c.Name == nil {
c.Name = String(DefaultSyslogName)
}
} }
// GoString defines the printable version of this struct. // GoString defines the printable version of this struct.
@@ -80,8 +99,10 @@ func (c *SyslogConfig) GoString() string {
return fmt.Sprintf("&SyslogConfig{"+ return fmt.Sprintf("&SyslogConfig{"+
"Enabled:%s, "+ "Enabled:%s, "+
"Facility:%s"+ "Facility:%s"+
"Name:%s"+
"}", "}",
BoolGoString(c.Enabled), BoolGoString(c.Enabled),
StringGoString(c.Facility), StringGoString(c.Facility),
StringGoString(c.Name),
) )
} }

View File

@@ -12,11 +12,6 @@ const (
// vault to version 1.1.0 or newer. // vault to version 1.1.0 or newer.
EnvVaultSkipVerify = "VAULT_SKIP_VERIFY" EnvVaultSkipVerify = "VAULT_SKIP_VERIFY"
// DefaultVaultGrace is the default grace period before which to read a new
// secret from Vault. If a lease is due to expire in 15 seconds, Consul
// Template will read a new secret at that time minus this value.
DefaultVaultGrace = 15 * time.Second
// DefaultVaultRenewToken is the default value for if the Vault token should // DefaultVaultRenewToken is the default value for if the Vault token should
// be renewed. // be renewed.
DefaultVaultRenewToken = true DefaultVaultRenewToken = true
@@ -42,10 +37,6 @@ type VaultConfig struct {
// Enabled controls whether the Vault integration is active. // Enabled controls whether the Vault integration is active.
Enabled *bool `mapstructure:"enabled"` Enabled *bool `mapstructure:"enabled"`
// Grace is the amount of time before a lease is about to expire to force a
// new secret to be read.
Grace *time.Duration `mapstructure:"grace"`
// Namespace is the Vault namespace to use for reading/writing secrets. This can // Namespace is the Vault namespace to use for reading/writing secrets. This can
// also be set via the VAULT_NAMESPACE environment variable. // also be set via the VAULT_NAMESPACE environment variable.
Namespace *string `mapstructure:"namespace"` Namespace *string `mapstructure:"namespace"`
@@ -104,8 +95,6 @@ func (c *VaultConfig) Copy() *VaultConfig {
o.Enabled = c.Enabled o.Enabled = c.Enabled
o.Grace = c.Grace
o.Namespace = c.Namespace o.Namespace = c.Namespace
o.RenewToken = c.RenewToken o.RenewToken = c.RenewToken
@@ -157,10 +146,6 @@ func (c *VaultConfig) Merge(o *VaultConfig) *VaultConfig {
r.Enabled = o.Enabled r.Enabled = o.Enabled
} }
if o.Grace != nil {
r.Grace = o.Grace
}
if o.Namespace != nil { if o.Namespace != nil {
r.Namespace = o.Namespace r.Namespace = o.Namespace
} }
@@ -204,24 +189,10 @@ func (c *VaultConfig) Finalize() {
}, "") }, "")
} }
if c.Grace == nil {
c.Grace = TimeDuration(DefaultVaultGrace)
}
if c.Namespace == nil { if c.Namespace == nil {
c.Namespace = stringFromEnv([]string{"VAULT_NAMESPACE"}, "") c.Namespace = stringFromEnv([]string{"VAULT_NAMESPACE"}, "")
} }
if c.RenewToken == nil {
default_renew := DefaultVaultRenewToken
if c.VaultAgentTokenFile != nil {
default_renew = false
}
c.RenewToken = boolFromEnv([]string{
"VAULT_RENEW_TOKEN",
}, default_renew)
}
if c.Retry == nil { if c.Retry == nil {
c.Retry = DefaultRetryConfig() c.Retry = DefaultRetryConfig()
} }
@@ -277,6 +248,19 @@ func (c *VaultConfig) Finalize() {
c.Token = stringFromFile([]string{*c.VaultAgentTokenFile}, "") c.Token = stringFromFile([]string{*c.VaultAgentTokenFile}, "")
} }
// must be after c.Token setting, as default depends on that.
if c.RenewToken == nil {
default_renew := DefaultVaultRenewToken
if c.VaultAgentTokenFile != nil {
default_renew = false
} else if StringVal(c.Token) == "" {
default_renew = false
}
c.RenewToken = boolFromEnv([]string{
"VAULT_RENEW_TOKEN",
}, default_renew)
}
if c.Transport == nil { if c.Transport == nil {
c.Transport = DefaultTransportConfig() c.Transport = DefaultTransportConfig()
} }
@@ -302,7 +286,6 @@ func (c *VaultConfig) GoString() string {
return fmt.Sprintf("&VaultConfig{"+ return fmt.Sprintf("&VaultConfig{"+
"Address:%s, "+ "Address:%s, "+
"Enabled:%s, "+ "Enabled:%s, "+
"Grace:%s, "+
"Namespace:%s,"+ "Namespace:%s,"+
"RenewToken:%s, "+ "RenewToken:%s, "+
"Retry:%#v, "+ "Retry:%#v, "+
@@ -314,7 +297,6 @@ func (c *VaultConfig) GoString() string {
"}", "}",
StringGoString(c.Address), StringGoString(c.Address),
BoolGoString(c.Enabled), BoolGoString(c.Enabled),
TimeDurationGoString(c.Grace),
StringGoString(c.Namespace), StringGoString(c.Namespace),
BoolGoString(c.RenewToken), BoolGoString(c.RenewToken),
c.Retry, c.Retry,

View File

@@ -38,6 +38,7 @@ type vaultClient struct {
// CreateConsulClientInput is used as input to the CreateConsulClient function. // CreateConsulClientInput is used as input to the CreateConsulClient function.
type CreateConsulClientInput struct { type CreateConsulClientInput struct {
Address string Address string
Namespace string
Token string Token string
AuthEnabled bool AuthEnabled bool
AuthUsername string AuthUsername string
@@ -95,6 +96,10 @@ func (c *ClientSet) CreateConsulClient(i *CreateConsulClientInput) error {
consulConfig.Address = i.Address consulConfig.Address = i.Address
} }
if i.Namespace != "" {
consulConfig.Namespace = i.Namespace
}
if i.Token != "" { if i.Token != "" {
consulConfig.Token = i.Token consulConfig.Token = i.Token
} }

View File

@@ -0,0 +1,72 @@
package dependency
import (
"log"
"net/url"
"github.com/pkg/errors"
)
var (
// Ensure implements
_ Dependency = (*ConnectCAQuery)(nil)
)
type ConnectCAQuery struct {
stopCh chan struct{}
}
func NewConnectCAQuery() *ConnectCAQuery {
return &ConnectCAQuery{
stopCh: make(chan struct{}, 1),
}
}
func (d *ConnectCAQuery) Fetch(clients *ClientSet, opts *QueryOptions) (
interface{}, *ResponseMetadata, error,
) {
select {
case <-d.stopCh:
return nil, nil, ErrStopped
default:
}
opts = opts.Merge(nil)
log.Printf("[TRACE] %s: GET %s", d, &url.URL{
Path: "/v1/agent/connect/ca/roots",
RawQuery: opts.String(),
})
certs, md, err := clients.Consul().Agent().ConnectCARoots(
opts.ToConsulOpts())
if err != nil {
return nil, nil, errors.Wrap(err, d.String())
}
log.Printf("[TRACE] %s: returned %d results", d, len(certs.Roots))
log.Printf("[TRACE] %s: %#v ", d, md)
rm := &ResponseMetadata{
LastIndex: md.LastIndex,
LastContact: md.LastContact,
Block: true,
}
return certs.Roots, rm, nil
}
func (d *ConnectCAQuery) Stop() {
close(d.stopCh)
}
func (d *ConnectCAQuery) CanShare() bool {
return false
}
func (d *ConnectCAQuery) Type() Type {
return TypeConsul
}
func (d *ConnectCAQuery) String() string {
return "connect.caroots"
}

View File

@@ -0,0 +1,77 @@
package dependency
import (
"fmt"
"log"
"net/url"
"github.com/pkg/errors"
)
var (
// Ensure implements
_ Dependency = (*ConnectLeafQuery)(nil)
)
type ConnectLeafQuery struct {
stopCh chan struct{}
service string
}
func NewConnectLeafQuery(service string) *ConnectLeafQuery {
return &ConnectLeafQuery{
stopCh: make(chan struct{}, 1),
service: service,
}
}
func (d *ConnectLeafQuery) Fetch(clients *ClientSet, opts *QueryOptions) (
interface{}, *ResponseMetadata, error,
) {
select {
case <-d.stopCh:
return nil, nil, ErrStopped
default:
}
opts = opts.Merge(nil)
log.Printf("[TRACE] %s: GET %s", d, &url.URL{
Path: "/v1/agent/connect/ca/leaf/" + d.service,
RawQuery: opts.String(),
})
cert, md, err := clients.Consul().Agent().ConnectCALeaf(d.service,
opts.ToConsulOpts())
if err != nil {
return nil, nil, errors.Wrap(err, d.String())
}
log.Printf("[TRACE] %s: returned response", d)
rm := &ResponseMetadata{
LastIndex: md.LastIndex,
LastContact: md.LastContact,
Block: true,
}
return cert, rm, nil
}
func (d *ConnectLeafQuery) Stop() {
close(d.stopCh)
}
func (d *ConnectLeafQuery) CanShare() bool {
return false
}
func (d *ConnectLeafQuery) Type() Type {
return TypeConsul
}
func (d *ConnectLeafQuery) String() string {
if d.service != "" {
return fmt.Sprintf("connect.caleaf(%s)", d.service)
}
return "connect.caleaf"
}

View File

@@ -51,6 +51,7 @@ type HealthService struct {
Checks api.HealthChecks Checks api.HealthChecks
Status string Status string
Port int Port int
Weights api.AgentWeights
} }
// HealthServiceQuery is the representation of all a service query in Consul. // HealthServiceQuery is the representation of all a service query in Consul.
@@ -62,10 +63,20 @@ type HealthServiceQuery struct {
name string name string
near string near string
tag string tag string
connect bool
} }
// NewHealthServiceQuery processes the strings to build a service dependency. // NewHealthServiceQuery processes the strings to build a service dependency.
func NewHealthServiceQuery(s string) (*HealthServiceQuery, error) { func NewHealthServiceQuery(s string) (*HealthServiceQuery, error) {
return healthServiceQuery(s, false)
}
// NewHealthConnect Query processes the strings to build a connect dependency.
func NewHealthConnectQuery(s string) (*HealthServiceQuery, error) {
return healthServiceQuery(s, true)
}
func healthServiceQuery(s string, connect bool) (*HealthServiceQuery, error) {
if !HealthServiceQueryRe.MatchString(s) { if !HealthServiceQueryRe.MatchString(s) {
return nil, fmt.Errorf("health.service: invalid format: %q", s) return nil, fmt.Errorf("health.service: invalid format: %q", s)
} }
@@ -86,7 +97,8 @@ func NewHealthServiceQuery(s string) (*HealthServiceQuery, error) {
filters = append(filters, f) filters = append(filters, f)
case "": case "":
default: default:
return nil, fmt.Errorf("health.service: invalid filter: %q in %q", f, s) return nil, fmt.Errorf(
"health.service: invalid filter: %q in %q", f, s)
} }
} }
sort.Strings(filters) sort.Strings(filters)
@@ -101,6 +113,7 @@ func NewHealthServiceQuery(s string) (*HealthServiceQuery, error) {
name: m["name"], name: m["name"],
near: m["near"], near: m["near"],
tag: m["tag"], tag: m["tag"],
connect: connect,
}, nil }, nil
} }
@@ -130,10 +143,15 @@ func (d *HealthServiceQuery) Fetch(clients *ClientSet, opts *QueryOptions) (inte
log.Printf("[TRACE] %s: GET %s", d, u) log.Printf("[TRACE] %s: GET %s", d, u)
// Check if a user-supplied filter was given. If so, we may be querying for // Check if a user-supplied filter was given. If so, we may be querying for
// more than healthy services, so we need to implement client-side filtering. // more than healthy services, so we need to implement client-side
// filtering.
passingOnly := len(d.filters) == 1 && d.filters[0] == HealthPassing passingOnly := len(d.filters) == 1 && d.filters[0] == HealthPassing
entries, qm, err := clients.Consul().Health().Service(d.name, d.tag, passingOnly, opts.ToConsulOpts()) nodes := clients.Consul().Health().Service
if d.connect {
nodes = clients.Consul().Health().Connect
}
entries, qm, err := nodes(d.name, d.tag, passingOnly, opts.ToConsulOpts())
if err != nil { if err != nil {
return nil, nil, errors.Wrap(err, d.String()) return nil, nil, errors.Wrap(err, d.String())
} }
@@ -145,13 +163,14 @@ func (d *HealthServiceQuery) Fetch(clients *ClientSet, opts *QueryOptions) (inte
// Get the status of this service from its checks. // Get the status of this service from its checks.
status := entry.Checks.AggregatedStatus() status := entry.Checks.AggregatedStatus()
// If we are not checking only healthy services, filter out services that do // If we are not checking only healthy services, filter out services
// not match the given filter. // that do not match the given filter.
if !acceptStatus(d.filters, status) { if !acceptStatus(d.filters, status) {
continue continue
} }
// Get the address of the service, falling back to the address of the node. // Get the address of the service, falling back to the address of the
// node.
address := entry.Service.Address address := entry.Service.Address
if address == "" { if address == "" {
address = entry.Node.Address address = entry.Node.Address
@@ -167,10 +186,12 @@ func (d *HealthServiceQuery) Fetch(clients *ClientSet, opts *QueryOptions) (inte
Address: address, Address: address,
ID: entry.Service.ID, ID: entry.Service.ID,
Name: entry.Service.Service, Name: entry.Service.Service,
Tags: ServiceTags(deepCopyAndSortTags(entry.Service.Tags)), Tags: ServiceTags(
Status: status, deepCopyAndSortTags(entry.Service.Tags)),
Checks: entry.Checks, Status: status,
Port: entry.Service.Port, Checks: entry.Checks,
Port: entry.Service.Port,
Weights: entry.Service.Weights,
}) })
} }

View File

@@ -8,6 +8,7 @@ import (
"time" "time"
"crypto/x509" "crypto/x509"
"encoding/json"
"encoding/pem" "encoding/pem"
"github.com/hashicorp/vault/api" "github.com/hashicorp/vault/api"
@@ -141,6 +142,20 @@ func leaseCheckWait(s *Secret) time.Duration {
} }
} }
// Handle if this is a secret with a rotation period. If this is a rotating secret,
// the rotating secret's TTL will be the duration to sleep before rendering the new secret.
var rotatingSecret bool
if _, ok := s.Data["rotation_period"]; ok && s.LeaseID == "" {
if ttlInterface, ok := s.Data["ttl"]; ok {
if ttlData, err := ttlInterface.(json.Number).Int64(); err == nil {
log.Printf("[DEBUG] Found rotation_period and set lease duration to %d seconds", ttlData)
// Add a second for cushion
base = int(ttlData) + 1
rotatingSecret = true
}
}
}
// Ensure we have a lease duration, since sometimes this can be zero. // Ensure we have a lease duration, since sometimes this can be zero.
if base <= 0 { if base <= 0 {
base = int(VaultDefaultLeaseDuration.Seconds()) base = int(VaultDefaultLeaseDuration.Seconds())
@@ -156,7 +171,9 @@ func leaseCheckWait(s *Secret) time.Duration {
// Use some randomness so many clients do not hit Vault simultaneously. // Use some randomness so many clients do not hit Vault simultaneously.
sleep = sleep * (rand.Float64() + 1) / 2.0 sleep = sleep * (rand.Float64() + 1) / 2.0
} else { } else if !rotatingSecret {
// If the secret doesn't have a rotation period, this is a non-renewable leased
// secret.
// For non-renewable leases set the renew duration to use much of the secret // For non-renewable leases set the renew duration to use much of the secret
// lease as possible. Use a stagger over 85%-95% of the lease duration so that // lease as possible. Use a stagger over 85%-95% of the lease duration so that
// many clients do not hit Vault simultaneously. // many clients do not hit Vault simultaneously.
@@ -339,8 +356,12 @@ func addPrefixToVKVPath(p, mountPath, apiPrefix string) string {
return path.Join(mountPath, apiPrefix) return path.Join(mountPath, apiPrefix)
default: default:
p = strings.TrimPrefix(p, mountPath) p = strings.TrimPrefix(p, mountPath)
// Don't add /data to the path if it's been added manually. // Don't add /data/ to the path if it's been added manually.
if strings.HasPrefix(p, apiPrefix) { apiPathPrefix := apiPrefix
if !strings.HasSuffix(apiPrefix, "/") {
apiPathPrefix += "/"
}
if strings.HasPrefix(p, apiPathPrefix) {
return path.Join(mountPath, p) return path.Join(mountPath, p)
} }
return path.Join(mountPath, apiPrefix, p) return path.Join(mountPath, apiPrefix, p)

View File

@@ -122,6 +122,9 @@ func (d *VaultReadQuery) Stop() {
// String returns the human-friendly version of this dependency. // String returns the human-friendly version of this dependency.
func (d *VaultReadQuery) String() string { func (d *VaultReadQuery) String() string {
if v := d.queryValues["version"]; len(v) > 0 {
return fmt.Sprintf("vault.read(%s.v%s)", d.rawPath, v[0])
}
return fmt.Sprintf("vault.read(%s)", d.rawPath) return fmt.Sprintf("vault.read(%s)", d.rawPath)
} }

View File

@@ -1,10 +1,8 @@
package dependency package dependency
import ( import (
"log"
"time"
"github.com/hashicorp/vault/api" "github.com/hashicorp/vault/api"
"github.com/pkg/errors"
) )
var ( var (
@@ -36,7 +34,8 @@ func NewVaultTokenQuery(token string) (*VaultTokenQuery, error) {
} }
// Fetch queries the Vault API // Fetch queries the Vault API
func (d *VaultTokenQuery) Fetch(clients *ClientSet, opts *QueryOptions) (interface{}, *ResponseMetadata, error) { func (d *VaultTokenQuery) Fetch(clients *ClientSet, opts *QueryOptions,
) (interface{}, *ResponseMetadata, error) {
select { select {
case <-d.stopCh: case <-d.stopCh:
return nil, nil, ErrStopped return nil, nil, ErrStopped
@@ -44,25 +43,13 @@ func (d *VaultTokenQuery) Fetch(clients *ClientSet, opts *QueryOptions) (interfa
} }
if vaultSecretRenewable(d.secret) { if vaultSecretRenewable(d.secret) {
err := renewSecret(clients, d)
if err != nil {
return nil, nil, errors.Wrap(err, d.String())
}
renewSecret(clients, d) renewSecret(clients, d)
} }
// The secret isn't renewable, probably the generic secret backend.
// TODO This is incorrect when given a non-renewable template. We should
// instead to a lookup self to determine the lease duration.
opts = opts.Merge(&QueryOptions{})
dur := leaseCheckWait(d.secret)
if dur < opts.VaultGrace {
dur = opts.VaultGrace
}
log.Printf("[TRACE] %s: token is not renewable, sleeping for %s", d, dur)
select {
case <-time.After(dur):
case <-d.stopCh:
return nil, nil, ErrStopped
}
return nil, nil, ErrLeaseExpired return nil, nil, ErrLeaseExpired
} }

View File

@@ -16,15 +16,14 @@ var Levels = []logutils.LogLevel{"TRACE", "DEBUG", "INFO", "WARN", "ERR"}
// Config is the configuration for this log setup. // Config is the configuration for this log setup.
type Config struct { type Config struct {
// Name is the progname as it will appear in syslog output (if enabled).
Name string `json:"name"`
// Level is the log level to use. // Level is the log level to use.
Level string `json:"level"` Level string `json:"level"`
// Syslog and SyslogFacility are the syslog configuration options. // Syslog and SyslogFacility are the syslog configuration options.
Syslog bool `json:"syslog"` Syslog bool `json:"syslog"`
SyslogFacility string `json:"syslog_facility"` SyslogFacility string `json:"syslog_facility"`
// SyslogName is the progname as it will appear in syslog output (if enabled).
SyslogName string `json:"name"`
// Writer is the output where logs should go. If syslog is enabled, data will // Writer is the output where logs should go. If syslog is enabled, data will
// be written to writer in addition to syslog. // be written to writer in addition to syslog.
@@ -51,7 +50,7 @@ func Setup(config *Config) error {
if config.Syslog { if config.Syslog {
log.Printf("[DEBUG] (logging) enabling syslog on %s", config.SyslogFacility) log.Printf("[DEBUG] (logging) enabling syslog on %s", config.SyslogFacility)
l, err := gsyslog.NewLogger(gsyslog.LOG_NOTICE, config.SyslogFacility, config.Name) l, err := gsyslog.NewLogger(gsyslog.LOG_NOTICE, config.SyslogFacility, config.SyslogName)
if err != nil { if err != nil {
return fmt.Errorf("error setting up syslog logger: %s", err) return fmt.Errorf("error setting up syslog logger: %s", err)
} }

View File

@@ -556,23 +556,6 @@ func (r *Runner) Run() error {
} }
} }
// Check if we need to deliver any rendered signals
if wouldRenderAny || renderedAny {
// Send the signal that a template got rendered
select {
case r.renderedCh <- struct{}{}:
default:
}
}
// Check if we need to deliver any event signals
if newRenderEvent {
select {
case r.renderEventCh <- struct{}{}:
default:
}
}
// Perform the diff and update the known dependencies. // Perform the diff and update the known dependencies.
r.diffAndUpdateDeps(runCtx.depsMap) r.diffAndUpdateDeps(runCtx.depsMap)
@@ -601,6 +584,23 @@ func (r *Runner) Run() error {
} }
} }
// Check if we need to deliver any rendered signals
if wouldRenderAny || renderedAny {
// Send the signal that a template got rendered
select {
case r.renderedCh <- struct{}{}:
default:
}
}
// Check if we need to deliver any event signals
if newRenderEvent {
select {
case r.renderEventCh <- struct{}{}:
default:
}
}
// If we got this far and have a child process, we need to send the reload // If we got this far and have a child process, we need to send the reload
// signal to the child process. // signal to the child process.
if renderedAny && r.child != nil { if renderedAny && r.child != nil {
@@ -691,12 +691,20 @@ func (r *Runner) runTemplate(tmpl *template.Template, runCtx *templateRunCtx) (*
// Grab the list of used and missing dependencies. // Grab the list of used and missing dependencies.
missing, used := result.Missing, result.Used missing, used := result.Missing, result.Used
if l := missing.Len(); l > 0 {
log.Printf("[DEBUG] (runner) missing data for %d dependencies", l)
for _, missingDependency := range missing.List() {
log.Printf("[DEBUG] (runner) missing dependency: %s", missingDependency)
}
}
// Add the dependency to the list of dependencies for this runner. // Add the dependency to the list of dependencies for this runner.
for _, d := range used.List() { for _, d := range used.List() {
// If we've taken over leadership for a template, we may have data // If we've taken over leadership for a template, we may have data
// that is cached, but not have the watcher. We must treat this as // that is cached, but not have the watcher. We must treat this as
// missing so that we create the watcher and re-run the template. // missing so that we create the watcher and re-run the template.
if isLeader && !r.watcher.Watching(d) { if isLeader && !r.watcher.Watching(d) {
log.Printf("[DEBUG] (runner) add used dependency %s to missing since isLeader but do not have a watcher", d)
missing.Add(d) missing.Add(d)
} }
if _, ok := runCtx.depsMap[d.String()]; !ok { if _, ok := runCtx.depsMap[d.String()]; !ok {
@@ -865,12 +873,21 @@ func (r *Runner) init() error {
// config templates is kept so templates can lookup their commands and output // config templates is kept so templates can lookup their commands and output
// destinations. // destinations.
for _, ctmpl := range *r.config.Templates { for _, ctmpl := range *r.config.Templates {
leftDelim := config.StringVal(ctmpl.LeftDelim)
if leftDelim == "" {
leftDelim = config.StringVal(r.config.DefaultDelims.Left)
}
rightDelim := config.StringVal(ctmpl.RightDelim)
if rightDelim == "" {
rightDelim = config.StringVal(r.config.DefaultDelims.Right)
}
tmpl, err := template.NewTemplate(&template.NewTemplateInput{ tmpl, err := template.NewTemplate(&template.NewTemplateInput{
Source: config.StringVal(ctmpl.Source), Source: config.StringVal(ctmpl.Source),
Contents: config.StringVal(ctmpl.Contents), Contents: config.StringVal(ctmpl.Contents),
ErrMissingKey: config.BoolVal(ctmpl.ErrMissingKey), ErrMissingKey: config.BoolVal(ctmpl.ErrMissingKey),
LeftDelim: config.StringVal(ctmpl.LeftDelim), LeftDelim: leftDelim,
RightDelim: config.StringVal(ctmpl.RightDelim), RightDelim: rightDelim,
FunctionBlacklist: ctmpl.FunctionBlacklist, FunctionBlacklist: ctmpl.FunctionBlacklist,
SandboxPath: config.StringVal(ctmpl.SandboxPath), SandboxPath: config.StringVal(ctmpl.SandboxPath),
}) })
@@ -1232,6 +1249,7 @@ func newClientSet(c *config.Config) (*dep.ClientSet, error) {
if err := clients.CreateConsulClient(&dep.CreateConsulClientInput{ if err := clients.CreateConsulClient(&dep.CreateConsulClientInput{
Address: config.StringVal(c.Consul.Address), Address: config.StringVal(c.Consul.Address),
Namespace: config.StringVal(c.Consul.Namespace),
Token: config.StringVal(c.Consul.Token), Token: config.StringVal(c.Consul.Token),
AuthEnabled: config.BoolVal(c.Consul.Auth.Enabled), AuthEnabled: config.BoolVal(c.Consul.Auth.Enabled),
AuthUsername: config.StringVal(c.Consul.Auth.Username), AuthUsername: config.StringVal(c.Consul.Auth.Username),
@@ -1288,6 +1306,7 @@ func newWatcher(c *config.Config, clients *dep.ClientSet, once bool) (*watch.Wat
Clients: clients, Clients: clients,
MaxStale: config.TimeDurationVal(c.MaxStale), MaxStale: config.TimeDurationVal(c.MaxStale),
Once: c.Once, Once: c.Once,
BlockQueryWaitTime: config.TimeDurationVal(c.BlockQueryWaitTime),
RenewVault: clients.Vault().Token() != "" && config.BoolVal(c.Vault.RenewToken), RenewVault: clients.Vault().Token() != "" && config.BoolVal(c.Vault.RenewToken),
VaultAgentTokenFile: config.StringVal(c.Vault.VaultAgentTokenFile), VaultAgentTokenFile: config.StringVal(c.Vault.VaultAgentTokenFile),
RetryFuncConsul: watch.RetryFunc(c.Consul.Retry.RetryFunc()), RetryFuncConsul: watch.RetryFunc(c.Consul.Retry.RetryFunc()),
@@ -1295,7 +1314,6 @@ func newWatcher(c *config.Config, clients *dep.ClientSet, once bool) (*watch.Wat
// dependencies like reading a file from disk. // dependencies like reading a file from disk.
RetryFuncDefault: nil, RetryFuncDefault: nil,
RetryFuncVault: watch.RetryFunc(c.Vault.Retry.RetryFunc()), RetryFuncVault: watch.RetryFunc(c.Vault.Retry.RetryFunc()),
VaultGrace: config.TimeDurationVal(c.Vault.Grace),
VaultToken: clients.Vault().Token(), VaultToken: clients.Vault().Token(),
}) })
if err != nil { if err != nil {

View File

@@ -2,7 +2,9 @@ package template
import ( import (
"bytes" "bytes"
"crypto/sha256"
"encoding/base64" "encoding/base64"
"encoding/hex"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
@@ -19,6 +21,7 @@ import (
"github.com/BurntSushi/toml" "github.com/BurntSushi/toml"
dep "github.com/hashicorp/consul-template/dependency" dep "github.com/hashicorp/consul-template/dependency"
"github.com/hashicorp/consul/api"
socktmpl "github.com/hashicorp/go-sockaddr/template" socktmpl "github.com/hashicorp/go-sockaddr/template"
"github.com/pkg/errors" "github.com/pkg/errors"
yaml "gopkg.in/yaml.v2" yaml "gopkg.in/yaml.v2"
@@ -387,10 +390,8 @@ func byMeta(meta string, services []*dep.HealthService) (groups map[string][]*de
} }
getOrDefault := func(m map[string]string, key string) string { getOrDefault := func(m map[string]string, key string) string {
realKey := strings.TrimSuffix(key, "|int") realKey := strings.TrimSuffix(key, "|int")
if val, ok := m[realKey]; ok { if val := m[realKey]; val != "" {
if val != "" { return val
return val
}
} }
if strings.HasSuffix(key, "|int") { if strings.HasSuffix(key, "|int") {
return "0" return "0"
@@ -472,6 +473,62 @@ func servicesFunc(b *Brain, used, missing *dep.Set) func(...string) ([]*dep.Cata
} }
} }
// connectFunc returns or accumulates health connect dependencies.
func connectFunc(b *Brain, used, missing *dep.Set) func(...string) ([]*dep.HealthService, error) {
return func(s ...string) ([]*dep.HealthService, error) {
result := []*dep.HealthService{}
if len(s) == 0 || s[0] == "" {
return result, nil
}
d, err := dep.NewHealthConnectQuery(strings.Join(s, "|"))
if err != nil {
return nil, err
}
used.Add(d)
if value, ok := b.Recall(d); ok {
return value.([]*dep.HealthService), nil
}
missing.Add(d)
return result, nil
}
}
func connectCARootsFunc(b *Brain, used, missing *dep.Set,
) func(...string) ([]*api.CARoot, error) {
return func(...string) ([]*api.CARoot, error) {
d := dep.NewConnectCAQuery()
used.Add(d)
if value, ok := b.Recall(d); ok {
return value.([]*api.CARoot), nil
}
missing.Add(d)
return nil, nil
}
}
func connectLeafFunc(b *Brain, used, missing *dep.Set,
) func(...string) (*api.LeafCert, error) {
return func(s ...string) (*api.LeafCert, error) {
if len(s) == 0 || s[0] == "" {
return nil, nil
}
d := dep.NewConnectLeafQuery(s[0])
used.Add(d)
if value, ok := b.Recall(d); ok {
return value.(*api.LeafCert), nil
}
missing.Add(d)
return nil, nil
}
}
func safeTreeFunc(b *Brain, used, missing *dep.Set) func(string) ([]*dep.KeyPair, error) { func safeTreeFunc(b *Brain, used, missing *dep.Set) func(string) ([]*dep.KeyPair, error) {
// call treeFunc but explicitly mark that empty data set returned on monitored KV prefix is NOT safe // call treeFunc but explicitly mark that empty data set returned on monitored KV prefix is NOT safe
return treeFunc(b, used, missing, false) return treeFunc(b, used, missing, false)
@@ -805,9 +862,11 @@ func loop(ifaces ...interface{}) (<-chan int64, error) {
to64 := func(i interface{}) (int64, error) { to64 := func(i interface{}) (int64, error) {
v := reflect.ValueOf(i) v := reflect.ValueOf(i)
switch v.Kind() { switch v.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32: case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
reflect.Int64:
return int64(v.Int()), nil return int64(v.Int()), nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32: case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32,
reflect.Uint64:
return int64(v.Uint()), nil return int64(v.Uint()), nil
case reflect.String: case reflect.String:
return parseInt(v.String()) return parseInt(v.String())
@@ -922,6 +981,19 @@ func parseUint(s string) (uint64, error) {
return result, nil return result, nil
} }
// parseYAML returns a structure for valid YAML
func parseYAML(s string) (interface{}, error) {
if s == "" {
return map[string]interface{}{}, nil
}
var data interface{}
if err := yaml.Unmarshal([]byte(s), &data); err != nil {
return nil, err
}
return data, nil
}
// plugin executes a subprocess as the given command string. It is assumed the // plugin executes a subprocess as the given command string. It is assumed the
// resulting command returns JSON which is then parsed and returned as the // resulting command returns JSON which is then parsed and returned as the
// value for use in the template. // value for use in the template.
@@ -1287,6 +1359,148 @@ func modulo(b, a interface{}) (interface{}, error) {
} }
} }
// minimum returns the minimum between a and b.
func minimum(b, a interface{}) (interface{}, error) {
av := reflect.ValueOf(a)
bv := reflect.ValueOf(b)
switch av.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
switch bv.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if av.Int() < bv.Int() {
return av.Int(), nil
}
return bv.Int(), nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
if av.Int() < int64(bv.Uint()) {
return av.Int(), nil
}
return bv.Uint(), nil
case reflect.Float32, reflect.Float64:
if float64(av.Int()) < bv.Float() {
return av.Int(), nil
}
return bv.Float(), nil
default:
return nil, fmt.Errorf("minimum: unknown type for %q (%T)", bv, b)
}
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
switch bv.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if int64(av.Uint()) < bv.Int() {
return av.Uint(), nil
}
return bv.Int(), nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
if av.Uint() < bv.Uint() {
return av.Uint(), nil
}
return bv.Uint(), nil
case reflect.Float32, reflect.Float64:
if float64(av.Uint()) < bv.Float() {
return av.Uint(), nil
}
return bv.Float(), nil
default:
return nil, fmt.Errorf("minimum: unknown type for %q (%T)", bv, b)
}
case reflect.Float32, reflect.Float64:
switch bv.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if av.Float() < float64(bv.Int()) {
return av.Float(), nil
}
return bv.Int(), nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
if av.Float() < float64(bv.Uint()) {
return av.Float(), nil
}
return bv.Uint(), nil
case reflect.Float32, reflect.Float64:
if av.Float() < bv.Float() {
return av.Float(), nil
}
return bv.Float(), nil
default:
return nil, fmt.Errorf("minimum: unknown type for %q (%T)", bv, b)
}
default:
return nil, fmt.Errorf("minimum: unknown type for %q (%T)", av, a)
}
}
// maximum returns the maximum between a and b.
func maximum(b, a interface{}) (interface{}, error) {
av := reflect.ValueOf(a)
bv := reflect.ValueOf(b)
switch av.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
switch bv.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if av.Int() > bv.Int() {
return av.Int(), nil
}
return bv.Int(), nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
if av.Int() > int64(bv.Uint()) {
return av.Int(), nil
}
return bv.Uint(), nil
case reflect.Float32, reflect.Float64:
if float64(av.Int()) > bv.Float() {
return av.Int(), nil
}
return bv.Float(), nil
default:
return nil, fmt.Errorf("maximum: unknown type for %q (%T)", bv, b)
}
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
switch bv.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if int64(av.Uint()) > bv.Int() {
return av.Uint(), nil
}
return bv.Int(), nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
if av.Uint() > bv.Uint() {
return av.Uint(), nil
}
return bv.Uint(), nil
case reflect.Float32, reflect.Float64:
if float64(av.Uint()) > bv.Float() {
return av.Uint(), nil
}
return bv.Float(), nil
default:
return nil, fmt.Errorf("maximum: unknown type for %q (%T)", bv, b)
}
case reflect.Float32, reflect.Float64:
switch bv.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if av.Float() > float64(bv.Int()) {
return av.Float(), nil
}
return bv.Int(), nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
if av.Float() > float64(bv.Uint()) {
return av.Float(), nil
}
return bv.Uint(), nil
case reflect.Float32, reflect.Float64:
if av.Float() > bv.Float() {
return av.Float(), nil
}
return bv.Float(), nil
default:
return nil, fmt.Errorf("maximum: unknown type for %q (%T)", bv, b)
}
default:
return nil, fmt.Errorf("maximum: unknown type for %q (%T)", av, a)
}
}
// blacklisted always returns an error, to be used in place of blacklisted template functions // blacklisted always returns an error, to be used in place of blacklisted template functions
func blacklisted(...string) (string, error) { func blacklisted(...string) (string, error) {
return "", errors.New("function is disabled") return "", errors.New("function is disabled")
@@ -1313,10 +1527,18 @@ func pathInSandbox(sandbox, path string) error {
// sockaddr wraps go-sockaddr templating // sockaddr wraps go-sockaddr templating
func sockaddr(args ...string) (string, error) { func sockaddr(args ...string) (string, error) {
t := fmt.Sprintf("{{ %s }} ", strings.Join(args, " ")) t := fmt.Sprintf("{{ %s }}", strings.Join(args, " "))
k, err := socktmpl.Parse(t) k, err := socktmpl.Parse(t)
if err != nil { if err != nil {
return "", err return "", err
} }
return k, nil return k, nil
} }
// sha256Hex return the sha256 hex of a string
func sha256Hex(item string) (string, error) {
h := sha256.New()
h.Write([]byte(item))
output := hex.EncodeToString(h.Sum(nil))
return output, nil
}

View File

@@ -15,12 +15,12 @@ import (
var ( var (
// ErrTemplateContentsAndSource is the error returned when a template // ErrTemplateContentsAndSource is the error returned when a template
// specifies both a "source" and "content" argument, which is not valid. // specifies both a "source" and "content" argument, which is not valid.
ErrTemplateContentsAndSource = errors.New("template: cannot specify both 'source' and 'content'") ErrTemplateContentsAndSource = errors.New("template: cannot specify both 'source' and 'contents'")
// ErrTemplateMissingContentsAndSource is the error returned when a template // ErrTemplateMissingContentsAndSource is the error returned when a template
// does not specify either a "source" or "content" argument, which is not // does not specify either a "source" or "content" argument, which is not
// valid. // valid.
ErrTemplateMissingContentsAndSource = errors.New("template: must specify exactly one of 'source' or 'content'") ErrTemplateMissingContentsAndSource = errors.New("template: must specify exactly one of 'source' or 'contents'")
) )
// Template is the internal representation of an individual template to process. // Template is the internal representation of an individual template to process.
@@ -237,9 +237,12 @@ func funcMap(i *funcMapInput) template.FuncMap {
"secret": secretFunc(i.brain, i.used, i.missing), "secret": secretFunc(i.brain, i.used, i.missing),
"secrets": secretsFunc(i.brain, i.used, i.missing), "secrets": secretsFunc(i.brain, i.used, i.missing),
"service": serviceFunc(i.brain, i.used, i.missing), "service": serviceFunc(i.brain, i.used, i.missing),
"connect": connectFunc(i.brain, i.used, i.missing),
"services": servicesFunc(i.brain, i.used, i.missing), "services": servicesFunc(i.brain, i.used, i.missing),
"tree": treeFunc(i.brain, i.used, i.missing, true), "tree": treeFunc(i.brain, i.used, i.missing, true),
"safeTree": safeTreeFunc(i.brain, i.used, i.missing), "safeTree": safeTreeFunc(i.brain, i.used, i.missing),
"caRoots": connectCARootsFunc(i.brain, i.used, i.missing),
"caLeaf": connectLeafFunc(i.brain, i.used, i.missing),
// Scratch // Scratch
"scratch": func() *Scratch { return &scratch }, "scratch": func() *Scratch { return &scratch },
@@ -270,10 +273,12 @@ func funcMap(i *funcMapInput) template.FuncMap {
"parseInt": parseInt, "parseInt": parseInt,
"parseJSON": parseJSON, "parseJSON": parseJSON,
"parseUint": parseUint, "parseUint": parseUint,
"parseYAML": parseYAML,
"plugin": plugin, "plugin": plugin,
"regexReplaceAll": regexReplaceAll, "regexReplaceAll": regexReplaceAll,
"regexMatch": regexMatch, "regexMatch": regexMatch,
"replaceAll": replaceAll, "replaceAll": replaceAll,
"sha256Hex": sha256Hex,
"timestamp": timestamp, "timestamp": timestamp,
"toLower": toLower, "toLower": toLower,
"toJSON": toJSON, "toJSON": toJSON,
@@ -291,6 +296,8 @@ func funcMap(i *funcMapInput) template.FuncMap {
"multiply": multiply, "multiply": multiply,
"divide": divide, "divide": divide,
"modulo": modulo, "modulo": modulo,
"minimum": minimum,
"maximum": maximum,
} }
for _, bf := range i.functionBlacklist { for _, bf := range i.functionBlacklist {

View File

@@ -2,7 +2,7 @@ package version
import "fmt" import "fmt"
const Version = "0.22.0" const Version = "0.25.0"
var ( var (
Name string Name string

View File

@@ -11,11 +11,6 @@ import (
dep "github.com/hashicorp/consul-template/dependency" dep "github.com/hashicorp/consul-template/dependency"
) )
const (
// The amount of time to do a blocking query for
defaultWaitTime = 60 * time.Second
)
// View is a representation of a Dependency and the most recent data it has // View is a representation of a Dependency and the most recent data it has
// received from Consul. // received from Consul.
type View struct { type View struct {
@@ -33,6 +28,9 @@ type View struct {
receivedData bool receivedData bool
lastIndex uint64 lastIndex uint64
// blockQueryWaitTime is amount of time in seconds to do a blocking query for
blockQueryWaitTime time.Duration
// maxStale is the maximum amount of time to allow a query to be stale. // maxStale is the maximum amount of time to allow a query to be stale.
maxStale time.Duration maxStale time.Duration
@@ -45,11 +43,6 @@ type View struct {
// stopCh is used to stop polling on this View // stopCh is used to stop polling on this View
stopCh chan struct{} stopCh chan struct{}
// vaultGrace is the grace period between a lease and the max TTL for which
// Consul Template will generate a new secret instead of renewing an existing
// one.
vaultGrace time.Duration
} }
// NewViewInput is used as input to the NewView function. // NewViewInput is used as input to the NewView function.
@@ -61,6 +54,9 @@ type NewViewInput struct {
// directly to the dependency. // directly to the dependency.
Clients *dep.ClientSet Clients *dep.ClientSet
// BlockQueryWaitTime is amount of time in seconds to do a blocking query for
BlockQueryWaitTime time.Duration
// MaxStale is the maximum amount a time a query response is allowed to be // MaxStale is the maximum amount a time a query response is allowed to be
// stale before forcing a read from the leader. // stale before forcing a read from the leader.
MaxStale time.Duration MaxStale time.Duration
@@ -71,23 +67,18 @@ type NewViewInput struct {
// RetryFunc is a function which dictates how this view should retry on // RetryFunc is a function which dictates how this view should retry on
// upstream errors. // upstream errors.
RetryFunc RetryFunc RetryFunc RetryFunc
// VaultGrace is the grace period between a lease and the max TTL for which
// Consul Template will generate a new secret instead of renewing an existing
// one.
VaultGrace time.Duration
} }
// NewView constructs a new view with the given inputs. // NewView constructs a new view with the given inputs.
func NewView(i *NewViewInput) (*View, error) { func NewView(i *NewViewInput) (*View, error) {
return &View{ return &View{
dependency: i.Dependency, dependency: i.Dependency,
clients: i.Clients, clients: i.Clients,
maxStale: i.MaxStale, blockQueryWaitTime: i.BlockQueryWaitTime,
once: i.Once, maxStale: i.MaxStale,
retryFunc: i.RetryFunc, once: i.Once,
stopCh: make(chan struct{}, 1), retryFunc: i.RetryFunc,
vaultGrace: i.VaultGrace, stopCh: make(chan struct{}, 1),
}, nil }, nil
} }
@@ -212,9 +203,8 @@ func (v *View) fetch(doneCh, successCh chan<- struct{}, errCh chan<- error) {
data, rm, err := v.dependency.Fetch(v.clients, &dep.QueryOptions{ data, rm, err := v.dependency.Fetch(v.clients, &dep.QueryOptions{
AllowStale: allowStale, AllowStale: allowStale,
WaitTime: defaultWaitTime, WaitTime: v.blockQueryWaitTime,
WaitIndex: v.lastIndex, WaitIndex: v.lastIndex,
VaultGrace: v.vaultGrace,
}) })
if err != nil { if err != nil {
if err == dep.ErrStopped { if err == dep.ErrStopped {

View File

@@ -27,6 +27,9 @@ type Watcher struct {
// errCh is the chan where any errors will be published. // errCh is the chan where any errors will be published.
errCh chan error errCh chan error
// blockQueryWaitTime is amount of time in seconds to do a blocking query for
blockQueryWaitTime time.Duration
// depViewMap is a map of Templates to Views. Templates are keyed by // depViewMap is a map of Templates to Views. Templates are keyed by
// their string. // their string.
depViewMap map[string]*View depViewMap map[string]*View
@@ -42,11 +45,6 @@ type Watcher struct {
retryFuncConsul RetryFunc retryFuncConsul RetryFunc
retryFuncDefault RetryFunc retryFuncDefault RetryFunc
retryFuncVault RetryFunc retryFuncVault RetryFunc
// vaultGrace is the grace period between a lease and the max TTL for which
// Consul Template will generate a new secret instead of renewing an existing
// one.
vaultGrace time.Duration
} }
type NewWatcherInput struct { type NewWatcherInput struct {
@@ -59,6 +57,9 @@ type NewWatcherInput struct {
// Once specifies this watcher should tell views to poll exactly once. // Once specifies this watcher should tell views to poll exactly once.
Once bool Once bool
// WaitTime is amount of time in seconds to do a blocking query for
BlockQueryWaitTime time.Duration
// RenewVault indicates if this watcher should renew Vault tokens. // RenewVault indicates if this watcher should renew Vault tokens.
RenewVault bool RenewVault bool
@@ -72,26 +73,21 @@ type NewWatcherInput struct {
RetryFuncConsul RetryFunc RetryFuncConsul RetryFunc
RetryFuncDefault RetryFunc RetryFuncDefault RetryFunc
RetryFuncVault RetryFunc RetryFuncVault RetryFunc
// VaultGrace is the grace period between a lease and the max TTL for which
// Consul Template will generate a new secret instead of renewing an existing
// one.
VaultGrace time.Duration
} }
// NewWatcher creates a new watcher using the given API client. // NewWatcher creates a new watcher using the given API client.
func NewWatcher(i *NewWatcherInput) (*Watcher, error) { func NewWatcher(i *NewWatcherInput) (*Watcher, error) {
w := &Watcher{ w := &Watcher{
clients: i.Clients, clients: i.Clients,
depViewMap: make(map[string]*View), depViewMap: make(map[string]*View),
dataCh: make(chan *View, dataBufferSize), dataCh: make(chan *View, dataBufferSize),
errCh: make(chan error), errCh: make(chan error),
maxStale: i.MaxStale, maxStale: i.MaxStale,
once: i.Once, once: i.Once,
retryFuncConsul: i.RetryFuncConsul, blockQueryWaitTime: i.BlockQueryWaitTime,
retryFuncDefault: i.RetryFuncDefault, retryFuncConsul: i.RetryFuncConsul,
retryFuncVault: i.RetryFuncVault, retryFuncDefault: i.RetryFuncDefault,
vaultGrace: i.VaultGrace, retryFuncVault: i.RetryFuncVault,
} }
// Start a watcher for the Vault renew if that config was specified // Start a watcher for the Vault renew if that config was specified
@@ -160,12 +156,12 @@ func (w *Watcher) Add(d dep.Dependency) (bool, error) {
} }
v, err := NewView(&NewViewInput{ v, err := NewView(&NewViewInput{
Dependency: d, Dependency: d,
Clients: w.clients, Clients: w.clients,
MaxStale: w.maxStale, MaxStale: w.maxStale,
Once: w.once, BlockQueryWaitTime: w.blockQueryWaitTime,
RetryFunc: retryFunc, Once: w.once,
VaultGrace: w.vaultGrace, RetryFunc: retryFunc,
}) })
if err != nil { if err != nil {
return false, errors.Wrap(err, "watcher") return false, errors.Wrap(err, "watcher")

View File

@@ -159,6 +159,14 @@ type AgentServiceRegistration struct {
Namespace string `json:",omitempty" bexpr:"-" hash:"ignore"` Namespace string `json:",omitempty" bexpr:"-" hash:"ignore"`
} }
//ServiceRegisterOpts is used to pass extra options to the service register.
type ServiceRegisterOpts struct {
//Missing healthchecks will be deleted from the agent.
//Using this parameter allows to idempotently register a service and its checks without
//having to manually deregister checks.
ReplaceExistingChecks bool
}
// AgentCheckRegistration is used to register a new check // AgentCheckRegistration is used to register a new check
type AgentCheckRegistration struct { type AgentCheckRegistration struct {
ID string `json:",omitempty"` ID string `json:",omitempty"`
@@ -182,6 +190,7 @@ type AgentServiceCheck struct {
HTTP string `json:",omitempty"` HTTP string `json:",omitempty"`
Header map[string][]string `json:",omitempty"` Header map[string][]string `json:",omitempty"`
Method string `json:",omitempty"` Method string `json:",omitempty"`
Body string `json:",omitempty"`
TCP string `json:",omitempty"` TCP string `json:",omitempty"`
Status string `json:",omitempty"` Status string `json:",omitempty"`
Notes string `json:",omitempty"` Notes string `json:",omitempty"`
@@ -554,8 +563,25 @@ func (a *Agent) MembersOpts(opts MembersOpts) ([]*AgentMember, error) {
// ServiceRegister is used to register a new service with // ServiceRegister is used to register a new service with
// the local agent // the local agent
func (a *Agent) ServiceRegister(service *AgentServiceRegistration) error { func (a *Agent) ServiceRegister(service *AgentServiceRegistration) error {
opts := ServiceRegisterOpts{
ReplaceExistingChecks: false,
}
return a.serviceRegister(service, opts)
}
// ServiceRegister is used to register a new service with
// the local agent and can be passed additional options.
func (a *Agent) ServiceRegisterOpts(service *AgentServiceRegistration, opts ServiceRegisterOpts) error {
return a.serviceRegister(service, opts)
}
func (a *Agent) serviceRegister(service *AgentServiceRegistration, opts ServiceRegisterOpts) error {
r := a.c.newRequest("PUT", "/v1/agent/service/register") r := a.c.newRequest("PUT", "/v1/agent/service/register")
r.obj = service r.obj = service
if opts.ReplaceExistingChecks {
r.params.Set("replace-existing-checks", "true")
}
_, resp, err := requireOK(a.c.doRequest(r)) _, resp, err := requireOK(a.c.doRequest(r))
if err != nil { if err != nil {
return err return err
@@ -870,20 +896,29 @@ func (a *Agent) DisableNodeMaintenance() error {
// log stream. An empty string will be sent down the given channel when there's // log stream. An empty string will be sent down the given channel when there's
// nothing left to stream, after which the caller should close the stopCh. // nothing left to stream, after which the caller should close the stopCh.
func (a *Agent) Monitor(loglevel string, stopCh <-chan struct{}, q *QueryOptions) (chan string, error) { func (a *Agent) Monitor(loglevel string, stopCh <-chan struct{}, q *QueryOptions) (chan string, error) {
return a.monitor(loglevel, false, stopCh, q)
}
// MonitorJSON is like Monitor except it returns logs in JSON format.
func (a *Agent) MonitorJSON(loglevel string, stopCh <-chan struct{}, q *QueryOptions) (chan string, error) {
return a.monitor(loglevel, true, stopCh, q)
}
func (a *Agent) monitor(loglevel string, logJSON bool, stopCh <-chan struct{}, q *QueryOptions) (chan string, error) {
r := a.c.newRequest("GET", "/v1/agent/monitor") r := a.c.newRequest("GET", "/v1/agent/monitor")
r.setQueryOptions(q) r.setQueryOptions(q)
if loglevel != "" { if loglevel != "" {
r.params.Add("loglevel", loglevel) r.params.Add("loglevel", loglevel)
} }
if logJSON {
r.params.Set("logjson", "true")
}
_, resp, err := requireOK(a.c.doRequest(r)) _, resp, err := requireOK(a.c.doRequest(r))
if err != nil { if err != nil {
return nil, err return nil, err
} }
logCh := make(chan string, 64) logCh := make(chan string, 64)
go func() { go func() {
defer resp.Body.Close() defer resp.Body.Close()
scanner := bufio.NewScanner(resp.Body) scanner := bufio.NewScanner(resp.Body)
for { for {
select { select {
@@ -907,7 +942,6 @@ func (a *Agent) Monitor(loglevel string, stopCh <-chan struct{}, q *QueryOptions
} }
} }
}() }()
return logCh, nil return logCh, nil
} }

View File

@@ -8,7 +8,6 @@ import (
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"log"
"net" "net"
"net/http" "net/http"
"net/url" "net/url"
@@ -18,6 +17,7 @@ import (
"time" "time"
"github.com/hashicorp/go-cleanhttp" "github.com/hashicorp/go-cleanhttp"
"github.com/hashicorp/go-hclog"
"github.com/hashicorp/go-rootcerts" "github.com/hashicorp/go-rootcerts"
) )
@@ -358,7 +358,14 @@ type TLSConfig struct {
// is not recommended, then you may notice idle connections building up over // is not recommended, then you may notice idle connections building up over
// time. To avoid this, use the DefaultNonPooledConfig() instead. // time. To avoid this, use the DefaultNonPooledConfig() instead.
func DefaultConfig() *Config { func DefaultConfig() *Config {
return defaultConfig(cleanhttp.DefaultPooledTransport) return defaultConfig(nil, cleanhttp.DefaultPooledTransport)
}
// DefaultConfigWithLogger returns a default configuration for the client. It
// is exactly the same as DefaultConfig, but allows for a pre-configured logger
// object to be passed through.
func DefaultConfigWithLogger(logger hclog.Logger) *Config {
return defaultConfig(logger, cleanhttp.DefaultPooledTransport)
} }
// DefaultNonPooledConfig returns a default configuration for the client which // DefaultNonPooledConfig returns a default configuration for the client which
@@ -367,12 +374,18 @@ func DefaultConfig() *Config {
// accumulation of idle connections if you make many client objects during the // accumulation of idle connections if you make many client objects during the
// lifetime of your application. // lifetime of your application.
func DefaultNonPooledConfig() *Config { func DefaultNonPooledConfig() *Config {
return defaultConfig(cleanhttp.DefaultTransport) return defaultConfig(nil, cleanhttp.DefaultTransport)
} }
// defaultConfig returns the default configuration for the client, using the // defaultConfig returns the default configuration for the client, using the
// given function to make the transport. // given function to make the transport.
func defaultConfig(transportFn func() *http.Transport) *Config { func defaultConfig(logger hclog.Logger, transportFn func() *http.Transport) *Config {
if logger == nil {
logger = hclog.New(&hclog.LoggerOptions{
Name: "consul-api",
})
}
config := &Config{ config := &Config{
Address: "127.0.0.1:8500", Address: "127.0.0.1:8500",
Scheme: "http", Scheme: "http",
@@ -410,7 +423,7 @@ func defaultConfig(transportFn func() *http.Transport) *Config {
if ssl := os.Getenv(HTTPSSLEnvName); ssl != "" { if ssl := os.Getenv(HTTPSSLEnvName); ssl != "" {
enabled, err := strconv.ParseBool(ssl) enabled, err := strconv.ParseBool(ssl)
if err != nil { if err != nil {
log.Printf("[WARN] client: could not parse %s: %s", HTTPSSLEnvName, err) logger.Warn(fmt.Sprintf("could not parse %s", HTTPSSLEnvName), "error", err)
} }
if enabled { if enabled {
@@ -436,7 +449,7 @@ func defaultConfig(transportFn func() *http.Transport) *Config {
if v := os.Getenv(HTTPSSLVerifyEnvName); v != "" { if v := os.Getenv(HTTPSSLVerifyEnvName); v != "" {
doVerify, err := strconv.ParseBool(v) doVerify, err := strconv.ParseBool(v)
if err != nil { if err != nil {
log.Printf("[WARN] client: could not parse %s: %s", HTTPSSLVerifyEnvName, err) logger.Warn(fmt.Sprintf("could not parse %s", HTTPSSLVerifyEnvName), "error", err)
} }
if !doVerify { if !doVerify {
config.TLSConfig.InsecureSkipVerify = true config.TLSConfig.InsecureSkipVerify = true

View File

@@ -33,7 +33,6 @@ func (d *DiscoveryChain) Get(name string, opts *DiscoveryChainOptions, q *QueryO
if opts.EvaluateInDatacenter != "" { if opts.EvaluateInDatacenter != "" {
r.params.Set("compile-dc", opts.EvaluateInDatacenter) r.params.Set("compile-dc", opts.EvaluateInDatacenter)
} }
// TODO(namespaces): handle possible EvaluateInNamespace here
} }
if method == "POST" { if method == "POST" {

View File

@@ -5,11 +5,12 @@ go 1.12
replace github.com/hashicorp/consul/sdk => ../sdk replace github.com/hashicorp/consul/sdk => ../sdk
require ( require (
github.com/hashicorp/consul/sdk v0.2.0 github.com/hashicorp/consul/sdk v0.4.0
github.com/hashicorp/go-cleanhttp v0.5.1 github.com/hashicorp/go-cleanhttp v0.5.1
github.com/hashicorp/go-hclog v0.12.0
github.com/hashicorp/go-rootcerts v1.0.2 github.com/hashicorp/go-rootcerts v1.0.2
github.com/hashicorp/go-uuid v1.0.1 github.com/hashicorp/go-uuid v1.0.1
github.com/hashicorp/serf v0.8.2 github.com/hashicorp/serf v0.8.2
github.com/mitchellh/mapstructure v1.1.2 github.com/mitchellh/mapstructure v1.1.2
github.com/stretchr/testify v1.3.0 github.com/stretchr/testify v1.4.0
) )

View File

@@ -6,21 +6,26 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c h1:964Od4U6p2jUkFxvCydnIczKteheJEzHRToSGK3Bnlw= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c h1:964Od4U6p2jUkFxvCydnIczKteheJEzHRToSGK3Bnlw=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/hashicorp/consul/sdk v0.4.0 h1:zBtCfKJZcJDBvSCkQJch4ulp59m1rATFLKwNo/LYY30=
github.com/hashicorp/consul/sdk v0.4.0/go.mod h1:fY08Y9z5SvJqevyZNy6WWPXiG3KwBPAvlcdx16zZ0fM=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-hclog v0.12.0 h1:d4QkX8FRTYaKaCZBoXYY8zJX2BXjWxurN/GA2tkrmZM=
github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0= github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3 h1:zKjpN5BK/P5lMYrLmBHdBULWbJ0XpYR+7NGzqkZzoD4= github.com/hashicorp/go-msgpack v0.5.3 h1:zKjpN5BK/P5lMYrLmBHdBULWbJ0XpYR+7NGzqkZzoD4=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-rootcerts v1.0.0 h1:Rqb66Oo1X/eSV1x66xbDccZjhJigjg0+e82kpwzSwCI=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
github.com/hashicorp/go-sockaddr v1.0.0 h1:GeH6tui99pF4NJgfnhp+L6+FfobzVW3Ah46sLo0ICXs= github.com/hashicorp/go-sockaddr v1.0.0 h1:GeH6tui99pF4NJgfnhp+L6+FfobzVW3Ah46sLo0ICXs=
@@ -38,13 +43,24 @@ github.com/hashicorp/memberlist v0.1.3 h1:EmmoJme1matNzb+hMpDuR/0sbJSUisxyqBGG67
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2 h1:YZ7UKsJv+hKjqGVUUbtE3HNj79Eln2oQ75tniF6iPt0= github.com/hashicorp/serf v0.8.2 h1:YZ7UKsJv+hKjqGVUUbtE3HNj79Eln2oQ75tniF6iPt0=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10=
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/miekg/dns v1.0.14 h1:9jZdLNd/P4+SfEJ0TNyxYpsK8N4GtfylBLqtbYN1sbA= github.com/miekg/dns v1.0.14 h1:9jZdLNd/P4+SfEJ0TNyxYpsK8N4GtfylBLqtbYN1sbA=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0=
@@ -68,6 +84,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3 h1:KYQXGkl6vs02hK7pK4eIbw0NpNPedieTSTEiJ//bwGs= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3 h1:KYQXGkl6vs02hK7pK4eIbw0NpNPedieTSTEiJ//bwGs=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -78,5 +96,18 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5 h1:x6r4Jo0KNzOOzYd8lbcRsqjuqEASK6ob3auvWYM4/8U= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5 h1:x6r4Jo0KNzOOzYd8lbcRsqjuqEASK6ob3auvWYM4/8U=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20191105231009-c1f44814a5cd h1:3x5uuvBgE6oaXJjCOvpCC1IpgJogqQ+PqGGU3ZxAgII= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20191105231009-c1f44814a5cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be h1:QAcqgptGM8IQBC9K/RC4o+O9YmqEm0diQn9QmZw/0mU=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9 h1:1/DFK4b7JH8DmkqhUk48onnSfrPzImPoVxuomtbT2nk=
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@@ -51,6 +51,7 @@ type HealthCheckDefinition struct {
HTTP string HTTP string
Header map[string][]string Header map[string][]string
Method string Method string
Body string
TLSSkipVerify bool TLSSkipVerify bool
TCP string TCP string
IntervalDuration time.Duration `json:"-"` IntervalDuration time.Duration `json:"-"`

View File

@@ -25,6 +25,9 @@ type ServiceQuery struct {
// Service is the service to query. // Service is the service to query.
Service string Service string
// Namespace of the service to query
Namespace string `json:",omitempty"`
// Near allows baking in the name of a node to automatically distance- // Near allows baking in the name of a node to automatically distance-
// sort from. The magic "_agent" value is supported, which sorts near // sort from. The magic "_agent" value is supported, which sorts near
// the agent which initiated the request by default. // the agent which initiated the request by default.
@@ -119,6 +122,9 @@ type PreparedQueryExecuteResponse struct {
// Service is the service that was queried. // Service is the service that was queried.
Service string Service string
// Namespace of the service that was queried
Namespace string `json:",omitempty"`
// Nodes has the nodes that were output by the query. // Nodes has the nodes that were output by the query.
Nodes []ServiceEntry Nodes []ServiceEntry

View File

@@ -75,12 +75,13 @@ const (
// KVTxnOp defines a single operation inside a transaction. // KVTxnOp defines a single operation inside a transaction.
type KVTxnOp struct { type KVTxnOp struct {
Verb KVOp Verb KVOp
Key string Key string
Value []byte Value []byte
Flags uint64 Flags uint64
Index uint64 Index uint64
Session string Session string
Namespace string `json:",omitempty"`
} }
// KVTxnOps defines a set of operations to be performed inside a single // KVTxnOps defines a set of operations to be performed inside a single

View File

@@ -2,9 +2,11 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/armon/go-metrics v0.3.0/go.mod h1:zXjbSimjXTd7vOpY8B0/2LpvNvDoXBuplAD+gJD3GYs= github.com/armon/go-metrics v0.3.0/go.mod h1:zXjbSimjXTd7vOpY8B0/2LpvNvDoXBuplAD+gJD3GYs=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310 h1:BUAU3CGlLvorLI26FmByPp2eC2qla6E1Tw+scpcg/to=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/aws/aws-sdk-go v1.25.37/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.25.37/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
@@ -65,6 +67,7 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10= github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10=
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mitchellh/cli v1.0.0 h1:iGBIsUe3+HZ/AD/Vd7DErOt5sU9fa8Uj7A2s1aggv1Y=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
@@ -82,6 +85,7 @@ github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1 h1:ccV59UEOTzVDnDUEFdT95ZzHVZ+5+158q8+SJb2QV5w=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=

View File

@@ -116,7 +116,7 @@ func (c *Sys) DisableAudit(path string) error {
return err return err
} }
// Structures for the requests/resposne are all down here. They aren't // Structures for the requests/response are all down here. They aren't
// individually documented because the map almost directly to the raw HTTP API // individually documented because the map almost directly to the raw HTTP API
// documentation. Please refer to that documentation for more details. // documentation. Please refer to that documentation for more details.

64
vendor/github.com/hashicorp/vault/api/sys_monitor.go generated vendored Normal file
View File

@@ -0,0 +1,64 @@
package api
import (
"bufio"
"context"
"fmt"
)
// Monitor returns a channel that outputs strings containing the log messages
// coming from the server.
func (c *Sys) Monitor(ctx context.Context, logLevel string) (chan string, error) {
r := c.c.NewRequest("GET", "/v1/sys/monitor")
if logLevel == "" {
r.Params.Add("log_level", "info")
} else {
r.Params.Add("log_level", logLevel)
}
resp, err := c.c.RawRequestWithContext(ctx, r)
if err != nil {
return nil, err
}
logCh := make(chan string, 64)
go func() {
scanner := bufio.NewScanner(resp.Body)
droppedCount := 0
defer close(logCh)
defer resp.Body.Close()
for {
if ctx.Err() != nil {
return
}
if !scanner.Scan() {
return
}
logMessage := scanner.Text()
if droppedCount > 0 {
select {
case logCh <- fmt.Sprintf("Monitor dropped %d logs during monitor request\n", droppedCount):
droppedCount = 0
default:
droppedCount++
continue
}
}
select {
case logCh <- logMessage:
default:
droppedCount++
}
}
}()
return logCh, nil
}

View File

@@ -171,10 +171,9 @@ func (c *Client) GetUserDN(cfg *ConfigEntry, conn Connection, bindDN, username s
if err != nil { if err != nil {
return userDN, errwrap.Wrapf("LDAP search failed for detecting user: {{err}}", err) return userDN, errwrap.Wrapf("LDAP search failed for detecting user: {{err}}", err)
} }
if len(result.Entries) != 1 { for _, e := range result.Entries {
return userDN, fmt.Errorf("LDAP search for userdn 0 or not unique") userDN = e.DN
} }
userDN = result.Entries[0].DN
} else { } else {
userDN = bindDN userDN = bindDN
} }

View File

@@ -185,13 +185,13 @@ func RespondWithStatusCode(resp *Response, req *Request, code int) (*Response, e
} }
// HTTPResponseWriter is optionally added to a request object and can be used to // HTTPResponseWriter is optionally added to a request object and can be used to
// write directly to the HTTP response writter. // write directly to the HTTP response writer.
type HTTPResponseWriter struct { type HTTPResponseWriter struct {
http.ResponseWriter http.ResponseWriter
written *uint32 written *uint32
} }
// NewHTTPResponseWriter creates a new HTTPRepoinseWriter object that wraps the // NewHTTPResponseWriter creates a new HTTPResponseWriter object that wraps the
// provided io.Writer. // provided io.Writer.
func NewHTTPResponseWriter(w http.ResponseWriter) *HTTPResponseWriter { func NewHTTPResponseWriter(w http.ResponseWriter) *HTTPResponseWriter {
return &HTTPResponseWriter{ return &HTTPResponseWriter{

View File

@@ -1,5 +1,3 @@
The MIT License (MIT)
Copyright 2012 Keith Rarick Copyright 2012 Keith Rarick
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy

View File

@@ -125,7 +125,6 @@ func (p *printer) printValue(v reflect.Value, showType, quote bool) {
} }
keys := v.MapKeys() keys := v.MapKeys()
for i := 0; i < v.Len(); i++ { for i := 0; i < v.Len(); i++ {
showTypeInStruct := true
k := keys[i] k := keys[i]
mv := v.MapIndex(k) mv := v.MapIndex(k)
pp.printValue(k, false, true) pp.printValue(k, false, true)
@@ -133,7 +132,7 @@ func (p *printer) printValue(v reflect.Value, showType, quote bool) {
if expand { if expand {
writeByte(pp, '\t') writeByte(pp, '\t')
} }
showTypeInStruct = t.Elem().Kind() == reflect.Interface showTypeInStruct := t.Elem().Kind() == reflect.Interface
pp.printValue(mv, showTypeInStruct, true) pp.printValue(mv, showTypeInStruct, true)
if expand { if expand {
io.WriteString(pp, ",\n") io.WriteString(pp, ",\n")

6
vendor/github.com/kr/pretty/go.mod generated vendored
View File

@@ -1,3 +1,5 @@
module "github.com/kr/pretty" module github.com/kr/pretty
require "github.com/kr/text" v0.1.0 go 1.12
require github.com/kr/text v0.1.0

3
vendor/github.com/kr/pretty/go.sum generated vendored Normal file
View File

@@ -0,0 +1,3 @@
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=

View File

@@ -75,7 +75,7 @@ func Printf(format string, a ...interface{}) (n int, errno error) {
// Println pretty-prints its operands and writes to standard output. // Println pretty-prints its operands and writes to standard output.
// //
// Calling Print(x, y) is equivalent to // Calling Println(x, y) is equivalent to
// fmt.Println(Formatter(x), Formatter(y)), but each operand is // fmt.Println(Formatter(x), Formatter(y)), but each operand is
// formatted with "%# v". // formatted with "%# v".
func Println(a ...interface{}) (n int, errno error) { func Println(a ...interface{}) (n int, errno error) {

View File

@@ -1,9 +1,15 @@
language: go language: go
sudo: false
go: go:
- 1.13.x
- tip - tip
before_install: before_install:
- go get github.com/mattn/goveralls - go get -t -v ./...
- go get golang.org/x/tools/cmd/cover
script: script:
- $HOME/gopath/bin/goveralls -repotoken xnXqRGwgW3SXIguzxf90ZSK1GPYZPaGrw - ./go.test.sh
after_success:
- bash <(curl -s https://codecov.io/bash)

View File

@@ -1,8 +1,8 @@
# go-colorable # go-colorable
[![Godoc Reference](https://godoc.org/github.com/mattn/go-colorable?status.svg)](http://godoc.org/github.com/mattn/go-colorable)
[![Build Status](https://travis-ci.org/mattn/go-colorable.svg?branch=master)](https://travis-ci.org/mattn/go-colorable) [![Build Status](https://travis-ci.org/mattn/go-colorable.svg?branch=master)](https://travis-ci.org/mattn/go-colorable)
[![Coverage Status](https://coveralls.io/repos/github/mattn/go-colorable/badge.svg?branch=master)](https://coveralls.io/github/mattn/go-colorable?branch=master) [![Codecov](https://codecov.io/gh/mattn/go-colorable/branch/master/graph/badge.svg)](https://codecov.io/gh/mattn/go-colorable)
[![GoDoc](https://godoc.org/github.com/mattn/go-colorable?status.svg)](http://godoc.org/github.com/mattn/go-colorable)
[![Go Report Card](https://goreportcard.com/badge/mattn/go-colorable)](https://goreportcard.com/report/mattn/go-colorable) [![Go Report Card](https://goreportcard.com/badge/mattn/go-colorable)](https://goreportcard.com/report/mattn/go-colorable)
Colorable writer for windows. Colorable writer for windows.

View File

@@ -27,3 +27,11 @@ func NewColorableStdout() io.Writer {
func NewColorableStderr() io.Writer { func NewColorableStderr() io.Writer {
return os.Stderr return os.Stderr
} }
// EnableColorsStdout enable colors if possible.
func EnableColorsStdout(enabled *bool) func() {
if enabled != nil {
*enabled = true
}
return func() {}
}

View File

@@ -28,3 +28,11 @@ func NewColorableStdout() io.Writer {
func NewColorableStderr() io.Writer { func NewColorableStderr() io.Writer {
return os.Stderr return os.Stderr
} }
// EnableColorsStdout enable colors if possible.
func EnableColorsStdout(enabled *bool) func() {
if enabled != nil {
*enabled = true
}
return func() {}
}

View File

@@ -27,6 +27,8 @@ const (
backgroundRed = 0x40 backgroundRed = 0x40
backgroundIntensity = 0x80 backgroundIntensity = 0x80
backgroundMask = (backgroundRed | backgroundBlue | backgroundGreen | backgroundIntensity) backgroundMask = (backgroundRed | backgroundBlue | backgroundGreen | backgroundIntensity)
cENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x4
) )
const ( const (
@@ -78,6 +80,8 @@ var (
procGetConsoleCursorInfo = kernel32.NewProc("GetConsoleCursorInfo") procGetConsoleCursorInfo = kernel32.NewProc("GetConsoleCursorInfo")
procSetConsoleCursorInfo = kernel32.NewProc("SetConsoleCursorInfo") procSetConsoleCursorInfo = kernel32.NewProc("SetConsoleCursorInfo")
procSetConsoleTitle = kernel32.NewProc("SetConsoleTitleW") procSetConsoleTitle = kernel32.NewProc("SetConsoleTitleW")
procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
procSetConsoleMode = kernel32.NewProc("SetConsoleMode")
procCreateConsoleScreenBuffer = kernel32.NewProc("CreateConsoleScreenBuffer") procCreateConsoleScreenBuffer = kernel32.NewProc("CreateConsoleScreenBuffer")
) )
@@ -98,6 +102,10 @@ func NewColorable(file *os.File) io.Writer {
} }
if isatty.IsTerminal(file.Fd()) { if isatty.IsTerminal(file.Fd()) {
var mode uint32
if r, _, _ := procGetConsoleMode.Call(file.Fd(), uintptr(unsafe.Pointer(&mode))); r != 0 && mode&cENABLE_VIRTUAL_TERMINAL_PROCESSING != 0 {
return file
}
var csbi consoleScreenBufferInfo var csbi consoleScreenBufferInfo
handle := syscall.Handle(file.Fd()) handle := syscall.Handle(file.Fd())
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
@@ -1003,3 +1011,23 @@ func n256setup() {
n256backAttr[i] = c.backgroundAttr() n256backAttr[i] = c.backgroundAttr()
} }
} }
// EnableColorsStdout enable colors if possible.
func EnableColorsStdout(enabled *bool) func() {
var mode uint32
h := os.Stdout.Fd()
if r, _, _ := procGetConsoleMode.Call(h, uintptr(unsafe.Pointer(&mode))); r != 0 {
if r, _, _ = procSetConsoleMode.Call(h, uintptr(mode|cENABLE_VIRTUAL_TERMINAL_PROCESSING)); r != 0 {
if enabled != nil {
*enabled = true
}
return func() {
procSetConsoleMode.Call(h, uintptr(mode))
}
}
}
if enabled != nil {
*enabled = true
}
return func() {}
}

View File

@@ -1,3 +1,8 @@
module github.com/mattn/go-colorable module github.com/mattn/go-colorable
require github.com/mattn/go-isatty v0.0.8 require (
github.com/mattn/go-isatty v0.0.12
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae // indirect
)
go 1.13

View File

@@ -1,4 +1,5 @@
github.com/mattn/go-isatty v0.0.5 h1:tHXDdz1cpzGaovsTB+TVB8q90WEokoVmfMqoVcrLUgw= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

12
vendor/github.com/mattn/go-colorable/go.test.sh generated vendored Normal file
View File

@@ -0,0 +1,12 @@
#!/usr/bin/env bash
set -e
echo "" > coverage.txt
for d in $(go list ./... | grep -v vendor); do
go test -race -coverprofile=profile.out -covermode=atomic "$d"
if [ -f profile.out ]; then
cat profile.out >> coverage.txt
rm profile.out
fi
done

View File

@@ -1,13 +1,14 @@
language: go language: go
sudo: false
go: go:
- 1.13.x
- tip - tip
os:
- linux
- osx
before_install: before_install:
- go get github.com/mattn/goveralls - go get -t -v ./...
- go get golang.org/x/tools/cmd/cover
script: script:
- $HOME/gopath/bin/goveralls -repotoken 3gHdORO5k5ziZcWMBxnd9LrMZaJs8m9x5 - ./go.test.sh
after_success:
- bash <(curl -s https://codecov.io/bash)

View File

@@ -1,7 +1,7 @@
# go-isatty # go-isatty
[![Godoc Reference](https://godoc.org/github.com/mattn/go-isatty?status.svg)](http://godoc.org/github.com/mattn/go-isatty) [![Godoc Reference](https://godoc.org/github.com/mattn/go-isatty?status.svg)](http://godoc.org/github.com/mattn/go-isatty)
[![Build Status](https://travis-ci.org/mattn/go-isatty.svg?branch=master)](https://travis-ci.org/mattn/go-isatty) [![Codecov](https://codecov.io/gh/mattn/go-isatty/branch/master/graph/badge.svg)](https://codecov.io/gh/mattn/go-isatty)
[![Coverage Status](https://coveralls.io/repos/github/mattn/go-isatty/badge.svg?branch=master)](https://coveralls.io/github/mattn/go-isatty?branch=master) [![Coverage Status](https://coveralls.io/repos/github/mattn/go-isatty/badge.svg?branch=master)](https://coveralls.io/github/mattn/go-isatty?branch=master)
[![Go Report Card](https://goreportcard.com/badge/mattn/go-isatty)](https://goreportcard.com/report/mattn/go-isatty) [![Go Report Card](https://goreportcard.com/badge/mattn/go-isatty)](https://goreportcard.com/report/mattn/go-isatty)

View File

@@ -1,5 +1,5 @@
module github.com/mattn/go-isatty module github.com/mattn/go-isatty
require golang.org/x/sys v0.0.0-20191008105621-543471e840be go 1.12
go 1.14 require golang.org/x/sys v0.0.0-20200116001909-b77594299b42

View File

@@ -1,4 +1,2 @@
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ= golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be h1:QAcqgptGM8IQBC9K/RC4o+O9YmqEm0diQn9QmZw/0mU=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

12
vendor/github.com/mattn/go-isatty/go.test.sh generated vendored Normal file
View File

@@ -0,0 +1,12 @@
#!/usr/bin/env bash
set -e
echo "" > coverage.txt
for d in $(go list ./... | grep -v vendor); do
go test -race -coverprofile=profile.out -covermode=atomic "$d"
if [ -f profile.out ]; then
cat profile.out >> coverage.txt
rm profile.out
fi
done

View File

@@ -1,23 +0,0 @@
// +build android
package isatty
import (
"syscall"
"unsafe"
)
const ioctlReadTermios = syscall.TCGETS
// IsTerminal return true if the file descriptor is terminal.
func IsTerminal(fd uintptr) bool {
var termios syscall.Termios
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, fd, ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
return err == 0
}
// IsCygwinTerminal return true if the file descriptor is a cygwin or msys2
// terminal. This is also always false on this environment.
func IsCygwinTerminal(fd uintptr) bool {
return false
}

View File

@@ -3,18 +3,12 @@
package isatty package isatty
import ( import "golang.org/x/sys/unix"
"syscall"
"unsafe"
)
const ioctlReadTermios = syscall.TIOCGETA
// IsTerminal return true if the file descriptor is terminal. // IsTerminal return true if the file descriptor is terminal.
func IsTerminal(fd uintptr) bool { func IsTerminal(fd uintptr) bool {
var termios syscall.Termios _, err := unix.IoctlGetTermios(int(fd), unix.TIOCGETA)
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, fd, ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0) return err == nil
return err == 0
} }
// IsCygwinTerminal return true if the file descriptor is a cygwin or msys2 // IsCygwinTerminal return true if the file descriptor is a cygwin or msys2

View File

@@ -8,7 +8,7 @@ import (
// IsTerminal returns true if the given file descriptor is a terminal. // IsTerminal returns true if the given file descriptor is a terminal.
func IsTerminal(fd uintptr) bool { func IsTerminal(fd uintptr) bool {
path, err := syscall.Fd2path(fd) path, err := syscall.Fd2path(int(fd))
if err != nil { if err != nil {
return false return false
} }

View File

@@ -1,6 +1,5 @@
// +build linux aix // +build linux aix
// +build !appengine // +build !appengine
// +build !android
package isatty package isatty

8
vendor/github.com/mattn/go-isatty/renovate.json generated vendored Normal file
View File

@@ -0,0 +1,8 @@
{
"extends": [
"config:base"
],
"postUpdateOptions": [
"gomodTidy"
]
}

18
vendor/gopkg.in/yaml.v2/.travis.yml generated vendored
View File

@@ -1,12 +1,16 @@
language: go language: go
go: go:
- 1.4 - "1.4.x"
- 1.5 - "1.5.x"
- 1.6 - "1.6.x"
- 1.7 - "1.7.x"
- 1.8 - "1.8.x"
- 1.9 - "1.9.x"
- tip - "1.10.x"
- "1.11.x"
- "1.12.x"
- "1.13.x"
- "tip"
go_import_path: gopkg.in/yaml.v2 go_import_path: gopkg.in/yaml.v2

109
vendor/gopkg.in/yaml.v2/scannerc.go generated vendored
View File

@@ -626,30 +626,17 @@ func trace(args ...interface{}) func() {
func yaml_parser_fetch_more_tokens(parser *yaml_parser_t) bool { func yaml_parser_fetch_more_tokens(parser *yaml_parser_t) bool {
// While we need more tokens to fetch, do it. // While we need more tokens to fetch, do it.
for { for {
// Check if we really need to fetch more tokens. if parser.tokens_head != len(parser.tokens) {
need_more_tokens := false // If queue is non-empty, check if any potential simple key may
// occupy the head position.
if parser.tokens_head == len(parser.tokens) { head_tok_idx, ok := parser.simple_keys_by_tok[parser.tokens_parsed]
// Queue is empty. if !ok {
need_more_tokens = true break
} else { } else if valid, ok := yaml_simple_key_is_valid(parser, &parser.simple_keys[head_tok_idx]); !ok {
// Check if any potential simple key may occupy the head position.
if !yaml_parser_stale_simple_keys(parser) {
return false return false
} else if !valid {
break
} }
for i := range parser.simple_keys {
simple_key := &parser.simple_keys[i]
if simple_key.possible && simple_key.token_number == parser.tokens_parsed {
need_more_tokens = true
break
}
}
}
// We are finished.
if !need_more_tokens {
break
} }
// Fetch the next token. // Fetch the next token.
if !yaml_parser_fetch_next_token(parser) { if !yaml_parser_fetch_next_token(parser) {
@@ -678,11 +665,6 @@ func yaml_parser_fetch_next_token(parser *yaml_parser_t) bool {
return false return false
} }
// Remove obsolete potential simple keys.
if !yaml_parser_stale_simple_keys(parser) {
return false
}
// Check the indentation level against the current column. // Check the indentation level against the current column.
if !yaml_parser_unroll_indent(parser, parser.mark.column) { if !yaml_parser_unroll_indent(parser, parser.mark.column) {
return false return false
@@ -837,29 +819,30 @@ func yaml_parser_fetch_next_token(parser *yaml_parser_t) bool {
"found character that cannot start any token") "found character that cannot start any token")
} }
// Check the list of potential simple keys and remove the positions that func yaml_simple_key_is_valid(parser *yaml_parser_t, simple_key *yaml_simple_key_t) (valid, ok bool) {
// cannot contain simple keys anymore. if !simple_key.possible {
func yaml_parser_stale_simple_keys(parser *yaml_parser_t) bool { return false, true
// Check for a potential simple key for each flow level.
for i := range parser.simple_keys {
simple_key := &parser.simple_keys[i]
// The specification requires that a simple key
//
// - is limited to a single line,
// - is shorter than 1024 characters.
if simple_key.possible && (simple_key.mark.line < parser.mark.line || simple_key.mark.index+1024 < parser.mark.index) {
// Check if the potential simple key to be removed is required.
if simple_key.required {
return yaml_parser_set_scanner_error(parser,
"while scanning a simple key", simple_key.mark,
"could not find expected ':'")
}
simple_key.possible = false
}
} }
return true
// The 1.2 specification says:
//
// "If the ? indicator is omitted, parsing needs to see past the
// implicit key to recognize it as such. To limit the amount of
// lookahead required, the “:” indicator must appear at most 1024
// Unicode characters beyond the start of the key. In addition, the key
// is restricted to a single line."
//
if simple_key.mark.line < parser.mark.line || simple_key.mark.index+1024 < parser.mark.index {
// Check if the potential simple key to be removed is required.
if simple_key.required {
return false, yaml_parser_set_scanner_error(parser,
"while scanning a simple key", simple_key.mark,
"could not find expected ':'")
}
simple_key.possible = false
return false, true
}
return true, true
} }
// Check if a simple key may start at the current position and add it if // Check if a simple key may start at the current position and add it if
@@ -879,13 +862,14 @@ func yaml_parser_save_simple_key(parser *yaml_parser_t) bool {
possible: true, possible: true,
required: required, required: required,
token_number: parser.tokens_parsed + (len(parser.tokens) - parser.tokens_head), token_number: parser.tokens_parsed + (len(parser.tokens) - parser.tokens_head),
mark: parser.mark,
} }
simple_key.mark = parser.mark
if !yaml_parser_remove_simple_key(parser) { if !yaml_parser_remove_simple_key(parser) {
return false return false
} }
parser.simple_keys[len(parser.simple_keys)-1] = simple_key parser.simple_keys[len(parser.simple_keys)-1] = simple_key
parser.simple_keys_by_tok[simple_key.token_number] = len(parser.simple_keys) - 1
} }
return true return true
} }
@@ -900,9 +884,10 @@ func yaml_parser_remove_simple_key(parser *yaml_parser_t) bool {
"while scanning a simple key", parser.simple_keys[i].mark, "while scanning a simple key", parser.simple_keys[i].mark,
"could not find expected ':'") "could not find expected ':'")
} }
// Remove the key from the stack.
parser.simple_keys[i].possible = false
delete(parser.simple_keys_by_tok, parser.simple_keys[i].token_number)
} }
// Remove the key from the stack.
parser.simple_keys[i].possible = false
return true return true
} }
@@ -912,7 +897,12 @@ const max_flow_level = 10000
// Increase the flow level and resize the simple key list if needed. // Increase the flow level and resize the simple key list if needed.
func yaml_parser_increase_flow_level(parser *yaml_parser_t) bool { func yaml_parser_increase_flow_level(parser *yaml_parser_t) bool {
// Reset the simple key on the next level. // Reset the simple key on the next level.
parser.simple_keys = append(parser.simple_keys, yaml_simple_key_t{}) parser.simple_keys = append(parser.simple_keys, yaml_simple_key_t{
possible: false,
required: false,
token_number: parser.tokens_parsed + (len(parser.tokens) - parser.tokens_head),
mark: parser.mark,
})
// Increase the flow level. // Increase the flow level.
parser.flow_level++ parser.flow_level++
@@ -928,7 +918,9 @@ func yaml_parser_increase_flow_level(parser *yaml_parser_t) bool {
func yaml_parser_decrease_flow_level(parser *yaml_parser_t) bool { func yaml_parser_decrease_flow_level(parser *yaml_parser_t) bool {
if parser.flow_level > 0 { if parser.flow_level > 0 {
parser.flow_level-- parser.flow_level--
parser.simple_keys = parser.simple_keys[:len(parser.simple_keys)-1] last := len(parser.simple_keys) - 1
delete(parser.simple_keys_by_tok, parser.simple_keys[last].token_number)
parser.simple_keys = parser.simple_keys[:last]
} }
return true return true
} }
@@ -1005,6 +997,8 @@ func yaml_parser_fetch_stream_start(parser *yaml_parser_t) bool {
// Initialize the simple key stack. // Initialize the simple key stack.
parser.simple_keys = append(parser.simple_keys, yaml_simple_key_t{}) parser.simple_keys = append(parser.simple_keys, yaml_simple_key_t{})
parser.simple_keys_by_tok = make(map[int]int)
// A simple key is allowed at the beginning of the stream. // A simple key is allowed at the beginning of the stream.
parser.simple_key_allowed = true parser.simple_key_allowed = true
@@ -1286,7 +1280,11 @@ func yaml_parser_fetch_value(parser *yaml_parser_t) bool {
simple_key := &parser.simple_keys[len(parser.simple_keys)-1] simple_key := &parser.simple_keys[len(parser.simple_keys)-1]
// Have we found a simple key? // Have we found a simple key?
if simple_key.possible { if valid, ok := yaml_simple_key_is_valid(parser, simple_key); !ok {
return false
} else if valid {
// Create the KEY token and insert it into the queue. // Create the KEY token and insert it into the queue.
token := yaml_token_t{ token := yaml_token_t{
typ: yaml_KEY_TOKEN, typ: yaml_KEY_TOKEN,
@@ -1304,6 +1302,7 @@ func yaml_parser_fetch_value(parser *yaml_parser_t) bool {
// Remove the simple key. // Remove the simple key.
simple_key.possible = false simple_key.possible = false
delete(parser.simple_keys_by_tok, simple_key.token_number)
// A simple key cannot follow another simple key. // A simple key cannot follow another simple key.
parser.simple_key_allowed = false parser.simple_key_allowed = false

2
vendor/gopkg.in/yaml.v2/yaml.go generated vendored
View File

@@ -89,7 +89,7 @@ func UnmarshalStrict(in []byte, out interface{}) (err error) {
return unmarshal(in, out, true) return unmarshal(in, out, true)
} }
// A Decorder reads and decodes YAML values from an input stream. // A Decoder reads and decodes YAML values from an input stream.
type Decoder struct { type Decoder struct {
strict bool strict bool
parser *parser parser *parser

1
vendor/gopkg.in/yaml.v2/yamlh.go generated vendored
View File

@@ -579,6 +579,7 @@ type yaml_parser_t struct {
simple_key_allowed bool // May a simple key occur at the current position? simple_key_allowed bool // May a simple key occur at the current position?
simple_keys []yaml_simple_key_t // The stack of simple keys. simple_keys []yaml_simple_key_t // The stack of simple keys.
simple_keys_by_tok map[int]int // possible simple_key indexes indexed by token_number
// Parser stuff // Parser stuff

14
vendor/modules.txt vendored
View File

@@ -233,7 +233,7 @@ github.com/duosecurity/duo_api_golang
github.com/duosecurity/duo_api_golang/authapi github.com/duosecurity/duo_api_golang/authapi
# github.com/elazarl/go-bindata-assetfs v1.0.1-0.20200509193318-234c15e7648f # github.com/elazarl/go-bindata-assetfs v1.0.1-0.20200509193318-234c15e7648f
github.com/elazarl/go-bindata-assetfs github.com/elazarl/go-bindata-assetfs
# github.com/fatih/color v1.7.0 # github.com/fatih/color v1.9.0
github.com/fatih/color github.com/fatih/color
# github.com/fatih/structs v1.1.0 # github.com/fatih/structs v1.1.0
github.com/fatih/structs github.com/fatih/structs
@@ -313,7 +313,7 @@ github.com/gorhill/cronexpr
github.com/gorilla/websocket github.com/gorilla/websocket
# github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed # github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed
github.com/hailocab/go-hostpool github.com/hailocab/go-hostpool
# github.com/hashicorp/consul-template v0.22.0 # github.com/hashicorp/consul-template v0.25.0
github.com/hashicorp/consul-template/child github.com/hashicorp/consul-template/child
github.com/hashicorp/consul-template/config github.com/hashicorp/consul-template/config
github.com/hashicorp/consul-template/dependency github.com/hashicorp/consul-template/dependency
@@ -324,7 +324,7 @@ github.com/hashicorp/consul-template/signals
github.com/hashicorp/consul-template/template github.com/hashicorp/consul-template/template
github.com/hashicorp/consul-template/version github.com/hashicorp/consul-template/version
github.com/hashicorp/consul-template/watch github.com/hashicorp/consul-template/watch
# github.com/hashicorp/consul/api v1.2.1-0.20200128105449-6681be918a6e # github.com/hashicorp/consul/api v1.4.0
github.com/hashicorp/consul/api github.com/hashicorp/consul/api
# github.com/hashicorp/errwrap v1.0.0 # github.com/hashicorp/errwrap v1.0.0
github.com/hashicorp/errwrap github.com/hashicorp/errwrap
@@ -596,7 +596,7 @@ github.com/keybase/go-crypto/openpgp/s2k
github.com/keybase/go-crypto/rsa github.com/keybase/go-crypto/rsa
# github.com/konsorten/go-windows-terminal-sequences v1.0.1 # github.com/konsorten/go-windows-terminal-sequences v1.0.1
github.com/konsorten/go-windows-terminal-sequences github.com/konsorten/go-windows-terminal-sequences
# github.com/kr/pretty v0.1.0 # github.com/kr/pretty v0.2.0
github.com/kr/pretty github.com/kr/pretty
# github.com/kr/text v0.1.0 # github.com/kr/text v0.1.0
github.com/kr/text github.com/kr/text
@@ -604,9 +604,9 @@ github.com/kr/text
github.com/lib/pq github.com/lib/pq
github.com/lib/pq/oid github.com/lib/pq/oid
github.com/lib/pq/scram github.com/lib/pq/scram
# github.com/mattn/go-colorable v0.1.4 # github.com/mattn/go-colorable v0.1.6
github.com/mattn/go-colorable github.com/mattn/go-colorable
# github.com/mattn/go-isatty v0.0.10 # github.com/mattn/go-isatty v0.0.12
github.com/mattn/go-isatty github.com/mattn/go-isatty
# github.com/mattn/go-shellwords v1.0.5 # github.com/mattn/go-shellwords v1.0.5
github.com/mattn/go-shellwords github.com/mattn/go-shellwords
@@ -1110,7 +1110,7 @@ gopkg.in/square/go-jose.v2
gopkg.in/square/go-jose.v2/cipher gopkg.in/square/go-jose.v2/cipher
gopkg.in/square/go-jose.v2/json gopkg.in/square/go-jose.v2/json
gopkg.in/square/go-jose.v2/jwt gopkg.in/square/go-jose.v2/jwt
# gopkg.in/yaml.v2 v2.2.5 # gopkg.in/yaml.v2 v2.2.8
gopkg.in/yaml.v2 gopkg.in/yaml.v2
# honnef.co/go/tools v0.0.1-2020.1.3 # honnef.co/go/tools v0.0.1-2020.1.3
honnef.co/go/tools/arg honnef.co/go/tools/arg