diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e44f7a7a..c7801de80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,9 +4,12 @@ IMPROVEMENTS * agent: improve startup message when no error occurs [[GH-5896](https://github.com/hashicorp/consul/issues/5896)] * agent: make sure client agent rate limits apply when hitting the client interface on a server directly [[GH-5927](https://github.com/hashicorp/consul/pull/5927)] +* agent: use stale requests when performing full sync [[GH-5873](https://github.com/hashicorp/consul/pull/5873)] +* agent: transfer leadership when establishLeadership fails [[GH-5247](https://github.com/hashicorp/consul/pull/5247)] * connect: provide -admin-access-log-path for envoy [[GH-5858](https://github.com/hashicorp/consul/pull/5858)] * connect: upgrade Envoy xDS protocol to support Envoy 1.10 [[GH-5872](https://github.com/hashicorp/consul/pull/5872)] - +* ui: Improve linking between sidecars and proxies and their services/service instances [[GH-5944](https://github.com/hashicorp/consul/pull/5944)] +* ui: Add ability to search for tokens by policy, role or service identity name [[GH-5811](https://github.com/hashicorp/consul/pull/5811)] BUG FIXES: @@ -14,6 +17,7 @@ BUG FIXES: * api: update link to agent caching in comments [[GH-5935](https://github.com/hashicorp/consul/pull/5935)] * connect: fix proxy address formatting for IPv6 addresses [[GH-5460](https://github.com/hashicorp/consul/issues/5460)] * ui: fix service instance linking when multiple non-unique service id's exist on multiple nodes [[GH-5933](https://github.com/hashicorp/consul/pull/5933)] +* ui: Improve error messaging for ACL policies [[GH-5836](https://github.com/hashicorp/consul/pull/5836)] * txn: Fixed an issue that would allow a CAS operation on a service to work when it shouldn't have. [[GH-5971](https://github.com/hashicorp/consul/pull/5971)] ## 1.5.1 (May 22, 2019) diff --git a/agent/agent.go b/agent/agent.go index a7c07670e..3a28af3dc 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -915,6 +915,7 @@ func (a *Agent) consulConfig() (*consul.Config, error) { base.CoordinateUpdateBatchSize = a.config.ConsulCoordinateUpdateBatchSize base.CoordinateUpdateMaxBatches = a.config.ConsulCoordinateUpdateMaxBatches base.CoordinateUpdatePeriod = a.config.ConsulCoordinateUpdatePeriod + base.CheckOutputMaxSize = a.config.CheckOutputMaxSize base.RaftConfig.HeartbeatTimeout = a.config.ConsulRaftHeartbeatTimeout base.RaftConfig.LeaderLeaseTimeout = a.config.ConsulRaftLeaderLeaseTimeout @@ -971,6 +972,9 @@ func (a *Agent) consulConfig() (*consul.Config, error) { if a.config.Bootstrap { base.Bootstrap = true } + if a.config.CheckOutputMaxSize > 0 { + base.CheckOutputMaxSize = a.config.CheckOutputMaxSize + } if a.config.RejoinAfterLeave { base.RejoinAfterLeave = true } @@ -2248,6 +2252,13 @@ func (a *Agent) addCheck(check *structs.HealthCheck, chkType *structs.CheckType, // Check if already registered if chkType != nil { + maxOutputSize := a.config.CheckOutputMaxSize + if maxOutputSize == 0 { + maxOutputSize = checks.DefaultBufSize + } + if chkType.OutputMaxSize > 0 && maxOutputSize > chkType.OutputMaxSize { + maxOutputSize = chkType.OutputMaxSize + } switch { case chkType.IsTTL(): @@ -2257,10 +2268,11 @@ func (a *Agent) addCheck(check *structs.HealthCheck, chkType *structs.CheckType, } ttl := &checks.CheckTTL{ - Notify: a.State, - CheckID: check.CheckID, - TTL: chkType.TTL, - Logger: a.logger, + Notify: a.State, + CheckID: check.CheckID, + TTL: chkType.TTL, + Logger: a.logger, + OutputMaxSize: maxOutputSize, } // Restore persisted state, if any @@ -2294,6 +2306,7 @@ func (a *Agent) addCheck(check *structs.HealthCheck, chkType *structs.CheckType, Interval: chkType.Interval, Timeout: chkType.Timeout, Logger: a.logger, + OutputMaxSize: maxOutputSize, TLSClientConfig: tlsClientConfig, } http.Start() @@ -2361,7 +2374,7 @@ func (a *Agent) addCheck(check *structs.HealthCheck, chkType *structs.CheckType, } if a.dockerClient == nil { - dc, err := checks.NewDockerClient(os.Getenv("DOCKER_HOST"), checks.BufSize) + dc, err := checks.NewDockerClient(os.Getenv("DOCKER_HOST"), int64(maxOutputSize)) if err != nil { a.logger.Printf("[ERR] agent: error creating docker client: %s", err) return err @@ -2396,14 +2409,14 @@ func (a *Agent) addCheck(check *structs.HealthCheck, chkType *structs.CheckType, check.CheckID, checks.MinInterval) chkType.Interval = checks.MinInterval } - monitor := &checks.CheckMonitor{ - Notify: a.State, - CheckID: check.CheckID, - ScriptArgs: chkType.ScriptArgs, - Interval: chkType.Interval, - Timeout: chkType.Timeout, - Logger: a.logger, + Notify: a.State, + CheckID: check.CheckID, + ScriptArgs: chkType.ScriptArgs, + Interval: chkType.Interval, + Timeout: chkType.Timeout, + Logger: a.logger, + OutputMaxSize: maxOutputSize, } monitor.Start() a.checkMonitors[check.CheckID] = monitor @@ -2878,7 +2891,7 @@ func (a *Agent) updateTTLCheck(checkID types.CheckID, status, output string) err } // Set the status through CheckTTL to reset the TTL. - check.SetStatus(status, output) + outputTruncated := check.SetStatus(status, output) // We don't write any files in dev mode so bail here. if a.config.DataDir == "" { @@ -2887,7 +2900,7 @@ func (a *Agent) updateTTLCheck(checkID types.CheckID, status, output string) err // Persist the state so the TTL check can come up in a good state after // an agent restart, especially with long TTL values. - if err := a.persistCheckState(check, status, output); err != nil { + if err := a.persistCheckState(check, status, outputTruncated); err != nil { return fmt.Errorf("failed persisting state for check %q: %s", checkID, err) } diff --git a/agent/agent_endpoint.go b/agent/agent_endpoint.go index 30cc271e9..6f60cb384 100644 --- a/agent/agent_endpoint.go +++ b/agent/agent_endpoint.go @@ -19,7 +19,6 @@ import ( "github.com/hashicorp/consul/acl" cachetype "github.com/hashicorp/consul/agent/cache-types" - "github.com/hashicorp/consul/agent/checks" "github.com/hashicorp/consul/agent/debug" "github.com/hashicorp/consul/agent/local" "github.com/hashicorp/consul/agent/structs" @@ -725,12 +724,6 @@ func (s *HTTPServer) AgentCheckUpdate(resp http.ResponseWriter, req *http.Reques return nil, nil } - total := len(update.Output) - if total > checks.BufSize { - update.Output = fmt.Sprintf("%s ... (captured %d of %d bytes)", - update.Output[:checks.BufSize], checks.BufSize, total) - } - checkID := types.CheckID(strings.TrimPrefix(req.URL.Path, "/v1/agent/check/update/")) // Get the provided token, if any, and vet against any ACL policies. diff --git a/agent/agent_endpoint_test.go b/agent/agent_endpoint_test.go index 1f03601f7..7ccb8134b 100644 --- a/agent/agent_endpoint_test.go +++ b/agent/agent_endpoint_test.go @@ -18,7 +18,6 @@ import ( "time" "github.com/hashicorp/consul/acl" - "github.com/hashicorp/consul/agent/checks" "github.com/hashicorp/consul/agent/config" "github.com/hashicorp/consul/agent/connect" "github.com/hashicorp/consul/agent/debug" @@ -2299,7 +2298,8 @@ func TestAgent_FailCheck_ACLDeny(t *testing.T) { func TestAgent_UpdateCheck(t *testing.T) { t.Parallel() - a := NewTestAgent(t, t.Name(), "") + maxChecksSize := 256 + a := NewTestAgent(t, t.Name(), fmt.Sprintf("check_output_max_size=%d", maxChecksSize)) defer a.Shutdown() testrpc.WaitForTestAgent(t, a.RPC, "dc1") @@ -2340,7 +2340,7 @@ func TestAgent_UpdateCheck(t *testing.T) { t.Run("log output limit", func(t *testing.T) { args := checkUpdate{ Status: api.HealthPassing, - Output: strings.Repeat("-= bad -=", 5*checks.BufSize), + Output: strings.Repeat("-= bad -=", 5*maxChecksSize), } req, _ := http.NewRequest("PUT", "/v1/agent/check/update/test", jsonReader(args)) resp := httptest.NewRecorder() @@ -2359,8 +2359,8 @@ func TestAgent_UpdateCheck(t *testing.T) { // rough check that the output buffer was cut down so this test // isn't super brittle. state := a.State.Checks()["test"] - if state.Status != api.HealthPassing || len(state.Output) > 2*checks.BufSize { - t.Fatalf("bad: %v", state) + if state.Status != api.HealthPassing || len(state.Output) > 2*maxChecksSize { + t.Fatalf("bad: %v, (len:=%d)", state, len(state.Output)) } }) diff --git a/agent/agent_test.go b/agent/agent_test.go index a4f227150..3cedfa0cf 100644 --- a/agent/agent_test.go +++ b/agent/agent_test.go @@ -1586,7 +1586,7 @@ func TestAgent_updateTTLCheck(t *testing.T) { t.Parallel() a := NewTestAgent(t, t.Name(), "") defer a.Shutdown() - + checkBufSize := 100 health := &structs.HealthCheck{ Node: "foo", CheckID: "mem", @@ -1594,7 +1594,8 @@ func TestAgent_updateTTLCheck(t *testing.T) { Status: api.HealthCritical, } chk := &structs.CheckType{ - TTL: 15 * time.Second, + TTL: 15 * time.Second, + OutputMaxSize: checkBufSize, } // Add check and update it. @@ -1614,6 +1615,19 @@ func TestAgent_updateTTLCheck(t *testing.T) { if status.Output != "foo" { t.Fatalf("bad: %v", status) } + + if err := a.updateTTLCheck("mem", api.HealthCritical, strings.Repeat("--bad-- ", 5*checkBufSize)); err != nil { + t.Fatalf("err: %v", err) + } + + // Ensure we have a check mapping. + status = a.State.Checks()["mem"] + if status.Status != api.HealthCritical { + t.Fatalf("bad: %v", status) + } + if len(status.Output) > checkBufSize*2 { + t.Fatalf("bad: %v", len(status.Output)) + } } func TestAgent_PersistService(t *testing.T) { diff --git a/agent/bindata_assetfs.go b/agent/bindata_assetfs.go index 5d4f2f129..3120010a1 100644 --- a/agent/bindata_assetfs.go +++ b/agent/bindata_assetfs.go @@ -121,7 +121,7 @@ func web_uiAssetsAndroidChrome192x192501b0811835ea92d42937aaf9edfbe08Png() (*ass return nil, err } - info := bindataFileInfo{name: "web_ui/assets/android-chrome-192x192-501b0811835ea92d42937aaf9edfbe08.png", size: 18250, mode: os.FileMode(420), modTime: time.Unix(1558556350, 0)} + info := bindataFileInfo{name: "web_ui/assets/android-chrome-192x192-501b0811835ea92d42937aaf9edfbe08.png", size: 18250, mode: os.FileMode(420), modTime: time.Unix(1559855141, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -141,7 +141,7 @@ func web_uiAssetsAndroidChrome512x512707625c5eb04f602ade1f89a8868a329Png() (*ass return nil, err } - info := bindataFileInfo{name: "web_ui/assets/android-chrome-512x512-707625c5eb04f602ade1f89a8868a329.png", size: 58433, mode: os.FileMode(420), modTime: time.Unix(1558556350, 0)} + info := bindataFileInfo{name: "web_ui/assets/android-chrome-512x512-707625c5eb04f602ade1f89a8868a329.png", size: 58433, mode: os.FileMode(420), modTime: time.Unix(1559855141, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -161,7 +161,7 @@ func web_uiAssetsAppleTouchIcon114x11449e20f98710f64b0cae7545628a94496Png() (*as return nil, err } - info := bindataFileInfo{name: "web_ui/assets/apple-touch-icon-114x114-49e20f98710f64b0cae7545628a94496.png", size: 15576, mode: os.FileMode(420), modTime: time.Unix(1558556350, 0)} + info := bindataFileInfo{name: "web_ui/assets/apple-touch-icon-114x114-49e20f98710f64b0cae7545628a94496.png", size: 15576, mode: os.FileMode(420), modTime: time.Unix(1559855141, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -181,7 +181,7 @@ func web_uiAssetsAppleTouchIcon120x120C9cc4fc809a6cbff9b9c261c70309819Png() (*as return nil, err } - info := bindataFileInfo{name: "web_ui/assets/apple-touch-icon-120x120-c9cc4fc809a6cbff9b9c261c70309819.png", size: 16251, mode: os.FileMode(420), modTime: time.Unix(1558556350, 0)} + info := bindataFileInfo{name: "web_ui/assets/apple-touch-icon-120x120-c9cc4fc809a6cbff9b9c261c70309819.png", size: 16251, mode: os.FileMode(420), modTime: time.Unix(1559855141, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -201,7 +201,7 @@ func web_uiAssetsAppleTouchIcon144x144Ac561ffa84c7e8ce1fe68d70f1c16d1dPng() (*as return nil, err } - info := bindataFileInfo{name: "web_ui/assets/apple-touch-icon-144x144-ac561ffa84c7e8ce1fe68d70f1c16d1d.png", size: 20027, mode: os.FileMode(420), modTime: time.Unix(1558556350, 0)} + info := bindataFileInfo{name: "web_ui/assets/apple-touch-icon-144x144-ac561ffa84c7e8ce1fe68d70f1c16d1d.png", size: 20027, mode: os.FileMode(420), modTime: time.Unix(1559855141, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -221,7 +221,7 @@ func web_uiAssetsAppleTouchIcon152x15208c9aa1c11a83650b824e3549b33a832Png() (*as return nil, err } - info := bindataFileInfo{name: "web_ui/assets/apple-touch-icon-152x152-08c9aa1c11a83650b824e3549b33a832.png", size: 23769, mode: os.FileMode(420), modTime: time.Unix(1558556350, 0)} + info := bindataFileInfo{name: "web_ui/assets/apple-touch-icon-152x152-08c9aa1c11a83650b824e3549b33a832.png", size: 23769, mode: os.FileMode(420), modTime: time.Unix(1559855141, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -241,7 +241,7 @@ func web_uiAssetsAppleTouchIcon57x57Ae96d6d27e61e25514af459bc8b20960Png() (*asse return nil, err } - info := bindataFileInfo{name: "web_ui/assets/apple-touch-icon-57x57-ae96d6d27e61e25514af459bc8b20960.png", size: 5158, mode: os.FileMode(420), modTime: time.Unix(1558556350, 0)} + info := bindataFileInfo{name: "web_ui/assets/apple-touch-icon-57x57-ae96d6d27e61e25514af459bc8b20960.png", size: 5158, mode: os.FileMode(420), modTime: time.Unix(1559855141, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -261,7 +261,7 @@ func web_uiAssetsAppleTouchIcon60x60522fca33a44f77c679561313def843b9Png() (*asse return nil, err } - info := bindataFileInfo{name: "web_ui/assets/apple-touch-icon-60x60-522fca33a44f77c679561313def843b9.png", size: 5522, mode: os.FileMode(420), modTime: time.Unix(1558556350, 0)} + info := bindataFileInfo{name: "web_ui/assets/apple-touch-icon-60x60-522fca33a44f77c679561313def843b9.png", size: 5522, mode: os.FileMode(420), modTime: time.Unix(1559855141, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -281,7 +281,7 @@ func web_uiAssetsAppleTouchIcon72x72Da5dd17cb4f094262b19223464fc9541Png() (*asse return nil, err } - info := bindataFileInfo{name: "web_ui/assets/apple-touch-icon-72x72-da5dd17cb4f094262b19223464fc9541.png", size: 7289, mode: os.FileMode(420), modTime: time.Unix(1558556350, 0)} + info := bindataFileInfo{name: "web_ui/assets/apple-touch-icon-72x72-da5dd17cb4f094262b19223464fc9541.png", size: 7289, mode: os.FileMode(420), modTime: time.Unix(1559855141, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -301,7 +301,7 @@ func web_uiAssetsAppleTouchIcon76x76C5fff53d5f3e96dbd2fe49c5cc472022Png() (*asse return nil, err } - info := bindataFileInfo{name: "web_ui/assets/apple-touch-icon-76x76-c5fff53d5f3e96dbd2fe49c5cc472022.png", size: 8031, mode: os.FileMode(420), modTime: time.Unix(1558556350, 0)} + info := bindataFileInfo{name: "web_ui/assets/apple-touch-icon-76x76-c5fff53d5f3e96dbd2fe49c5cc472022.png", size: 8031, mode: os.FileMode(420), modTime: time.Unix(1559855141, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -321,7 +321,7 @@ func web_uiAssetsAppleTouchIconD2b583b1104a1e6810fb3984f8f132aePng() (*asset, er return nil, err } - info := bindataFileInfo{name: "web_ui/assets/apple-touch-icon-d2b583b1104a1e6810fb3984f8f132ae.png", size: 8285, mode: os.FileMode(420), modTime: time.Unix(1558556350, 0)} + info := bindataFileInfo{name: "web_ui/assets/apple-touch-icon-d2b583b1104a1e6810fb3984f8f132ae.png", size: 8285, mode: os.FileMode(420), modTime: time.Unix(1559855141, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -341,7 +341,7 @@ func web_uiAssetsAutoImportFastbootD41d8cd98f00b204e9800998ecf8427eJs() (*asset, return nil, err } - info := bindataFileInfo{name: "web_ui/assets/auto-import-fastboot-d41d8cd98f00b204e9800998ecf8427e.js", size: 0, mode: os.FileMode(420), modTime: time.Unix(1558556350, 0)} + info := bindataFileInfo{name: "web_ui/assets/auto-import-fastboot-d41d8cd98f00b204e9800998ecf8427e.js", size: 0, mode: os.FileMode(420), modTime: time.Unix(1559855141, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -361,7 +361,7 @@ func web_uiAssetsCodemirrorModeJavascriptJavascriptA4c0d68244e6e74f9225801e5f9d7 return nil, err } - info := bindataFileInfo{name: "web_ui/assets/codemirror/mode/javascript/javascript-a4c0d68244e6e74f9225801e5f9d724e.js", size: 21475, mode: os.FileMode(420), modTime: time.Unix(1558556350, 0)} + info := bindataFileInfo{name: "web_ui/assets/codemirror/mode/javascript/javascript-a4c0d68244e6e74f9225801e5f9d724e.js", size: 21475, mode: os.FileMode(420), modTime: time.Unix(1559855141, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -381,7 +381,7 @@ func web_uiAssetsCodemirrorModeRubyRuby61421add5f64c0fc261fe6049c3bd5d7Js() (*as return nil, err } - info := bindataFileInfo{name: "web_ui/assets/codemirror/mode/ruby/ruby-61421add5f64c0fc261fe6049c3bd5d7.js", size: 5265, mode: os.FileMode(420), modTime: time.Unix(1558556350, 0)} + info := bindataFileInfo{name: "web_ui/assets/codemirror/mode/ruby/ruby-61421add5f64c0fc261fe6049c3bd5d7.js", size: 5265, mode: os.FileMode(420), modTime: time.Unix(1559855141, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -401,7 +401,7 @@ func web_uiAssetsCodemirrorModeYamlYaml48498cacbe2464bb47bdb3732d4e25cbJs() (*as return nil, err } - info := bindataFileInfo{name: "web_ui/assets/codemirror/mode/yaml/yaml-48498cacbe2464bb47bdb3732d4e25cb.js", size: 43950, mode: os.FileMode(420), modTime: time.Unix(1558556350, 0)} + info := bindataFileInfo{name: "web_ui/assets/codemirror/mode/yaml/yaml-48498cacbe2464bb47bdb3732d4e25cb.js", size: 43950, mode: os.FileMode(420), modTime: time.Unix(1559855141, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -421,7 +421,7 @@ func web_uiAssetsConsulLogo707625c5eb04f602ade1f89a8868a329Png() (*asset, error) return nil, err } - info := bindataFileInfo{name: "web_ui/assets/consul-logo-707625c5eb04f602ade1f89a8868a329.png", size: 58433, mode: os.FileMode(420), modTime: time.Unix(1558556350, 0)} + info := bindataFileInfo{name: "web_ui/assets/consul-logo-707625c5eb04f602ade1f89a8868a329.png", size: 58433, mode: os.FileMode(420), modTime: time.Unix(1559855141, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -441,7 +441,7 @@ func web_uiAssetsConsulUi0cc7f4e2a5bcf306897a58923befa74cJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "web_ui/assets/consul-ui-0cc7f4e2a5bcf306897a58923befa74c.js", size: 481716, mode: os.FileMode(420), modTime: time.Unix(1558556350, 0)} + info := bindataFileInfo{name: "web_ui/assets/consul-ui-0cc7f4e2a5bcf306897a58923befa74c.js", size: 481716, mode: os.FileMode(420), modTime: time.Unix(1559855141, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -461,7 +461,7 @@ func web_uiAssetsConsulUiBfc60df255fe6a60a2b070dd0e3afa99Css() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "web_ui/assets/consul-ui-bfc60df255fe6a60a2b070dd0e3afa99.css", size: 100495, mode: os.FileMode(420), modTime: time.Unix(1558556350, 0)} + info := bindataFileInfo{name: "web_ui/assets/consul-ui-bfc60df255fe6a60a2b070dd0e3afa99.css", size: 100495, mode: os.FileMode(420), modTime: time.Unix(1559855141, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -481,7 +481,7 @@ func web_uiAssetsEncoding5ed8e95353b97ff5dd41bf66212d118eJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "web_ui/assets/encoding-5ed8e95353b97ff5dd41bf66212d118e.js", size: 18533, mode: os.FileMode(420), modTime: time.Unix(1558556350, 0)} + info := bindataFileInfo{name: "web_ui/assets/encoding-5ed8e95353b97ff5dd41bf66212d118e.js", size: 18533, mode: os.FileMode(420), modTime: time.Unix(1559855141, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -501,7 +501,7 @@ func web_uiAssetsEncodingIndexes75eea16b259716db4fd162ee283d2ae5Js() (*asset, er return nil, err } - info := bindataFileInfo{name: "web_ui/assets/encoding-indexes-75eea16b259716db4fd162ee283d2ae5.js", size: 529734, mode: os.FileMode(420), modTime: time.Unix(1558556350, 0)} + info := bindataFileInfo{name: "web_ui/assets/encoding-indexes-75eea16b259716db4fd162ee283d2ae5.js", size: 529734, mode: os.FileMode(420), modTime: time.Unix(1559855141, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -521,7 +521,7 @@ func web_uiAssetsFavicon12808e1368e84f412f6ad30279d849b1df9Png() (*asset, error) return nil, err } - info := bindataFileInfo{name: "web_ui/assets/favicon-128-08e1368e84f412f6ad30279d849b1df9.png", size: 11154, mode: os.FileMode(420), modTime: time.Unix(1558556350, 0)} + info := bindataFileInfo{name: "web_ui/assets/favicon-128-08e1368e84f412f6ad30279d849b1df9.png", size: 11154, mode: os.FileMode(420), modTime: time.Unix(1559855141, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -541,7 +541,7 @@ func web_uiAssetsFavicon16x16672c31374646b24b235b9511857cdadePng() (*asset, erro return nil, err } - info := bindataFileInfo{name: "web_ui/assets/favicon-16x16-672c31374646b24b235b9511857cdade.png", size: 821, mode: os.FileMode(420), modTime: time.Unix(1558556350, 0)} + info := bindataFileInfo{name: "web_ui/assets/favicon-16x16-672c31374646b24b235b9511857cdade.png", size: 821, mode: os.FileMode(420), modTime: time.Unix(1559855141, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -561,7 +561,7 @@ func web_uiAssetsFavicon196x19657be5a82d3da06c261f9e4eb972a8a3aPng() (*asset, er return nil, err } - info := bindataFileInfo{name: "web_ui/assets/favicon-196x196-57be5a82d3da06c261f9e4eb972a8a3a.png", size: 37174, mode: os.FileMode(420), modTime: time.Unix(1558556350, 0)} + info := bindataFileInfo{name: "web_ui/assets/favicon-196x196-57be5a82d3da06c261f9e4eb972a8a3a.png", size: 37174, mode: os.FileMode(420), modTime: time.Unix(1559855141, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -581,7 +581,7 @@ func web_uiAssetsFavicon32x32646753a205c6a6db7f93d0d1ba30bd93Png() (*asset, erro return nil, err } - info := bindataFileInfo{name: "web_ui/assets/favicon-32x32-646753a205c6a6db7f93d0d1ba30bd93.png", size: 2075, mode: os.FileMode(420), modTime: time.Unix(1558556350, 0)} + info := bindataFileInfo{name: "web_ui/assets/favicon-32x32-646753a205c6a6db7f93d0d1ba30bd93.png", size: 2075, mode: os.FileMode(420), modTime: time.Unix(1559855141, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -601,7 +601,7 @@ func web_uiAssetsFavicon672c31374646b24b235b9511857cdadePng() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "web_ui/assets/favicon-672c31374646b24b235b9511857cdade.png", size: 821, mode: os.FileMode(420), modTime: time.Unix(1558556350, 0)} + info := bindataFileInfo{name: "web_ui/assets/favicon-672c31374646b24b235b9511857cdade.png", size: 821, mode: os.FileMode(420), modTime: time.Unix(1559855141, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -621,7 +621,7 @@ func web_uiAssetsFavicon96x966f8f8393df02b51582417746da41b274Png() (*asset, erro return nil, err } - info := bindataFileInfo{name: "web_ui/assets/favicon-96x96-6f8f8393df02b51582417746da41b274.png", size: 10171, mode: os.FileMode(420), modTime: time.Unix(1558556350, 0)} + info := bindataFileInfo{name: "web_ui/assets/favicon-96x96-6f8f8393df02b51582417746da41b274.png", size: 10171, mode: os.FileMode(420), modTime: time.Unix(1559855141, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -641,7 +641,7 @@ func web_uiAssetsFaviconIco() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "web_ui/assets/favicon.ico", size: 34494, mode: os.FileMode(420), modTime: time.Unix(1558556350, 0)} + info := bindataFileInfo{name: "web_ui/assets/favicon.ico", size: 34494, mode: os.FileMode(420), modTime: time.Unix(1559855141, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -661,7 +661,7 @@ func web_uiAssetsLoadingCylonPinkSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "web_ui/assets/loading-cylon-pink.svg", size: 983, mode: os.FileMode(420), modTime: time.Unix(1558556350, 0)} + info := bindataFileInfo{name: "web_ui/assets/loading-cylon-pink.svg", size: 983, mode: os.FileMode(420), modTime: time.Unix(1559855141, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -681,7 +681,7 @@ func web_uiAssetsMstile144x144Ac561ffa84c7e8ce1fe68d70f1c16d1dPng() (*asset, err return nil, err } - info := bindataFileInfo{name: "web_ui/assets/mstile-144x144-ac561ffa84c7e8ce1fe68d70f1c16d1d.png", size: 20027, mode: os.FileMode(420), modTime: time.Unix(1558556350, 0)} + info := bindataFileInfo{name: "web_ui/assets/mstile-144x144-ac561ffa84c7e8ce1fe68d70f1c16d1d.png", size: 20027, mode: os.FileMode(420), modTime: time.Unix(1559855141, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -701,7 +701,7 @@ func web_uiAssetsMstile150x1506b13ab220a09a9e72328a3b05d5b9eecPng() (*asset, err return nil, err } - info := bindataFileInfo{name: "web_ui/assets/mstile-150x150-6b13ab220a09a9e72328a3b05d5b9eec.png", size: 64646, mode: os.FileMode(420), modTime: time.Unix(1558556350, 0)} + info := bindataFileInfo{name: "web_ui/assets/mstile-150x150-6b13ab220a09a9e72328a3b05d5b9eec.png", size: 64646, mode: os.FileMode(420), modTime: time.Unix(1559855141, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -721,7 +721,7 @@ func web_uiAssetsMstile310x150Ccc673174b188a92f1e78bc25aa6f3f8Png() (*asset, err return nil, err } - info := bindataFileInfo{name: "web_ui/assets/mstile-310x150-ccc673174b188a92f1e78bc25aa6f3f8.png", size: 112362, mode: os.FileMode(420), modTime: time.Unix(1558556350, 0)} + info := bindataFileInfo{name: "web_ui/assets/mstile-310x150-ccc673174b188a92f1e78bc25aa6f3f8.png", size: 112362, mode: os.FileMode(420), modTime: time.Unix(1559855141, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -741,7 +741,7 @@ func web_uiAssetsMstile310x31049242d1935854126c10457d1cdb1762bPng() (*asset, err return nil, err } - info := bindataFileInfo{name: "web_ui/assets/mstile-310x310-49242d1935854126c10457d1cdb1762b.png", size: 201893, mode: os.FileMode(420), modTime: time.Unix(1558556350, 0)} + info := bindataFileInfo{name: "web_ui/assets/mstile-310x310-49242d1935854126c10457d1cdb1762b.png", size: 201893, mode: os.FileMode(420), modTime: time.Unix(1559855141, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -761,7 +761,7 @@ func web_uiAssetsMstile70x7008e1368e84f412f6ad30279d849b1df9Png() (*asset, error return nil, err } - info := bindataFileInfo{name: "web_ui/assets/mstile-70x70-08e1368e84f412f6ad30279d849b1df9.png", size: 11154, mode: os.FileMode(420), modTime: time.Unix(1558556350, 0)} + info := bindataFileInfo{name: "web_ui/assets/mstile-70x70-08e1368e84f412f6ad30279d849b1df9.png", size: 11154, mode: os.FileMode(420), modTime: time.Unix(1559855141, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -781,7 +781,7 @@ func web_uiAssetsSafariPinnedTabSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "web_ui/assets/safari-pinned-tab.svg", size: 3798, mode: os.FileMode(420), modTime: time.Unix(1558556350, 0)} + info := bindataFileInfo{name: "web_ui/assets/safari-pinned-tab.svg", size: 3798, mode: os.FileMode(420), modTime: time.Unix(1559855141, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -801,7 +801,7 @@ func web_uiAssetsVendor9ea7d400c0cec7682e8871df14073823Css() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "web_ui/assets/vendor-9ea7d400c0cec7682e8871df14073823.css", size: 8078, mode: os.FileMode(420), modTime: time.Unix(1558556350, 0)} + info := bindataFileInfo{name: "web_ui/assets/vendor-9ea7d400c0cec7682e8871df14073823.css", size: 8078, mode: os.FileMode(420), modTime: time.Unix(1559855141, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -821,12 +821,12 @@ func web_uiAssetsVendorEb72c0b1a44ba20937ce35fd2ad2e8daJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "web_ui/assets/vendor-eb72c0b1a44ba20937ce35fd2ad2e8da.js", size: 1336336, mode: os.FileMode(420), modTime: time.Unix(1558556350, 0)} + info := bindataFileInfo{name: "web_ui/assets/vendor-eb72c0b1a44ba20937ce35fd2ad2e8da.js", size: 1336336, mode: os.FileMode(420), modTime: time.Unix(1559855141, 0)} a := &asset{bytes: bytes, info: info} return a, nil } -var _web_uiIndexHtml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xa4\x78\x6d\x6f\xdb\x48\x92\xff\xfb\x7c\x8a\x1e\xfd\xff\x41\x32\xc0\x76\xa9\xbb\xfa\x39\x63\x19\xf0\x38\x9e\x19\x1f\xe2\x24\x70\x9c\x60\xe7\x55\xc0\x87\x96\xc5\x1c\x45\xea\x48\x4a\x8e\x73\x98\xef\x7e\xa8\x26\x25\xcb\x89\x77\x17\xb7\xf7\xc2\xad\x62\x57\xf5\xaf\x9e\xab\x49\x9f\xfc\xf4\xfa\xdd\xf9\xcd\x9f\xef\x2f\xd8\x6a\x58\xd7\xa7\xcf\x4e\xe8\x87\x15\x75\xd6\xf7\x8b\x59\x5c\xe7\xb1\xe3\x75\x9b\x95\x55\x73\x3b\x3b\x7d\xc6\xd8\xc9\x2a\x66\x25\x11\x8c\x9d\xac\xe3\x90\xb1\x62\x95\x75\x7d\x1c\x16\xb3\xed\xb0\xe4\x7e\x76\xcc\x5a\x0d\xc3\x86\xc7\xff\xda\x56\xbb\xc5\xec\xef\xfc\xe3\x19\x3f\x6f\xd7\x9b\x6c\xa8\xf2\x3a\xce\x58\xd1\x36\x43\x6c\x86\xc5\xec\xf2\x62\x11\xcb\xdb\xb8\x3f\x39\x54\x43\x1d\x4f\xcf\xdb\xa6\xdf\xd6\x2c\xbf\x67\x7f\x64\xfd\xaa\x3a\x6f\xbb\xcd\xc9\x7c\x64\x1d\x29\x68\xb2\x75\x5c\xcc\xca\xd8\x17\x5d\xb5\x19\xaa\xb6\x39\x82\x9d\xfd\x28\xb8\xab\xe2\xdd\xa6\xed\x86\x23\xa9\xbb\xaa\x1c\x56\x8b\x32\xee\xaa\x22\xf2\xf4\xf0\x37\x56\x35\xd5\x50\x65\x35\xef\x8b\xac\x8e\x0b\x39\x3b\x7d\x96\x90\x9e\x1d\x43\x15\xc9\x3e\xbe\xad\xe6\x45\xdb\x2c\xab\xdb\x79\x6c\x76\x55\xd7\x36\xeb\xd8\x1c\xc3\x3f\x77\xbf\x3e\x47\x5c\xb7\xe5\xb6\x8e\xef\xbb\xb8\xac\xbe\x3e\x47\x7c\xae\xce\x9e\x23\x1e\x10\x68\x07\xcf\x9f\x23\x1e\x41\x1c\xa4\x36\x5d\x5b\x6e\x0b\x72\xed\x20\xd6\xb5\xed\xf0\xf1\xfa\xcd\x41\x64\xbe\xad\xe6\x07\x66\xdd\x16\x19\x49\xdf\xdc\x6f\xe2\x41\x22\xdb\x0e\xed\x41\xe2\x82\x52\x7a\xf1\xf6\xd3\xc4\x4d\x06\xfe\x76\x71\x76\xf3\xf1\xfa\xe2\xc3\xf1\x5e\xd9\xf3\x6a\xbd\xe9\xda\x5d\x2c\x79\xf6\x25\x9b\x0c\x1f\xba\x6d\x7c\xee\x5e\x4f\x50\x7f\xbf\xb9\x78\xfb\xfa\xf3\xfb\xeb\x77\x37\xef\xa8\x82\x1e\x9d\x7f\x9d\x0d\x93\x05\xcb\xac\xee\xd3\xa1\xfd\xb9\xb3\xf7\xef\x8f\x25\x29\xa2\xff\x24\x2c\xbb\xd8\xf5\x93\xff\x49\x02\x01\x41\xd0\xd3\x1e\xae\x8b\x7d\xf5\x2d\x7e\x88\x1d\x25\xf1\x75\x5c\x66\xdb\x7a\xe8\x8f\x15\x54\xcd\x97\x98\x62\xf8\x5b\x56\x0c\x6d\x57\xc5\x3d\xd7\x10\x97\xaa\xe2\xa0\x8c\x32\xd7\xb5\x75\x1d\xbb\xa3\xad\xf5\xa6\x6d\xa6\xa4\x98\x07\x2f\xce\xdf\xbd\xfd\xf0\xf1\xcd\xe7\x8f\x97\x9f\x5f\x5f\x7e\x38\xfb\xf5\xcd\xc5\xe7\xeb\x8b\xb3\x37\x37\x97\x57\x17\xc7\x6e\x1f\x8b\xfe\x7e\x79\xf3\xf9\xc3\x1f\x67\x07\x4f\x96\x4b\x1f\x42\x69\x7d\x38\xe8\x9a\x04\x3f\x5d\x5c\x7f\xb8\x7c\xf7\xf6\x20\x28\xc1\x80\xfc\x5e\xe8\xd7\xcb\xb7\x67\xd7\x7f\x7e\xa6\xc0\x1f\x04\xdb\xbe\xff\x5e\xec\xf5\xbb\xf3\x8f\x57\x17\x6f\x6f\xce\x6e\x2e\xdf\xbd\xfd\x7c\x5c\x38\xd4\x9e\xfd\x73\x75\x36\x9f\xdf\xdd\xdd\xc1\x18\x78\xa8\xda\x79\xd9\x16\x3f\xa0\x9c\xbf\x7b\xff\xe7\xf5\xe5\xef\x7f\xdc\xfc\x13\x84\x15\x75\x6a\xd1\x76\x1b\x28\xda\xf5\x3f\x06\xf8\xf3\xe2\xec\xfa\x21\x99\x42\x3e\x78\x9f\x77\xed\x5d\x1f\xbb\x6a\x79\x7f\x9c\xbd\x21\xf6\xfb\x74\x3e\xaa\xbe\xf8\x95\x7a\xf9\x6c\xb3\xa9\xab\xb1\xe6\x7f\xaf\xdb\x3c\xab\x1f\xd7\xdc\x8c\xcd\x4f\x9f\x9d\xfc\xc4\x39\x7b\x1c\xda\x57\x2c\xc5\x94\x71\x3e\x8d\x89\xba\x6a\xfe\x93\x75\xb1\x5e\xcc\xaa\x82\xe6\xc8\x70\xbf\x89\x8b\x59\xb5\xce\x6e\xe3\x7c\xd3\xdc\xce\xd8\xaa\x8b\xcb\xc5\x8c\x9a\x2d\xeb\xfb\x38\xf4\xf3\x65\xb6\x23\x51\xae\xf0\xab\x42\x6e\xb5\x75\x46\x65\x28\x4c\x61\x33\x5b\xe6\x6e\x19\x54\x29\x4a\x99\x67\x4a\xe4\x65\x50\x90\x40\xa8\x50\xfb\xc5\x2c\x1d\x99\xfd\x9f\x35\x4b\xfb\x55\x5a\x6e\x1d\x16\x4a\x2a\xa7\xad\xb6\x39\xea\x1c\x95\xc9\x83\x91\xd2\x1b\x57\x94\x59\x19\x1f\x69\x4e\x47\xf6\x13\x6d\x54\x5d\x35\x43\xbc\xed\xaa\xe1\x7e\x31\x9b\x8d\x76\xf4\xc3\x7d\x1d\xfb\x55\x8c\xc3\x13\xca\x77\xb1\x29\xdb\x8e\x87\x98\xb9\x52\x0b\x51\x88\x22\x16\xce\x7a\x8c\xde\x3b\x59\x2e\xa5\x16\x4e\x79\x54\x50\xf4\xfd\x23\x0f\xff\x97\x6a\x0e\x73\x80\xe7\xcb\xc2\x8a\x72\x89\xc6\x2c\xa3\xcd\xac\xc8\x30\x17\x4e\x94\xa5\x88\x2a\x5b\x66\x21\x4c\x9a\xc6\x19\xcd\xd8\xc9\x7c\x7f\x43\x9d\xe4\x6d\x79\x3f\x99\xd0\xb4\xe3\x1d\x31\x3e\xa6\xad\xb2\xda\xb1\x64\xc2\x62\xb6\xce\xba\xdb\xaa\x79\xc5\x04\xa3\x51\xf9\xcb\xec\x41\x2a\x49\xae\xf0\xf4\x3f\xb2\x5d\xf6\x21\x21\xb0\x6b\xba\xd1\xba\x58\x9e\xcc\x57\xf8\x9d\xe0\xe6\xf4\x7d\x1d\xb3\x3e\xb2\xd8\x64\x79\x1d\xd9\xd1\xa9\xaa\x61\xf7\xed\xb6\x63\x77\x31\x67\x53\x9d\xb3\xa1\x65\xdb\x3e\xb2\xe9\xae\xfb\x78\x09\x27\xf3\xcd\x91\x81\xf3\xb2\xda\x4d\xe6\xcf\x1f\xdb\x7f\xd2\xef\x6e\xd9\x78\x75\xcd\xa4\xf5\x33\xb6\x8a\xd5\xed\x6a\x58\xcc\x8c\x9a\xb1\xaf\xeb\xba\xe9\x17\x33\xea\xcd\x57\x63\x63\xde\x29\x68\xbb\xdb\x39\x0a\x21\xe6\xfd\xee\x76\x76\x7a\x72\xcb\x96\x55\x5d\x2f\x66\xff\x2f\xc8\xf0\xdb\x99\x9f\xa5\x47\xde\x6d\x29\x18\x71\x17\x9b\xb6\x2c\x67\xa7\x27\x9b\x6c\x58\xb1\x72\x31\xbb\x42\x0b\xc2\x79\xa6\x10\x24\x66\x06\x8c\xb7\x6c\x5c\x05\x93\x4c\x12\xed\x1c\x37\x60\x42\x18\xe9\x69\x15\xc4\xe7\xfb\x1d\x7b\xa5\x1c\x08\x11\x18\x06\x50\xe8\x33\x04\x63\x59\x5a\x46\x14\x22\xf9\x7e\xd3\xc8\x69\x1d\x31\x0e\xb2\x57\xda\x42\x90\x96\x29\x09\xd6\x86\x1f\x30\x40\x18\xc9\x01\x65\xc1\x41\xa0\x07\x61\xed\x48\x48\xc5\x89\x05\x28\xaf\xb4\x06\xe3\x3d\x43\x03\xc2\x8e\x46\x18\x36\xae\x7b\x55\xd6\x21\x87\x10\x30\x59\xe0\xa7\x75\x64\x82\x14\xc8\x11\xbc\x1e\xcf\xe8\x69\x4d\x4c\xa6\xc1\x3a\x0b\xce\xea\x02\x84\x43\xf2\x11\x84\x27\x3b\x1d\x08\x74\x4c\x66\x08\xda\x2a\x36\xae\x23\x9e\x84\x80\x04\x25\x9c\xba\x32\x0a\x82\x42\xf2\x4c\x0b\x24\xc3\xb4\x63\xe3\xba\x37\x2c\x8c\xa2\x63\x80\xc2\xb4\xee\x99\xc2\x6a\x12\x19\xbd\x79\x7c\x94\x21\x04\xed\x49\xc6\xa5\xfd\x69\xd9\xf3\x24\x92\x93\x4e\x5a\xb0\x56\xd3\x5f\xe2\x08\x8a\x18\x20\xfa\x2b\x23\xc1\xd3\x19\x03\x52\xa8\x27\xd2\x26\x05\x61\x07\x6d\x0a\x10\x4a\x03\x4a\x0f\x42\x2b\xd0\x2a\x90\xdf\x60\x8d\x7f\xd2\x1b\xa9\xc8\x54\xf4\xee\x4a\x07\x08\x46\x33\x2d\x40\xca\x1f\x15\x80\x92\x9a\x4b\x10\xca\xa5\xac\x82\xa2\xa4\x4a\xf4\xe0\x50\x72\x62\xb2\xc4\xbc\xd2\x1e\x82\xd3\x4c\x5a\xf0\xe1\x09\x94\xe0\xb8\x02\xed\x5d\x01\x68\x35\x68\x6d\x41\x39\x43\xf1\x02\x25\x1d\x93\xa0\xdd\xe3\x72\x1a\xd3\x83\x9e\xcc\x16\xc2\xcf\xe6\x8f\xfb\xc1\xa0\x65\x06\xc1\x0a\x55\x70\xa9\x41\x05\xc5\x04\xa7\x3e\xb1\x5c\x4a\x30\xd6\x4d\x0f\x68\xc0\x2b\x7b\x0e\xda\x5a\x26\x11\x74\xf0\xf4\x23\x15\x25\x5b\xb2\x11\x88\xc8\x0c\x0d\x04\x65\xd9\xf4\x33\xe6\x46\xa6\xc3\xcc\x80\x12\xae\xe6\x0a\xa4\x75\x4c\x83\x94\xee\x0c\x05\x04\x8b\x6c\xfa\x11\xa3\xbc\x03\x25\x34\xf3\x80\xea\x5c\x0a\x90\x41\x33\x29\xc1\x49\xc5\x0c\x38\x26\x3d\x50\x77\x3a\xa9\x49\xab\xb3\x8a\xa2\x29\x35\x73\xe0\x2d\x32\x0d\xda\x93\x3a\x61\xe8\x8c\x09\x24\x6e\x94\xce\x9e\xd2\x83\x12\x7c\xa0\x04\xa2\xab\xc9\x26\x4f\x36\xa1\x4a\x1e\x78\xc9\xa6\x9f\x29\x82\x07\x0f\xe4\xb7\x47\x21\xb4\x92\x29\x01\xd2\x7c\x92\x8e\x8a\xb3\x10\x5c\x8f\x9a\xa8\xb9\xb8\x03\x6f\x58\x00\x1d\x26\x12\x41\x7b\x2a\x4b\x03\x42\x7b\x50\x68\x99\x03\x34\x08\x3e\x98\x9a\x83\xb1\xd4\x7c\x5a\xa9\x82\xac\xb2\x9a\x83\x16\x96\x6b\xb0\xde\x73\xb0\x41\x72\x0b\x46\x8d\x14\x95\x80\x65\xa4\xcc\x0a\xcf\x24\x20\x4e\xa4\xa6\x32\xde\x49\x01\x5a\x62\x21\xc8\x0c\x4f\x7c\x89\x38\xb2\xa8\xbb\x85\x9b\x68\x09\x5e\x53\x0b\x6b\x50\xd6\x71\x40\xaf\xd9\x41\x45\x0d\x66\x8c\xe7\x68\x0e\x0a\x0d\xc6\x71\x0d\xce\x7a\x32\x97\x1f\x0c\x3f\xb7\x2a\x0d\x12\xe5\x19\x05\xc3\x80\x94\xc8\xf6\x61\xf9\xb6\x56\x16\xbc\xa0\x4e\x2a\x48\x8f\xb0\x9e\x4b\xf0\x02\x29\x5f\x92\x7b\xd0\x41\x8d\xa4\x05\x1b\x98\x48\x3b\x9a\x2b\x70\x1a\x27\x9a\xb8\x3b\x6e\x40\x08\x4c\xd1\x15\x96\xca\xdc\x0b\x95\x38\xec\x41\x88\x8d\x10\x6c\x04\x25\x08\xc5\x1e\x14\xec\x08\x41\x7d\x5b\x73\x0d\xde\xd3\x94\x97\xd6\x14\x82\x3c\x53\x8e\xda\x52\x20\x05\x55\x5a\xae\xc0\x0a\x3b\xd2\xfd\xf8\x40\xf1\xf3\x13\x99\xf6\x77\x86\xa6\x62\x0a\x2f\x2a\xb2\x46\x08\x4c\x1c\xc7\x1e\xa4\x5c\x3f\x22\xd1\x61\xf6\x00\xea\xc8\x17\x85\xfe\xdb\x1a\x0d\xb8\x40\x5d\x64\xac\x5f\x8d\x66\x7d\x42\x95\x06\xaf\x20\x8b\x8c\xe3\xa0\x69\x64\x18\x4f\x28\xc6\xaa\x03\x8d\x42\x32\xc1\x15\x28\xc2\x50\x48\xd1\x49\x69\xb6\xd6\xef\x24\x79\xee\xf6\x80\xd2\x81\xf3\x66\xa5\xc0\xa1\xad\x53\x73\x90\x50\x81\xa0\x13\x34\x22\xdd\x71\x2a\xcd\xd4\xd4\x43\x72\x4f\x2b\x40\x6d\x52\x6d\x68\x41\xb3\x43\x49\x3f\xd1\x86\xc6\xe8\x8e\x86\x85\x71\xdf\xd6\xd2\x03\x5a\x6e\xc0\x39\x53\x08\xa6\xc0\x23\x99\x29\x2d\x32\x0b\xd2\x23\x77\x20\xcc\x9e\x46\x10\x5e\xa5\xaa\x0d\x48\xce\x79\xcf\x2d\xb8\x24\xaf\x42\x0d\x96\x82\xec\x3c\x16\x12\x2c\x0d\x5e\x4f\x71\xf3\x82\x3a\x47\x93\x56\x99\x88\xd4\x1a\xd4\xbe\xd4\x4b\x09\x64\x22\x25\x84\x34\xbd\x52\x37\x00\xd2\x86\x57\x14\x73\x61\x78\x9a\x70\x63\xf5\xa6\x84\xa4\x1c\x50\xf3\xeb\x89\xb2\x80\xa8\x52\x4c\x83\xa7\x84\x3b\x54\xe4\x94\x32\xd4\xa1\x01\x27\x3a\xd5\x5d\x0a\x8a\xb4\x16\x50\x27\xb3\x4c\x6a\x17\xb2\x84\x8a\x80\xee\x0f\x2e\xe9\x21\x35\x14\xa7\xe6\xb0\xd4\xdb\x81\x34\x59\x3f\x92\x34\x75\x34\x4d\x5a\xb2\x3c\x05\x62\x1c\x0d\x12\x9c\xa0\x4b\x9a\x12\xed\x40\x62\xda\xa0\x2b\xd2\xa5\x4b\x9a\xf4\x69\x1d\x68\xcc\x5b\xa2\x03\x65\x46\x06\x3b\x92\x96\xae\x84\x6f\xeb\x94\x18\x1a\xe6\x42\xa9\x55\xaa\x02\xaa\x09\xaf\x0a\x82\x15\xc6\x81\xd6\x32\x29\x20\x3f\x8d\x55\x07\x1a\x05\x8d\xc6\x54\x55\x1c\x9c\xa2\x79\x2b\x3c\xc5\xcb\x5a\xbf\x2f\x24\xc2\xf3\x3b\x19\xa8\xa9\x57\x7c\x2c\x2b\x72\x5d\x26\x31\x47\xa3\x42\x6b\x3d\x4e\x24\x03\x06\x43\xba\xea\xc7\x06\xc5\x89\xa6\xd2\x1a\x87\x97\x16\x8e\xa7\xd2\x9a\xe8\x54\x5a\x93\xaa\x6f\x57\x34\x92\x95\x03\x13\xd4\x58\xcd\xfe\x53\xa0\x50\xbf\xa1\xfd\xb0\x43\x4f\x9c\x34\x8d\xe7\xb7\xa7\x27\xf4\x46\xb8\x7f\xab\x1c\x5f\x55\xfb\xae\x78\xea\x8d\x3f\xe6\x0e\x0b\x91\xcb\x4c\xeb\x3c\x43\x11\x94\x2b\xa2\x32\xcb\x12\xb3\x12\xa3\x2f\x33\xf8\xd2\xcf\x08\xee\xd1\x7b\xea\xa3\x97\xee\x5d\xd6\xb1\x6c\xb3\x89\x4d\x39\xbd\x14\x2f\xd8\x72\xdb\xa4\x2f\xf3\x97\x7d\x57\xfc\xcc\xfe\xfb\xf0\xf2\x4b\xa2\xff\xbf\xdf\x4b\x95\x6d\xb1\x5d\xc7\x66\x80\xa2\x8b\xd9\x10\x2f\xea\x48\x4f\x2f\x5f\x8c\x02\x2f\x7e\xfe\xe5\x70\x6e\x3a\x03\x7d\x57\xb0\x05\x79\xf2\xc0\x3a\x80\xd0\x67\x01\x8c\x76\x9c\xaf\xaa\xba\x7c\x39\x1d\x3a\xc0\xfc\x35\xfd\x56\xcb\x97\x3f\xbd\x7c\x71\x13\xbf\x0e\xaf\x63\xd1\x96\xb1\x7b\x41\xaf\xf1\x77\x55\x53\xb6\x77\x3f\x1f\x5b\x7b\xec\xd4\xcb\x17\x47\xa1\x8b\x4d\xd1\x96\x55\x73\xcb\xab\xa6\x8c\x5f\x63\xcf\x9d\x89\x31\x93\x36\x47\x13\x9c\xb4\x65\xae\x97\xa5\xb4\x18\x23\x7a\x55\x62\x16\x0d\x7c\xe9\x8f\xdd\xf9\xd7\xc0\x26\x96\x3e\x06\xa3\x8c\xca\x83\x5b\x2e\x4d\x59\x6a\x99\x2f\xad\x45\x89\xa5\x94\x3e\x3e\x02\xfc\x6b\xfa\x9a\x78\x22\x47\x3f\x64\xfd\xe1\x03\x4c\x14\x85\x5b\xea\x88\x99\xc9\x8b\xa5\x12\xd6\x07\x97\x19\x1f\x50\xe5\x71\x99\x39\x5d\xfc\xeb\xc4\x9f\xb7\x65\xbc\xaa\xba\xae\xed\x60\xdd\x96\xf1\xe3\xf5\x1b\xb6\x38\x0a\x5f\x17\x37\x75\x56\xc4\x57\x0f\xd5\xd0\xfc\x8d\x91\xe0\x71\x8c\x19\xeb\xef\xaa\xa1\x58\xbd\xfc\x91\xc1\x58\x41\x1f\x5d\x2f\xbe\x64\xbb\x6c\x2a\x89\x57\x8f\xd8\xa4\x62\xd8\x76\x0d\x7b\xf1\xc8\xc1\x32\xae\x93\x51\x73\x82\x9c\x3f\x9c\x3e\x22\x79\xa6\x0b\x51\x5a\x8f\x5a\x47\x1b\x9d\x5e\x06\x44\xe3\x85\x8c\x66\x19\x4a\x87\x3a\xc5\xf7\x97\x27\x6c\xe9\xb6\xf9\xfd\xbf\x63\x05\x9d\x4b\x0b\xb7\x52\xa3\xcc\xca\xd2\x2c\xe9\x62\x5b\x16\x68\xe5\x32\x5a\xa1\x43\xa1\xf2\xd2\x94\xee\x1f\x69\xbe\xcf\xd6\xf5\xbf\xa3\x99\xce\xa5\x85\x6b\xaf\x83\x2f\xb2\x22\x8f\xa8\xad\xce\x73\xed\xf2\x32\x57\x4e\x61\xa9\x23\x9a\x22\xff\x5e\xf3\x5f\xcf\xbe\xa7\xfe\xfa\xe5\xa9\x4a\xa3\x2f\xee\xaa\xdc\xff\x57\x39\xcf\xfa\xaa\xe0\x65\xd7\x6e\xca\xf6\xae\xe1\x77\x6d\xb7\x5e\xb5\x75\xa4\x52\x9a\x3e\x7c\x4f\xe6\xe3\x17\xfc\xc9\x7c\xfc\xd7\xf4\xff\x04\x00\x00\xff\xff\xe7\xa4\xc4\x1b\xab\x16\x00\x00") +var _web_uiIndexHtml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xac\x58\x6d\x6f\xdb\xc6\x93\x7f\x9f\x4f\xb1\xd5\x9d\x91\x14\xf8\xef\x68\x77\xf6\xb9\xb5\x0c\xb8\x8e\xdb\xfa\x10\x27\x81\xe3\x04\xff\xbe\x0a\xf8\xb0\xb2\x98\xa3\x48\x1d\x49\xcb\x71\x8a\x7c\xf7\xc3\x2c\x29\x59\xce\x43\x8b\x43\xef\x85\x57\xc3\x9d\xd9\xdf\x3c\xcf\x92\x3e\xfe\xe1\xf9\xab\xb3\xeb\x3f\x5e\x9f\xb3\xd5\xb0\xae\x4f\x9e\x1c\xd3\x0f\x2b\xea\xac\xef\x17\xb3\xb8\xce\x63\xc7\xeb\x36\x2b\xab\xe6\x66\x76\xf2\x84\xb1\xe3\x55\xcc\x4a\x22\x18\x3b\x5e\xc7\x21\x63\xc5\x2a\xeb\xfa\x38\x2c\x66\xb7\xc3\x92\xfb\xd9\x21\x6b\x35\x0c\x1b\x1e\xff\xe7\xb6\xda\x2e\x66\xff\xe6\x6f\x4f\xf9\x59\xbb\xde\x64\x43\x95\xd7\x71\xc6\x8a\xb6\x19\x62\x33\x2c\x66\x17\xe7\x8b\x58\xde\xc4\xdd\xc9\xa1\x1a\xea\x78\x72\xd6\x36\xfd\x6d\xcd\xf2\x7b\xf6\x7b\xd6\xaf\xaa\xb3\xb6\xdb\x1c\xcf\x47\xd6\x81\x82\x26\x5b\xc7\xc5\xac\x8c\x7d\xd1\x55\x9b\xa1\x6a\x9b\x03\xd8\xd9\xd7\x82\xdb\x2a\xde\x6d\xda\x6e\x38\x90\xba\xab\xca\x61\xb5\x28\xe3\xb6\x2a\x22\x4f\x0f\xff\x62\x55\x53\x0d\x55\x56\xf3\xbe\xc8\xea\xb8\x90\xb3\x93\x27\x09\xe9\xc9\x21\x54\x91\xec\xe3\xb7\xd5\xbc\x68\x9b\x65\x75\x33\x8f\xcd\xb6\xea\xda\x66\x1d\x9b\x43\xf8\x23\xf7\xcb\x11\xe2\xba\x2d\x6f\xeb\xf8\xba\x8b\xcb\xea\xe3\x11\xe2\x91\x3a\x3d\x42\xdc\x23\xd0\x0e\x9e\x1d\x21\x1e\x40\xec\xa5\x36\x5d\x5b\xde\x16\xe4\xda\x5e\xac\x6b\xdb\xe1\xed\xd5\x8b\xbd\xc8\xfc\x08\x0d\xa9\x31\xee\x17\x38\x1b\x15\xbf\xce\x86\x15\x6d\x3c\x4f\xcb\x7c\x7f\xb4\x6e\x8b\x8c\xb0\xae\xef\x37\x71\x7f\x3e\xbb\x1d\xda\xbd\xc4\x39\x25\xfc\xfc\xe5\xbb\x89\x9b\xcc\xff\xf5\xfc\xf4\xfa\xed\xd5\xf9\x9b\xc3\xbd\xb2\xe7\xd5\x7a\xd3\xb5\xdb\x58\xf2\xec\x43\x36\xb9\x35\x74\xb7\xf1\x88\xb4\x26\xa8\x7f\x5f\x9f\xbf\x7c\xfe\xfe\xf5\xd5\xab\xeb\x57\x54\x5f\x8f\xce\x3f\xcf\x86\xc9\x82\x65\x56\xf7\xe9\xd0\xee\xdc\xe9\xeb\xd7\x87\x92\x14\xef\xbf\x08\xda\x36\x76\xfd\x14\x9d\x24\x81\x80\x20\xe8\x69\x07\xd7\xc5\xbe\xfa\x14\xdf\xc4\x8e\x52\xfc\x3c\x2e\xb3\xdb\x7a\xe8\x0f\x15\x54\xcd\x87\x98\x22\xfc\x6b\x56\x0c\x6d\x57\xc5\x1d\xd7\x10\x97\x6a\x66\xaf\x8c\xf2\xda\xb5\x75\x1d\xbb\x83\xad\xf5\xa6\x6d\xa6\x94\x99\x07\x2f\xce\x5e\xbd\x7c\xf3\xf6\xc5\xfb\xb7\x17\xef\x9f\x5f\xbc\x39\xfd\xe5\xc5\xf9\xfb\xab\xf3\xd3\x17\xd7\x17\x97\xe7\x87\x6e\x1f\x8a\xfe\x76\x71\xfd\xfe\xcd\xef\xa7\x7b\x4f\x82\x52\x36\x14\xb2\x2c\xf6\xba\x26\xc1\x77\xe7\x57\x6f\x2e\x5e\xbd\xdc\x0b\x6e\x25\x18\x90\x1c\xf9\xcd\xfe\x08\x2f\xe3\xf6\x08\xc5\x11\xfa\x03\x94\xf0\x25\xd0\x2f\x17\x2f\x4f\xaf\xfe\x78\x4f\xc9\xd9\x83\xb5\x7d\xff\xa5\xd8\xf3\x57\x67\x6f\x2f\xcf\x5f\x5e\x9f\x5e\x5f\xbc\x7a\xf9\xfe\xb0\xf4\xa8\xc1\xfb\x23\x75\x3a\x9f\xdf\xdd\xdd\xc1\x98\x1c\xa8\xda\x79\xd9\x16\x5f\xa1\x9c\xbd\x7a\xfd\xc7\xd5\xc5\x6f\xbf\x5f\xff\x05\xc2\x8a\x7a\xbd\x68\xbb\x0d\x14\xed\xfa\xfb\x00\x7f\x9c\x9f\x5e\x3d\x24\x5c\xc8\x07\xc7\xf2\xae\xbd\xeb\x63\x57\x2d\xef\x0f\x33\x3c\xc4\x7e\x97\xf2\x47\x15\x1a\x3f\xd2\x34\x38\xdd\x6c\xea\x6a\xec\x8b\xdf\xea\x36\xcf\xea\xc7\x75\x39\x63\xf3\x93\x27\xc7\x3f\x70\xce\x1e\x87\xff\x27\xf6\x9d\xb8\xb3\x67\xfb\xc7\x1f\x19\xe7\xd3\x18\xaa\xab\xe6\xbf\x59\x17\xeb\xc5\xac\x2a\x68\x4e\x0d\xf7\x9b\xb8\x98\x55\xeb\xec\x26\xce\x37\xcd\xcd\x8c\xad\xba\xb8\x5c\xcc\xfe\xfc\xf3\xb0\x87\x3f\x7f\xce\xfa\x3e\x0e\xfd\x7c\x99\x6d\xe9\x18\x57\xf8\x51\x21\xb7\xda\x3a\xa3\x32\x14\xa6\xb0\x99\x2d\x73\xb7\x0c\xaa\x14\xa5\xcc\x33\x25\xf2\x32\x28\x48\x80\x54\xf6\xfd\x62\x96\x8e\xcc\xfe\x5f\xad\x90\xf6\xa3\xb4\xdc\x3a\x2c\x94\x54\x4e\x5b\x6d\x73\xd4\x39\x2a\x93\x07\x23\xa5\x37\xae\x28\xb3\x32\x3e\xb2\x22\x1d\xd9\x4d\xd2\xd1\x8c\xaa\x19\xe2\x4d\x57\x0d\xf7\x8b\xd9\x6c\xb4\xa9\x1f\xee\xeb\xd8\xaf\x62\x1c\xfe\xc6\x90\x6d\x6c\xca\xb6\xe3\x21\x66\xae\xd4\x42\x14\xa2\x88\x85\xb3\x1e\xa3\xf7\x4e\x96\x4b\xa9\x85\x53\x1e\x15\x14\x7d\xff\xc8\xf3\x7f\xa0\x72\x3f\x79\x78\xbe\x2c\xac\x28\x97\x68\xcc\x32\xda\xcc\x8a\x0c\x73\xe1\x44\x59\x8a\xa8\xb2\x65\x16\xc2\xa4\x75\xbc\x33\x18\x3b\x9e\xef\x6e\xcc\xe3\xbc\x2d\xef\x27\x73\x9a\x76\xbc\xb3\xc6\xc7\xb4\x55\x56\x5b\x96\xcc\x59\xcc\xd6\x59\x77\x53\x35\x3f\x31\xc1\x68\x38\xff\x3c\x7b\x90\x4a\x92\x2b\x3c\xf9\xaf\x6c\x9b\xbd\x49\x08\xec\x8a\x6e\xd8\x2e\x96\xc7\xf3\x15\x7e\x21\xb8\x39\x79\x5d\xc7\xac\x8f\x2c\x36\x59\x5e\x47\x76\x70\xaa\x6a\xd8\x7d\x7b\xdb\xb1\xbb\x98\xb3\xa9\x6b\xd8\xd0\xb2\xdb\x3e\xb2\xe9\xee\x7d\x7b\x01\xc7\xf3\xcd\x81\x81\xf3\xb2\xda\x4e\xe6\xcf\x1f\xdb\x7f\xdc\x6f\x6f\xd8\x78\x95\xce\xa4\xf5\x33\xb6\x8a\xd5\xcd\x6a\x58\xcc\x8c\x9a\xb1\x8f\xeb\xba\xe9\x17\x33\xea\xf4\x9f\xc6\x36\xbf\x53\xd0\x76\x37\x73\x14\x42\xcc\xfb\xed\xcd\xec\xe4\xf8\x86\x2d\xab\xba\x5e\xcc\xfe\x23\xc8\xf0\xeb\xa9\x9f\xa5\x47\xde\xdd\x52\x30\xe2\x36\x36\x6d\x59\xce\x4e\x8e\x37\xd9\xb0\x62\xe5\x62\x76\x89\x16\x84\xf3\x4c\x21\x48\xcc\x0c\x18\x6f\xd9\xb8\x0a\x26\x99\x24\xda\x39\x6e\xc0\x84\x30\xd2\xd3\x2a\x88\xcf\x77\x3b\xf6\x52\x39\x10\x22\x30\x0c\xa0\xd0\x67\x08\xc6\xb2\xb4\x8c\x28\x44\xf2\xdd\xa6\x91\xd3\x3a\x62\xec\x65\x2f\xb5\x85\x20\x2d\x53\x12\xac\x0d\x5f\x61\x80\x30\x92\x03\xca\x82\x83\x40\x0f\xc2\xda\x91\x90\x8a\x13\x0b\x50\x5e\x6a\x0d\xc6\x7b\x86\x06\x84\x1d\x8d\x30\x6c\x5c\x77\xaa\xac\x43\x0e\x21\x60\xb2\xc0\x4f\xeb\xc8\x04\x29\x90\x23\x78\x3d\x9e\xd1\xd3\x9a\x98\x4c\x83\x75\x16\x9c\xd5\x05\x08\x87\xe4\x23\x08\x4f\x76\x3a\x10\xe8\x98\xcc\x10\xb4\x55\x6c\x5c\x47\x3c\x09\x01\x09\x4a\x38\x75\x69\x14\x04\x85\xe4\x99\x16\x48\x86\x69\xc7\xc6\x75\x67\x58\x18\x45\xc7\x00\x85\x69\xdd\x31\x85\xd5\x24\x32\x7a\xf3\xf8\x28\x43\x08\xda\x93\x8c\x4b\xfb\xd3\xb2\xe3\x49\x24\x27\x9d\xb4\x60\xad\xa6\xbf\xc4\x11\x14\x31\x40\xf4\x97\x46\x82\xa7\x33\x06\xa4\x50\xdf\x48\x9b\x14\x84\x1d\xb4\x29\x40\x28\x0d\x28\x3d\x08\xad\x40\xab\x40\x7e\x83\x35\xfe\x9b\xde\x48\x45\xa6\xa2\x77\x97\x3a\x40\x30\x9a\x69\x01\x52\x7e\xad\x00\x94\xd4\x5c\x82\x50\x2e\x65\x15\x14\x25\x55\xa2\x07\x87\x92\x13\x93\x25\xe6\xa5\xf6\x10\x9c\x66\xd2\x82\x0f\xdf\x40\x09\x8e\x2b\xd0\xde\x15\x80\x56\x83\xd6\x16\x94\x33\x14\x2f\x50\xd2\x31\x09\xda\x3d\x2e\xa7\x31\x3d\xe8\xc9\x6c\x21\xfc\x6c\xfe\xb8\x1f\x0c\x5a\x66\x10\xac\x50\x05\x97\x1a\x54\x50\x4c\x70\xea\x13\xcb\xa5\x04\x63\xdd\xf4\x80\x06\xbc\xb2\x67\xa0\xad\x65\x12\x41\x07\x4f\x3f\x52\x51\xb2\x25\x1b\x81\x88\xcc\xd0\x40\x50\x96\x4d\x3f\x63\x6e\x64\x3a\xcc\x0c\x28\xe1\x6a\xae\x40\x5a\xc7\x34\x48\xe9\x4e\x51\x40\xb0\xc8\xa6\x1f\x31\xca\x3b\x50\x42\x33\x0f\xa8\xce\xa4\x00\x19\x34\x93\x12\x9c\x54\xcc\x80\x63\xd2\x03\x75\xa7\x93\x9a\xb4\x3a\xab\x28\x9a\x52\x33\x07\xde\x22\xd3\xa0\x3d\xa9\x13\x86\xce\x98\x40\xe2\x46\xe9\xec\x5b\x7a\x50\x82\x0f\x94\x40\x74\x35\xd9\xe4\xc9\x26\x54\xc9\x03\x2f\xd9\xf4\x33\x45\x70\xef\x81\xfc\xf4\x28\x84\x56\x32\x25\x40\x9a\x77\xd2\x51\x71\x16\x82\xeb\x51\x13\x35\x17\x77\xe0\x0d\x0b\xa0\xc3\x44\x22\x68\x4f\x65\x69\x40\x68\x0f\x0a\x2d\x73\x80\x06\xc1\x07\x53\x73\x30\x96\x9a\x4f\x2b\x55\x90\x55\x56\x73\xd0\xc2\x72\x0d\xd6\x7b\x0e\x36\x48\x6e\xc1\xa8\x91\xa2\x12\xb0\x8c\x94\x59\xe1\x99\x04\xc4\x89\xd4\x54\xc6\x5b\x29\x40\x4b\x2c\x04\x99\xe1\x89\x2f\x11\x47\x16\x75\xb7\x70\x13\x2d\xc1\x6b\x6a\x61\x0d\xca\x3a\x0e\xe8\x35\xdb\xab\xa8\xc1\x8c\xf1\x1c\xcd\x41\xa1\xc1\x38\xae\xc1\x59\x4f\xe6\xf2\xbd\xe1\x67\x56\xa5\x41\xa2\x3c\xa3\x60\x18\x90\x12\xd9\x2e\x2c\x9f\xd6\xca\x82\x17\xd4\x49\x05\xe9\x11\xd6\x73\x09\x5e\x20\xe5\x4b\x72\x0f\x3a\xa8\x91\xb4\x60\x03\x13\x69\x47\x73\x05\x4e\xe3\x44\x13\x77\xcb\x0d\x08\x81\x29\xba\xc2\x52\x99\x7b\xa1\x12\x87\x3d\x08\xb1\x11\x82\x8d\xa0\x04\xa1\xd8\x83\x82\x2d\x21\xa8\x4f\x6b\xae\xc1\x7b\x9a\xf2\xd2\x9a\x42\x90\x67\xca\x51\x5b\x0a\xa4\xa0\x4a\xcb\x15\x58\x61\x47\xba\x1f\x1f\x28\x7e\x7e\x22\xd3\xfe\xd6\xd0\x54\x4c\xe1\x45\x45\xd6\x08\x81\x89\xe3\xd8\x83\x94\xeb\x47\x24\x3a\xcc\x1e\x40\x1d\xf9\xa2\xd0\x7f\x5a\xa3\x01\x17\xa8\x8b\x8c\xf5\xab\xd1\xac\x77\xa8\xd2\xe0\x15\x64\x91\x71\x1c\x34\x8d\x0c\xe3\x09\xc5\x58\xb5\xa7\x51\x48\x26\xb8\x02\x45\x18\x0a\x29\x3a\x29\xcd\xd6\xfa\xad\x24\xcf\xdd\x0e\x50\x3a\x70\xde\xac\x14\x38\xb4\x75\x6a\x0e\x12\x2a\x10\x74\x82\x46\xa4\x3b\x4e\xa5\x99\x9a\x7a\x48\xee\x68\x05\xa8\x4d\xaa\x0d\x2d\x68\x76\x28\xe9\x27\xda\xd0\x18\xdd\xd2\xb0\x30\xee\xd3\x5a\x7a\x40\xcb\x0d\x38\x67\x0a\xc1\x14\x78\x24\x33\xa5\x45\x66\x41\x7a\xe4\x0e\x84\xd9\xd1\x08\xc2\xab\x54\xb5\x01\xc9\x39\xef\xb9\x05\x97\xe4\x55\xa8\xc1\x52\x90\x9d\xc7\x42\x82\xa5\xc1\xeb\x29\x6e\x5e\x50\xe7\x68\xd2\x2a\x13\x91\x5a\x83\xda\x97\x7a\x29\x81\x4c\xa4\x84\x90\xa6\x57\xea\x06\x40\xda\xf0\x8a\x62\x2e\x0c\x4f\x13\x6e\xac\xde\x94\x90\x94\x03\x6a\x7e\x3d\x51\x16\x10\x55\x8a\x69\xf0\x94\x70\x87\x8a\x9c\x52\x86\x3a\x34\xe0\x44\xa7\xba\x4b\x41\x91\xd6\x02\xea\x64\x96\x49\xed\x42\x96\x50\x11\xd0\xfd\xc1\x25\x3d\xa4\x86\xe2\xd4\x1c\x96\x7a\x3b\x90\x26\xeb\x47\x92\xa6\x8e\xa6\x49\x4b\x96\xa7\x40\x8c\xa3\x41\x82\x13\x74\x49\x53\xa2\x1d\x48\x4c\x1b\x74\x45\xba\x74\x49\x93\x3e\xad\x03\x8d\x79\x4b\x74\xa0\xcc\xc8\x60\x47\xd2\xd2\x95\xf0\x69\x9d\x12\x43\xc3\x5c\x28\xb5\x4a\x55\x40\x35\xe1\x55\x41\xb0\xc2\x38\xd0\x5a\x26\x05\xe4\xa7\xb1\x6a\x4f\xa3\xa0\xd1\x98\xaa\x8a\x83\x53\x34\x6f\x85\xa7\x78\x59\xeb\x77\x85\x44\x78\x7e\x2b\x03\x35\xf5\x8a\x8f\x65\x45\xae\xcb\x24\xe6\x68\x54\x68\xad\xc7\x89\x64\xc0\x60\x48\x57\xfd\xd8\xa0\x38\xd1\x54\x5a\xe3\xf0\xd2\xc2\xf1\x54\x5a\x13\x9d\x4a\x6b\x52\xf5\xe9\x92\x46\xb2\x72\x60\x82\x1a\xab\xd9\xbf\x0b\x14\xea\x17\xb4\x1f\xb6\xe8\x89\x93\xa6\xf1\xfc\xe6\xe4\x98\xde\x08\x77\x6f\x95\xe3\xab\x6a\xdf\x15\x7f\xf7\x25\x10\x73\x87\x85\xc8\x65\xa6\x75\x9e\xa1\x08\xca\x15\x51\x99\x65\x89\x59\x89\xd1\x97\x19\x7c\xe8\x67\x04\xfd\xe8\x9d\xf5\xd1\x0b\xf8\x36\xeb\x58\xb6\xd9\xc4\xa6\x9c\x5e\x90\x17\x6c\x79\xdb\xa4\xff\x0b\x3c\xeb\xbb\xe2\x47\xf6\xe7\xfe\x45\x98\x44\xff\xb3\xdf\x49\x95\x6d\x71\xbb\x8e\xcd\x00\x45\x17\xb3\x21\x9e\xd7\x91\x9e\x9e\x3d\x1d\x05\x9e\xfe\xf8\xf3\xfe\xdc\x74\x06\xfa\xae\x60\x0b\xf2\xea\x81\xb5\x07\xa1\x4f\x04\x18\xed\x38\x5b\x55\x75\xf9\x6c\x3a\xb4\x87\xf9\x3c\xfd\x56\xcb\x67\x3f\x3c\x7b\x7a\x1d\x3f\x0e\xcf\x63\xd1\x96\xb1\x7b\x4a\xaf\xf4\x77\x55\x53\xb6\x77\x3f\x1e\x5a\x7b\xe8\xd4\xb3\xa7\xdf\x09\x63\x6c\x8a\xb6\xac\x9a\x1b\x5e\x35\x65\xfc\x18\x7b\xee\x4c\x8c\x99\xb4\x39\x9a\xe0\xa4\x2d\x73\xbd\x2c\xa5\xc5\x18\xd1\xab\x12\xb3\x68\xe0\x43\x7f\xe8\xda\xff\x4d\x89\x89\xa5\x8f\xc1\x28\xa3\xf2\xe0\x96\x4b\x53\x96\x5a\xe6\x4b\x6b\x51\x62\x29\xa5\x8f\x8f\xc0\x3f\x4f\x5f\x1c\xdf\xc8\xdd\x5f\x56\xc6\xc3\x07\x9b\x28\x0a\xb7\xd4\x11\x33\x93\x17\x4b\x25\xac\x0f\x2e\x33\x3e\xa0\xca\xe3\x32\x73\xba\xf8\xfb\xe2\x38\x6b\xcb\x78\x59\x75\x5d\xdb\xc1\xba\x2d\xe3\xdb\xab\x17\x6c\x71\x10\xe2\x2e\x6e\xea\xac\x88\x3f\x3d\x54\x4c\xf3\x2f\x46\x82\x87\x79\x60\xac\xbf\xab\x86\x62\xf5\xec\x6b\x06\x63\x05\x7d\xa4\x3d\xfd\x90\x6d\xb3\xa9\x6c\x7e\x7a\xc4\x26\x15\xc3\x6d\xd7\xb0\xef\x85\x96\x2a\x60\x9d\x0c\x9c\x13\xfc\xfc\x01\xe9\x80\xe4\x99\x2e\x44\x69\x3d\x6a\x1d\x6d\x74\x7a\x19\x10\x8d\x17\x32\x9a\x65\x28\x1d\xea\x14\xf7\x9f\xbf\x61\x57\x77\x9b\xdf\xff\x53\x8b\x08\x23\x2d\xdc\x4a\x8d\x32\x2b\x4b\xb3\xa4\x0b\x72\x59\xa0\x95\xcb\x68\x85\x0e\x85\xca\x4b\x53\xba\xef\x59\x71\x9f\xad\xeb\x7f\x6a\x05\x61\xa4\x85\x6b\xaf\x83\x2f\xb2\x22\x8f\xa8\xad\xce\x73\xed\xf2\x32\x57\x4e\x61\xa9\x23\x9a\x22\xff\xd2\x8a\xcf\x4f\xbe\xa4\x3e\xff\xfc\xad\xca\xa4\xaf\xf8\xaa\xdc\xfd\xe7\x3c\xcf\xfa\xaa\xe0\x65\xd7\x6e\xca\xf6\xae\xe1\x77\x6d\xb7\x5e\xb5\x75\xa4\x72\x9b\x3e\xa6\x8f\xe7\xe3\x7f\x05\x8e\xe7\xe3\xbf\xdf\xff\x37\x00\x00\xff\xff\x47\x67\x03\x90\x8f\x17\x00\x00") func web_uiIndexHtmlBytes() ([]byte, error) { return bindataRead( @@ -841,7 +841,7 @@ func web_uiIndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "web_ui/index.html", size: 5803, mode: os.FileMode(420), modTime: time.Unix(1558556350, 0)} + info := bindataFileInfo{name: "web_ui/index.html", size: 6031, mode: os.FileMode(420), modTime: time.Unix(1559855141, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -861,7 +861,7 @@ func web_uiRobotsTxt() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "web_ui/robots.txt", size: 52, mode: os.FileMode(420), modTime: time.Unix(1558556350, 0)} + info := bindataFileInfo{name: "web_ui/robots.txt", size: 52, mode: os.FileMode(420), modTime: time.Unix(1559855141, 0)} a := &asset{bytes: bytes, info: info} return a, nil } diff --git a/agent/checks/check.go b/agent/checks/check.go index b09bdc8be..f61a68e51 100644 --- a/agent/checks/check.go +++ b/agent/checks/check.go @@ -28,10 +28,10 @@ const ( // Otherwise we risk fork bombing a system. MinInterval = time.Second - // BufSize is the maximum size of the captured - // check output. Prevents an enormous buffer + // DefaultBufSize is the maximum size of the captured + // check output by defaut. Prevents an enormous buffer // from being captured - BufSize = 4 * 1024 // 4KB + DefaultBufSize = 4 * 1024 // 4KB // UserAgent is the value of the User-Agent header // for HTTP health checks. @@ -56,13 +56,14 @@ type CheckNotifier interface { // determine the health of a given check. It is compatible with // nagios plugins and expects the output in the same format. type CheckMonitor struct { - Notify CheckNotifier - CheckID types.CheckID - Script string - ScriptArgs []string - Interval time.Duration - Timeout time.Duration - Logger *log.Logger + Notify CheckNotifier + CheckID types.CheckID + Script string + ScriptArgs []string + Interval time.Duration + Timeout time.Duration + Logger *log.Logger + OutputMaxSize int stop bool stopCh chan struct{} @@ -122,7 +123,7 @@ func (c *CheckMonitor) check() { } // Collect the output - output, _ := circbuf.NewBuffer(BufSize) + output, _ := circbuf.NewBuffer(int64(c.OutputMaxSize)) cmd.Stdout = output cmd.Stderr = output exec.SetSysProcAttr(cmd) @@ -222,12 +223,17 @@ type CheckTTL struct { stop bool stopCh chan struct{} stopLock sync.Mutex + + OutputMaxSize int } // Start is used to start a check ttl, runs until Stop() func (c *CheckTTL) Start() { c.stopLock.Lock() defer c.stopLock.Unlock() + if c.OutputMaxSize < 1 { + c.OutputMaxSize = DefaultBufSize + } c.stop = false c.stopCh = make(chan struct{}) c.timer = time.NewTimer(c.TTL) @@ -275,16 +281,22 @@ func (c *CheckTTL) getExpiredOutput() string { // SetStatus is used to update the status of the check, // and to renew the TTL. If expired, TTL is restarted. -func (c *CheckTTL) SetStatus(status, output string) { +// output is returned (might be truncated) +func (c *CheckTTL) SetStatus(status, output string) string { c.Logger.Printf("[DEBUG] agent: Check %q status is now %s", c.CheckID, status) + total := len(output) + if total > c.OutputMaxSize { + output = fmt.Sprintf("%s ... (captured %d of %d bytes)", + output[:c.OutputMaxSize], c.OutputMaxSize, total) + } c.Notify.UpdateCheck(c.CheckID, status, output) - // Store the last output so we can retain it if the TTL expires. c.lastOutputLock.Lock() c.lastOutput = output c.lastOutputLock.Unlock() c.timer.Reset(c.TTL) + return output } // CheckHTTP is used to periodically make an HTTP request to @@ -303,6 +315,7 @@ type CheckHTTP struct { Timeout time.Duration Logger *log.Logger TLSClientConfig *tls.Config + OutputMaxSize int httpClient *http.Client stop bool @@ -339,6 +352,9 @@ func (c *CheckHTTP) Start() { } else if c.Interval < 10*time.Second { c.httpClient.Timeout = c.Interval } + if c.OutputMaxSize < 1 { + c.OutputMaxSize = DefaultBufSize + } } c.stop = false @@ -413,7 +429,7 @@ func (c *CheckHTTP) check() { defer resp.Body.Close() // Read the response into a circular buffer to limit the size - output, _ := circbuf.NewBuffer(BufSize) + output, _ := circbuf.NewBuffer(int64(c.OutputMaxSize)) if _, err := io.Copy(output, resp.Body); err != nil { c.Logger.Printf("[WARN] agent: Check %q error while reading body: %s", c.CheckID, err) } diff --git a/agent/checks/check_test.go b/agent/checks/check_test.go index 014d58302..b61bbc19c 100644 --- a/agent/checks/check_test.go +++ b/agent/checks/check_test.go @@ -44,11 +44,12 @@ func TestCheckMonitor_Script(t *testing.T) { t.Run(tt.status, func(t *testing.T) { notif := mock.NewNotify() check := &CheckMonitor{ - Notify: notif, - CheckID: types.CheckID("foo"), - Script: tt.script, - Interval: 25 * time.Millisecond, - Logger: log.New(ioutil.Discard, uniqueID(), log.LstdFlags), + Notify: notif, + CheckID: types.CheckID("foo"), + Script: tt.script, + Interval: 25 * time.Millisecond, + Logger: log.New(ioutil.Discard, uniqueID(), log.LstdFlags), + OutputMaxSize: DefaultBufSize, } check.Start() defer check.Stop() @@ -79,11 +80,12 @@ func TestCheckMonitor_Args(t *testing.T) { t.Run(tt.status, func(t *testing.T) { notif := mock.NewNotify() check := &CheckMonitor{ - Notify: notif, - CheckID: types.CheckID("foo"), - ScriptArgs: tt.args, - Interval: 25 * time.Millisecond, - Logger: log.New(ioutil.Discard, uniqueID(), log.LstdFlags), + Notify: notif, + CheckID: types.CheckID("foo"), + ScriptArgs: tt.args, + Interval: 25 * time.Millisecond, + Logger: log.New(ioutil.Discard, uniqueID(), log.LstdFlags), + OutputMaxSize: DefaultBufSize, } check.Start() defer check.Stop() @@ -103,12 +105,13 @@ func TestCheckMonitor_Timeout(t *testing.T) { // t.Parallel() // timing test. no parallel notif := mock.NewNotify() check := &CheckMonitor{ - Notify: notif, - CheckID: types.CheckID("foo"), - ScriptArgs: []string{"sh", "-c", "sleep 1 && exit 0"}, - Interval: 50 * time.Millisecond, - Timeout: 25 * time.Millisecond, - Logger: log.New(ioutil.Discard, uniqueID(), log.LstdFlags), + Notify: notif, + CheckID: types.CheckID("foo"), + ScriptArgs: []string{"sh", "-c", "sleep 1 && exit 0"}, + Interval: 50 * time.Millisecond, + Timeout: 25 * time.Millisecond, + Logger: log.New(ioutil.Discard, uniqueID(), log.LstdFlags), + OutputMaxSize: DefaultBufSize, } check.Start() defer check.Stop() @@ -128,11 +131,12 @@ func TestCheckMonitor_RandomStagger(t *testing.T) { // t.Parallel() // timing test. no parallel notif := mock.NewNotify() check := &CheckMonitor{ - Notify: notif, - CheckID: types.CheckID("foo"), - ScriptArgs: []string{"sh", "-c", "exit 0"}, - Interval: 25 * time.Millisecond, - Logger: log.New(ioutil.Discard, uniqueID(), log.LstdFlags), + Notify: notif, + CheckID: types.CheckID("foo"), + ScriptArgs: []string{"sh", "-c", "exit 0"}, + Interval: 25 * time.Millisecond, + Logger: log.New(ioutil.Discard, uniqueID(), log.LstdFlags), + OutputMaxSize: DefaultBufSize, } check.Start() defer check.Stop() @@ -153,11 +157,12 @@ func TestCheckMonitor_LimitOutput(t *testing.T) { t.Parallel() notif := mock.NewNotify() check := &CheckMonitor{ - Notify: notif, - CheckID: types.CheckID("foo"), - ScriptArgs: []string{"od", "-N", "81920", "/dev/urandom"}, - Interval: 25 * time.Millisecond, - Logger: log.New(ioutil.Discard, uniqueID(), log.LstdFlags), + Notify: notif, + CheckID: types.CheckID("foo"), + ScriptArgs: []string{"od", "-N", "81920", "/dev/urandom"}, + Interval: 25 * time.Millisecond, + Logger: log.New(ioutil.Discard, uniqueID(), log.LstdFlags), + OutputMaxSize: DefaultBufSize, } check.Start() defer check.Stop() @@ -165,7 +170,7 @@ func TestCheckMonitor_LimitOutput(t *testing.T) { time.Sleep(50 * time.Millisecond) // Allow for extra bytes for the truncation message - if len(notif.Output("foo")) > BufSize+100 { + if len(notif.Output("foo")) > DefaultBufSize+100 { t.Fatalf("output size is too long") } } @@ -287,7 +292,7 @@ func TestCheckHTTP(t *testing.T) { } // Body larger than 4k limit - body := bytes.Repeat([]byte{'a'}, 2*BufSize) + body := bytes.Repeat([]byte{'a'}, 2*DefaultBufSize) w.WriteHeader(tt.code) w.Write(body) })) @@ -295,13 +300,14 @@ func TestCheckHTTP(t *testing.T) { notif := mock.NewNotify() check := &CheckHTTP{ - Notify: notif, - CheckID: types.CheckID("foo"), - HTTP: server.URL, - Method: tt.method, - Header: tt.header, - Interval: 10 * time.Millisecond, - Logger: log.New(ioutil.Discard, uniqueID(), log.LstdFlags), + Notify: notif, + CheckID: types.CheckID("foo"), + HTTP: server.URL, + Method: tt.method, + OutputMaxSize: DefaultBufSize, + Header: tt.header, + Interval: 10 * time.Millisecond, + Logger: log.New(ioutil.Discard, uniqueID(), log.LstdFlags), } check.Start() defer check.Stop() @@ -313,15 +319,52 @@ func TestCheckHTTP(t *testing.T) { if got, want := notif.State("foo"), tt.status; got != want { r.Fatalf("got state %q want %q", got, want) } - // Allow slightly more data than BufSize, for the header - if n := len(notif.Output("foo")); n > (BufSize + 256) { - r.Fatalf("output too long: %d (%d-byte limit)", n, BufSize) + // Allow slightly more data than DefaultBufSize, for the header + if n := len(notif.Output("foo")); n > (DefaultBufSize + 256) { + r.Fatalf("output too long: %d (%d-byte limit)", n, DefaultBufSize) } }) }) } } +func TestCheckMaxOutputSize(t *testing.T) { + t.Parallel() + timeout := 5 * time.Millisecond + server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, req *http.Request) { + body := bytes.Repeat([]byte{'x'}, 2*DefaultBufSize) + writer.WriteHeader(200) + writer.Write(body) + })) + defer server.Close() + + notif := mock.NewNotify() + maxOutputSize := 32 + check := &CheckHTTP{ + Notify: notif, + CheckID: types.CheckID("bar"), + HTTP: server.URL + "/v1/agent/self", + Timeout: timeout, + Interval: 2 * time.Millisecond, + Logger: log.New(ioutil.Discard, uniqueID(), log.LstdFlags), + OutputMaxSize: maxOutputSize, + } + + check.Start() + defer check.Stop() + retry.Run(t, func(r *retry.R) { + if got, want := notif.Updates("bar"), 2; got < want { + r.Fatalf("got %d updates want at least %d", got, want) + } + if got, want := notif.State("bar"), api.HealthPassing; got != want { + r.Fatalf("got state %q want %q", got, want) + } + if got, want := notif.Output("bar"), "HTTP GET "+server.URL+"/v1/agent/self: 200 OK Output: "+strings.Repeat("x", maxOutputSize); got != want { + r.Fatalf("got state %q want %q", got, want) + } + }) +} + func TestCheckHTTPTimeout(t *testing.T) { t.Parallel() timeout := 5 * time.Millisecond @@ -372,7 +415,7 @@ func TestCheckHTTP_disablesKeepAlives(t *testing.T) { func largeBodyHandler(code int) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Body larger than 4k limit - body := bytes.Repeat([]byte{'a'}, 2*BufSize) + body := bytes.Repeat([]byte{'a'}, 2*DefaultBufSize) w.WriteHeader(code) w.Write(body) }) diff --git a/agent/config/builder.go b/agent/config/builder.go index 4e633ff2a..964231b04 100644 --- a/agent/config/builder.go +++ b/agent/config/builder.go @@ -14,6 +14,7 @@ import ( "strings" "time" + "github.com/hashicorp/consul/agent/checks" "github.com/hashicorp/consul/agent/connect/ca" "github.com/hashicorp/consul/agent/consul" "github.com/hashicorp/consul/agent/structs" @@ -786,6 +787,7 @@ func (b *Builder) Build() (rt RuntimeConfig, err error) { CAPath: b.stringVal(c.CAPath), CertFile: b.stringVal(c.CertFile), CheckUpdateInterval: b.durationVal("check_update_interval", c.CheckUpdateInterval), + CheckOutputMaxSize: b.intValWithDefault(c.CheckOutputMaxSize, 4096), Checks: checks, ClientAddrs: clientAddrs, ConfigEntryBootstrap: configEntries, @@ -880,6 +882,7 @@ func (b *Builder) Build() (rt RuntimeConfig, err error) { TaggedAddresses: c.TaggedAddresses, TranslateWANAddrs: b.boolVal(c.TranslateWANAddrs), UIDir: b.stringVal(c.UIDir), + UIContentPath: UIPathBuilder(b.stringVal(b.Flags.Config.UIContentPath)), UnixSocketGroup: b.stringVal(c.UnixSocket.Group), UnixSocketMode: b.stringVal(c.UnixSocket.Mode), UnixSocketUser: b.stringVal(c.UnixSocket.User), @@ -905,6 +908,9 @@ func (b *Builder) Validate(rt RuntimeConfig) error { // reDatacenter defines a regexp for a valid datacenter name var reDatacenter = regexp.MustCompile("^[a-z0-9_-]+$") + // validContentPath defines a regexp for a valid content path name. + var validContentPath = regexp.MustCompile(`^[A-Za-z0-9/_-]+$`) + var hasVersion = regexp.MustCompile(`^/v\d+/$`) // ---------------------------------------------------------------- // check required params we cannot recover from first // @@ -913,11 +919,20 @@ func (b *Builder) Validate(rt RuntimeConfig) error { return fmt.Errorf("datacenter cannot be empty") } if !reDatacenter.MatchString(rt.Datacenter) { - return fmt.Errorf("datacenter cannot be %q. Please use only [a-z0-9-_].", rt.Datacenter) + return fmt.Errorf("datacenter cannot be %q. Please use only [a-z0-9-_]", rt.Datacenter) } if rt.DataDir == "" && !rt.DevMode { return fmt.Errorf("data_dir cannot be empty") } + + if !validContentPath.MatchString(rt.UIContentPath) { + return fmt.Errorf("ui-content-path can only contain alphanumeric, -, _, or /. received: %s", rt.UIContentPath) + } + + if hasVersion.MatchString(rt.UIContentPath) { + return fmt.Errorf("ui-content-path cannot have 'v[0-9]'. received: %s", rt.UIContentPath) + } + if !rt.DevMode { fi, err := os.Stat(rt.DataDir) switch { @@ -964,6 +979,9 @@ func (b *Builder) Validate(rt RuntimeConfig) error { if rt.BootstrapExpect > 0 && rt.Bootstrap { return fmt.Errorf("'bootstrap_expect > 0' and 'bootstrap = true' are mutually exclusive") } + if rt.CheckOutputMaxSize < 1 { + return fmt.Errorf("check_output_max_size must be positive, to discard check output use the discard_check_output flag") + } if rt.AEInterval <= 0 { return fmt.Errorf("ae_interval cannot be %s. Must be positive", rt.AEInterval) } @@ -971,7 +989,7 @@ func (b *Builder) Validate(rt RuntimeConfig) error { return fmt.Errorf("autopilot.max_trailing_logs cannot be %d. Must be greater than or equal to zero", rt.AutopilotMaxTrailingLogs) } if rt.ACLDatacenter != "" && !reDatacenter.MatchString(rt.ACLDatacenter) { - return fmt.Errorf("acl_datacenter cannot be %q. Please use only [a-z0-9-_].", rt.ACLDatacenter) + return fmt.Errorf("acl_datacenter cannot be %q. Please use only [a-z0-9-_]", rt.ACLDatacenter) } if rt.EnableUI && rt.UIDir != "" { return fmt.Errorf( @@ -1174,6 +1192,7 @@ func (b *Builder) checkVal(v *CheckDefinition) *structs.CheckDefinition { Timeout: b.durationVal(fmt.Sprintf("check[%s].timeout", id), v.Timeout), TTL: b.durationVal(fmt.Sprintf("check[%s].ttl", id), v.TTL), DeregisterCriticalServiceAfter: b.durationVal(fmt.Sprintf("check[%s].deregister_critical_service_after", id), v.DeregisterCriticalServiceAfter), + OutputMaxSize: b.intValWithDefault(v.OutputMaxSize, checks.DefaultBufSize), } } @@ -1368,13 +1387,17 @@ func (b *Builder) durationVal(name string, v *string) (d time.Duration) { return b.durationValWithDefault(name, v, 0) } -func (b *Builder) intVal(v *int) int { +func (b *Builder) intValWithDefault(v *int, defaultVal int) int { if v == nil { - return 0 + return defaultVal } return *v } +func (b *Builder) intVal(v *int) int { + return b.intValWithDefault(v, 0) +} + func (b *Builder) portVal(name string, v *int) int { if v == nil || *v <= 0 { return -1 @@ -1683,3 +1706,17 @@ func isUnixAddr(a net.Addr) bool { _, ok := a.(*net.UnixAddr) return ok } + +// UIPathBuilder checks to see if there was a path set +// If so, adds beginning and trailing slashes to UI path +func UIPathBuilder(UIContentString string) string { + if UIContentString != "" { + var fmtedPath string + fmtedPath = strings.Trim(UIContentString, "/") + fmtedPath = "/" + fmtedPath + "/" + return fmtedPath + + } + return "/ui/" + +} diff --git a/agent/config/config.go b/agent/config/config.go index 4cf66e51a..20656ceb9 100644 --- a/agent/config/config.go +++ b/agent/config/config.go @@ -184,6 +184,7 @@ type Config struct { CAPath *string `json:"ca_path,omitempty" hcl:"ca_path" mapstructure:"ca_path"` CertFile *string `json:"cert_file,omitempty" hcl:"cert_file" mapstructure:"cert_file"` Check *CheckDefinition `json:"check,omitempty" hcl:"check" mapstructure:"check"` // needs to be a pointer to avoid partial merges + CheckOutputMaxSize *int `json:"check_output_max_size,omitempty" hcl:"check_output_max_size" mapstructure:"check_output_max_size"` CheckUpdateInterval *string `json:"check_update_interval,omitempty" hcl:"check_update_interval" mapstructure:"check_update_interval"` Checks []CheckDefinition `json:"checks,omitempty" hcl:"checks" mapstructure:"checks"` ClientAddr *string `json:"client_addr,omitempty" hcl:"client_addr" mapstructure:"client_addr"` @@ -264,6 +265,7 @@ type Config struct { Telemetry Telemetry `json:"telemetry,omitempty" hcl:"telemetry" mapstructure:"telemetry"` TranslateWANAddrs *bool `json:"translate_wan_addrs,omitempty" hcl:"translate_wan_addrs" mapstructure:"translate_wan_addrs"` UI *bool `json:"ui,omitempty" hcl:"ui" mapstructure:"ui"` + UIContentPath *string `json:"ui_content_path,omitempty" hcl:"ui_content_path" mapstructure:"ui_content_path"` UIDir *string `json:"ui_dir,omitempty" hcl:"ui_dir" mapstructure:"ui_dir"` UnixSocket UnixSocket `json:"unix_sockets,omitempty" hcl:"unix_sockets" mapstructure:"unix_sockets"` VerifyIncoming *bool `json:"verify_incoming,omitempty" hcl:"verify_incoming" mapstructure:"verify_incoming"` @@ -396,6 +398,7 @@ type CheckDefinition struct { HTTP *string `json:"http,omitempty" hcl:"http" mapstructure:"http"` Header map[string][]string `json:"header,omitempty" hcl:"header" mapstructure:"header"` Method *string `json:"method,omitempty" hcl:"method" mapstructure:"method"` + OutputMaxSize *int `json:"output_max_size,omitempty" hcl:"output_max_size" mapstructure:"output_max_size"` TCP *string `json:"tcp,omitempty" hcl:"tcp" mapstructure:"tcp"` Interval *string `json:"interval,omitempty" hcl:"interval" mapstructure:"interval"` DockerContainerID *string `json:"docker_container_id,omitempty" hcl:"docker_container_id" mapstructure:"docker_container_id"` diff --git a/agent/config/default.go b/agent/config/default.go index 0d6fe3ff6..477c14435 100644 --- a/agent/config/default.go +++ b/agent/config/default.go @@ -4,6 +4,7 @@ import ( "fmt" "strconv" + "github.com/hashicorp/consul/agent/checks" "github.com/hashicorp/consul/agent/consul" "github.com/hashicorp/consul/version" ) @@ -49,6 +50,7 @@ func DefaultSource() Source { bind_addr = "0.0.0.0" bootstrap = false bootstrap_expect = 0 + check_output_max_size = ` + strconv.Itoa(checks.DefaultBufSize) + ` check_update_interval = "5m" client_addr = "127.0.0.1" datacenter = "` + consul.DefaultDC + `" diff --git a/agent/config/flags.go b/agent/config/flags.go index 7dc4f5a1f..d25896840 100644 --- a/agent/config/flags.go +++ b/agent/config/flags.go @@ -60,6 +60,7 @@ func AddFlags(fs *flag.FlagSet, f *Flags) { add(&f.Config.Bootstrap, "bootstrap", "Sets server to bootstrap mode.") add(&f.Config.BootstrapExpect, "bootstrap-expect", "Sets server to expect bootstrap mode.") add(&f.Config.ClientAddr, "client", "Sets the address to bind for client access. This includes RPC, DNS, HTTP, HTTPS and gRPC (if configured).") + add(&f.Config.CheckOutputMaxSize, "check_output_max_size", "Sets the maximum output size for checks on this agent") add(&f.ConfigFiles, "config-dir", "Path to a directory to read configuration files from. This will read every file ending in '.json' as configuration in this directory in alphabetical order. Can be specified multiple times.") add(&f.ConfigFiles, "config-file", "Path to a file in JSON or HCL format with a matching file extension. Can be specified multiple times.") add(&f.ConfigFormat, "config-format", "Config files are in this format irrespective of their extension. Must be 'hcl' or 'json'") @@ -105,6 +106,7 @@ func AddFlags(fs *flag.FlagSet, f *Flags) { add(&f.Config.ServerMode, "server", "Switches agent to server mode.") add(&f.Config.EnableSyslog, "syslog", "Enables logging to syslog.") add(&f.Config.UI, "ui", "Enables the built-in static web UI server.") + add(&f.Config.UIContentPath, "ui-content-path", "Sets the external UI path to a string. Defaults to: /ui/ ") add(&f.Config.UIDir, "ui-dir", "Path to directory containing the web UI resources.") add(&f.HCL, "hcl", "hcl config fragment. Can be specified multiple times.") } diff --git a/agent/config/runtime.go b/agent/config/runtime.go index dc9d567f8..528428775 100644 --- a/agent/config/runtime.go +++ b/agent/config/runtime.go @@ -459,6 +459,11 @@ type RuntimeConfig struct { // hcl: check_update_interval = "duration" CheckUpdateInterval time.Duration + // Maximum size for the output of a healtcheck + // hcl check_output_max_size int + // flag: -check_output_max_size int + CheckOutputMaxSize int + // Checks contains the provided check definitions. // // hcl: checks = [ @@ -1382,6 +1387,10 @@ type RuntimeConfig struct { // flag: -ui-dir string UIDir string + //UIContentPath is a string that sets the external + // path to a string. Default: /ui/ + UIContentPath string + // UnixSocketGroup contains the group of the file permissions when // Consul binds to UNIX sockets. // diff --git a/agent/config/runtime_test.go b/agent/config/runtime_test.go index b008cf6bc..154cc3d41 100644 --- a/agent/config/runtime_test.go +++ b/agent/config/runtime_test.go @@ -18,6 +18,7 @@ import ( "testing" "time" + "github.com/hashicorp/consul/agent/checks" "github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/lib" "github.com/hashicorp/consul/sdk/testutil" @@ -742,6 +743,18 @@ func TestConfigFlagsAndEdgecases(t *testing.T) { rt.DataDir = dataDir }, }, + { + desc: "-ui-content-path", + args: []string{ + `-ui-content-path=/a/b`, + `-data-dir=` + dataDir, + }, + + patch: func(rt *RuntimeConfig) { + rt.UIContentPath = "/a/b/" + rt.DataDir = dataDir + }, + }, // ------------------------------------------------------------ // ports and addresses @@ -2076,8 +2089,8 @@ func TestConfigFlagsAndEdgecases(t *testing.T) { }, patch: func(rt *RuntimeConfig) { rt.Checks = []*structs.CheckDefinition{ - &structs.CheckDefinition{Name: "a", ScriptArgs: []string{"/bin/true"}}, - &structs.CheckDefinition{Name: "b", ScriptArgs: []string{"/bin/false"}}, + &structs.CheckDefinition{Name: "a", ScriptArgs: []string{"/bin/true"}, OutputMaxSize: checks.DefaultBufSize}, + &structs.CheckDefinition{Name: "b", ScriptArgs: []string{"/bin/false"}, OutputMaxSize: checks.DefaultBufSize}, } rt.DataDir = dataDir }, @@ -2095,7 +2108,7 @@ func TestConfigFlagsAndEdgecases(t *testing.T) { }, patch: func(rt *RuntimeConfig) { rt.Checks = []*structs.CheckDefinition{ - &structs.CheckDefinition{Name: "a", GRPC: "localhost:12345/foo", GRPCUseTLS: true}, + &structs.CheckDefinition{Name: "a", GRPC: "localhost:12345/foo", GRPCUseTLS: true, OutputMaxSize: checks.DefaultBufSize}, } rt.DataDir = dataDir }, @@ -2113,7 +2126,7 @@ func TestConfigFlagsAndEdgecases(t *testing.T) { }, patch: func(rt *RuntimeConfig) { rt.Checks = []*structs.CheckDefinition{ - &structs.CheckDefinition{Name: "a", AliasService: "foo"}, + &structs.CheckDefinition{Name: "a", AliasService: "foo", OutputMaxSize: checks.DefaultBufSize}, } rt.DataDir = dataDir }, @@ -2250,6 +2263,7 @@ func TestConfigFlagsAndEdgecases(t *testing.T) { DockerContainerID: "z", DeregisterCriticalServiceAfter: 10 * time.Second, ScriptArgs: []string{"a", "b"}, + OutputMaxSize: checks.DefaultBufSize, }, }, Weights: &structs.Weights{ @@ -2515,8 +2529,9 @@ func TestConfigFlagsAndEdgecases(t *testing.T) { Port: 2345, Checks: structs.CheckTypes{ { - TCP: "127.0.0.1:2345", - Interval: 10 * time.Second, + TCP: "127.0.0.1:2345", + Interval: 10 * time.Second, + OutputMaxSize: checks.DefaultBufSize, }, }, Proxy: &structs.ConnectProxyConfig{ @@ -2610,8 +2625,9 @@ func TestConfigFlagsAndEdgecases(t *testing.T) { Port: 2345, Checks: structs.CheckTypes{ { - TCP: "127.0.0.1:2345", - Interval: 10 * time.Second, + TCP: "127.0.0.1:2345", + Interval: 10 * time.Second, + OutputMaxSize: checks.DefaultBufSize, }, }, Proxy: &structs.ConnectProxyConfig{ @@ -3043,6 +3059,7 @@ func TestFullConfig(t *testing.T) { "f3r6xFtM": [ "RyuIdDWv", "QbxEcIUM" ] }, "method": "Dou0nGT5", + "output_max_size": ` + strconv.Itoa(checks.DefaultBufSize) + `, "tcp": "JY6fTTcw", "interval": "18714s", "docker_container_id": "qF66POS9", @@ -3069,6 +3086,7 @@ func TestFullConfig(t *testing.T) { "method": "aldrIQ4l", "tcp": "RJQND605", "interval": "22164s", + "output_max_size": ` + strconv.Itoa(checks.DefaultBufSize) + `, "docker_container_id": "ipgdFtjd", "shell": "qAeOYy0M", "tls_skip_verify": true, @@ -3092,6 +3110,7 @@ func TestFullConfig(t *testing.T) { "method": "gLrztrNw", "tcp": "4jG5casb", "interval": "28767s", + "output_max_size": ` + strconv.Itoa(checks.DefaultBufSize) + `, "docker_container_id": "THW6u7rL", "shell": "C1Zt3Zwh", "tls_skip_verify": true, @@ -3302,6 +3321,7 @@ func TestFullConfig(t *testing.T) { "method": "9afLm3Mj", "tcp": "fjiLFqVd", "interval": "23926s", + "output_max_size": ` + strconv.Itoa(checks.DefaultBufSize) + `, "docker_container_id": "dO5TtRHk", "shell": "e6q2ttES", "tls_skip_verify": true, @@ -3324,6 +3344,7 @@ func TestFullConfig(t *testing.T) { "method": "T66MFBfR", "tcp": "bNnNfx2A", "interval": "22224s", + "output_max_size": ` + strconv.Itoa(checks.DefaultBufSize) + `, "docker_container_id": "ipgdFtjd", "shell": "omVZq7Sz", "tls_skip_verify": true, @@ -3345,6 +3366,7 @@ func TestFullConfig(t *testing.T) { "method": "ciYHWors", "tcp": "FfvCwlqH", "interval": "12356s", + "output_max_size": ` + strconv.Itoa(checks.DefaultBufSize) + `, "docker_container_id": "HBndBU6R", "shell": "hVI33JjA", "tls_skip_verify": true, @@ -3380,6 +3402,7 @@ func TestFullConfig(t *testing.T) { "method": "X5DrovFc", "tcp": "ICbxkpSF", "interval": "24392s", + "output_max_size": ` + strconv.Itoa(checks.DefaultBufSize) + `, "docker_container_id": "ZKXr68Yb", "shell": "CEfzx0Fo", "tls_skip_verify": true, @@ -3418,6 +3441,7 @@ func TestFullConfig(t *testing.T) { "method": "5wkAxCUE", "tcp": "MN3oA9D2", "interval": "32718s", + "output_max_size": ` + strconv.Itoa(checks.DefaultBufSize) + `, "docker_container_id": "cU15LMet", "shell": "nEz9qz2l", "tls_skip_verify": true, @@ -3439,6 +3463,7 @@ func TestFullConfig(t *testing.T) { "method": "wzByP903", "tcp": "2exjZIGE", "interval": "5656s", + "output_max_size": ` + strconv.Itoa(checks.DefaultBufSize) + `, "docker_container_id": "5tDBWpfA", "shell": "rlTpLM8s", "tls_skip_verify": true, @@ -3621,6 +3646,7 @@ func TestFullConfig(t *testing.T) { method = "Dou0nGT5" tcp = "JY6fTTcw" interval = "18714s" + output_max_size = ` + strconv.Itoa(checks.DefaultBufSize) + ` docker_container_id = "qF66POS9" shell = "sOnDy228" tls_skip_verify = true @@ -3645,6 +3671,7 @@ func TestFullConfig(t *testing.T) { method = "aldrIQ4l" tcp = "RJQND605" interval = "22164s" + output_max_size = ` + strconv.Itoa(checks.DefaultBufSize) + ` docker_container_id = "ipgdFtjd" shell = "qAeOYy0M" tls_skip_verify = true @@ -3668,6 +3695,7 @@ func TestFullConfig(t *testing.T) { method = "gLrztrNw" tcp = "4jG5casb" interval = "28767s" + output_max_size = ` + strconv.Itoa(checks.DefaultBufSize) + ` docker_container_id = "THW6u7rL" shell = "C1Zt3Zwh" tls_skip_verify = true @@ -3903,6 +3931,7 @@ func TestFullConfig(t *testing.T) { method = "T66MFBfR" tcp = "bNnNfx2A" interval = "22224s" + output_max_size = ` + strconv.Itoa(checks.DefaultBufSize) + ` docker_container_id = "ipgdFtjd" shell = "omVZq7Sz" tls_skip_verify = true @@ -3924,6 +3953,7 @@ func TestFullConfig(t *testing.T) { method = "ciYHWors" tcp = "FfvCwlqH" interval = "12356s" + output_max_size = ` + strconv.Itoa(checks.DefaultBufSize) + ` docker_container_id = "HBndBU6R" shell = "hVI33JjA" tls_skip_verify = true @@ -3959,6 +3989,7 @@ func TestFullConfig(t *testing.T) { method = "X5DrovFc" tcp = "ICbxkpSF" interval = "24392s" + output_max_size = ` + strconv.Itoa(checks.DefaultBufSize) + ` docker_container_id = "ZKXr68Yb" shell = "CEfzx0Fo" tls_skip_verify = true @@ -3997,6 +4028,7 @@ func TestFullConfig(t *testing.T) { method = "5wkAxCUE" tcp = "MN3oA9D2" interval = "32718s" + output_max_size = ` + strconv.Itoa(checks.DefaultBufSize) + ` docker_container_id = "cU15LMet" shell = "nEz9qz2l" tls_skip_verify = true @@ -4018,6 +4050,7 @@ func TestFullConfig(t *testing.T) { method = "wzByP903" tcp = "2exjZIGE" interval = "5656s" + output_max_size = ` + strconv.Itoa(checks.DefaultBufSize) + ` docker_container_id = "5tDBWpfA" shell = "rlTpLM8s" tls_skip_verify = true @@ -4286,6 +4319,7 @@ func TestFullConfig(t *testing.T) { CAFile: "erA7T0PM", CAPath: "mQEN1Mfp", CertFile: "7s4QAzDk", + CheckOutputMaxSize: checks.DefaultBufSize, Checks: []*structs.CheckDefinition{ &structs.CheckDefinition{ ID: "uAjE6m9Z", @@ -4303,6 +4337,7 @@ func TestFullConfig(t *testing.T) { Method: "aldrIQ4l", TCP: "RJQND605", Interval: 22164 * time.Second, + OutputMaxSize: checks.DefaultBufSize, DockerContainerID: "ipgdFtjd", Shell: "qAeOYy0M", TLSSkipVerify: true, @@ -4324,6 +4359,7 @@ func TestFullConfig(t *testing.T) { "qxvdnSE9": []string{"6wBPUYdF", "YYh8wtSZ"}, }, Method: "gLrztrNw", + OutputMaxSize: checks.DefaultBufSize, TCP: "4jG5casb", Interval: 28767 * time.Second, DockerContainerID: "THW6u7rL", @@ -4347,6 +4383,7 @@ func TestFullConfig(t *testing.T) { "f3r6xFtM": {"RyuIdDWv", "QbxEcIUM"}, }, Method: "Dou0nGT5", + OutputMaxSize: checks.DefaultBufSize, TCP: "JY6fTTcw", Interval: 18714 * time.Second, DockerContainerID: "qF66POS9", @@ -4515,6 +4552,7 @@ func TestFullConfig(t *testing.T) { "cVFpko4u": {"gGqdEB6k", "9LsRo22u"}, }, Method: "X5DrovFc", + OutputMaxSize: checks.DefaultBufSize, TCP: "ICbxkpSF", Interval: 24392 * time.Second, DockerContainerID: "ZKXr68Yb", @@ -4563,6 +4601,7 @@ func TestFullConfig(t *testing.T) { "1UJXjVrT": {"OJgxzTfk", "xZZrFsq7"}, }, Method: "5wkAxCUE", + OutputMaxSize: checks.DefaultBufSize, TCP: "MN3oA9D2", Interval: 32718 * time.Second, DockerContainerID: "cU15LMet", @@ -4584,6 +4623,7 @@ func TestFullConfig(t *testing.T) { "vr7wY7CS": {"EtCoNPPL", "9vAarJ5s"}, }, Method: "wzByP903", + OutputMaxSize: checks.DefaultBufSize, TCP: "2exjZIGE", Interval: 5656 * time.Second, DockerContainerID: "5tDBWpfA", @@ -4679,6 +4719,7 @@ func TestFullConfig(t *testing.T) { "SHOVq1Vv": {"jntFhyym", "GYJh32pp"}, }, Method: "T66MFBfR", + OutputMaxSize: checks.DefaultBufSize, TCP: "bNnNfx2A", Interval: 22224 * time.Second, DockerContainerID: "ipgdFtjd", @@ -4700,6 +4741,7 @@ func TestFullConfig(t *testing.T) { "p2UI34Qz": {"UsG1D0Qh", "NHhRiB6s"}, }, Method: "ciYHWors", + OutputMaxSize: checks.DefaultBufSize, TCP: "FfvCwlqH", Interval: 12356 * time.Second, DockerContainerID: "HBndBU6R", @@ -4721,6 +4763,7 @@ func TestFullConfig(t *testing.T) { "l4HwQ112": {"fk56MNlo", "dhLK56aZ"}, }, Method: "9afLm3Mj", + OutputMaxSize: checks.DefaultBufSize, TCP: "fjiLFqVd", Interval: 23926 * time.Second, DockerContainerID: "dO5TtRHk", @@ -4777,6 +4820,7 @@ func TestFullConfig(t *testing.T) { "wan": "78.63.37.19", }, TranslateWANAddrs: true, + UIContentPath: "/ui/", UIDir: "11IFzAUn", UnixSocketUser: "E0nB1DwA", UnixSocketGroup: "8pFodrV8", @@ -5058,6 +5102,7 @@ func TestConfigDecodeBytes(t *testing.T) { func TestSanitize(t *testing.T) { rt := RuntimeConfig{ BindAddr: &net.IPAddr{IP: net.ParseIP("127.0.0.1")}, + CheckOutputMaxSize: checks.DefaultBufSize, SerfAdvertiseAddrLAN: &net.TCPAddr{IP: net.ParseIP("1.2.3.4"), Port: 5678}, DNSAddrs: []net.Addr{ &net.TCPAddr{IP: net.ParseIP("1.2.3.4"), Port: 5678}, @@ -5080,7 +5125,8 @@ func TestSanitize(t *testing.T) { Name: "foo", Token: "bar", Check: structs.CheckType{ - Name: "blurb", + Name: "blurb", + OutputMaxSize: checks.DefaultBufSize, }, Weights: &structs.Weights{ Passing: 67, @@ -5090,8 +5136,9 @@ func TestSanitize(t *testing.T) { }, Checks: []*structs.CheckDefinition{ &structs.CheckDefinition{ - Name: "zoo", - Token: "zope", + Name: "zoo", + Token: "zope", + OutputMaxSize: checks.DefaultBufSize, }, }, } @@ -5131,6 +5178,7 @@ func TestSanitize(t *testing.T) { "CAPath": "", "CertFile": "", "CheckDeregisterIntervalMin": "0s", + "CheckOutputMaxSize": ` + strconv.Itoa(checks.DefaultBufSize) + `, "CheckReapInterval": "0s", "CheckUpdateInterval": "0s", "Checks": [{ @@ -5147,6 +5195,7 @@ func TestSanitize(t *testing.T) { "Method": "", "Name": "zoo", "Notes": "", + "OutputMaxSize": ` + strconv.Itoa(checks.DefaultBufSize) + `, "ScriptArgs": [], "ServiceID": "", "Shell": "", @@ -5178,6 +5227,7 @@ func TestSanitize(t *testing.T) { "ConsulCoordinateUpdateMaxBatches": 0, "ConsulCoordinateUpdatePeriod": "15s", "ConsulRaftElectionTimeout": "0s", + "CheckOutputMaxSize": ` + strconv.Itoa(checks.DefaultBufSize) + `, "ConsulRaftHeartbeatTimeout": "0s", "ConsulRaftLeaderLeaseTimeout": "0s", "GossipLANGossipInterval": "0s", @@ -5317,6 +5367,7 @@ func TestSanitize(t *testing.T) { "Method": "", "Name": "blurb", "Notes": "", + "OutputMaxSize": ` + strconv.Itoa(checks.DefaultBufSize) + `, "ScriptArgs": [], "Shell": "", "Status": "", @@ -5380,6 +5431,7 @@ func TestSanitize(t *testing.T) { "StatsiteAddr": "" }, "TranslateWANAddrs": false, + "UIContentPath": "", "UIDir": "", "UnixSocketGroup": "", "UnixSocketMode": "", @@ -5720,6 +5772,41 @@ func TestReadPath(t *testing.T) { } } +func Test_UIPathBuilder(t *testing.T) { + cases := []struct { + name string + path string + expected string + }{ + { + "Letters only string", + "hello", + "/hello/", + }, + { + "Alphanumeric", + "Hello1", + "/Hello1/", + }, + { + "Hyphen and underscore", + "-_", + "/-_/", + }, + { + "Many slashes", + "/hello/ui/1/", + "/hello/ui/1/", + }, + } + + for _, tt := range cases { + actual := UIPathBuilder(tt.path) + require.Equal(t, tt.expected, actual) + + } +} + func splitIPPort(hostport string) (net.IP, int) { h, p, err := net.SplitHostPort(hostport) if err != nil { diff --git a/agent/consul/config.go b/agent/consul/config.go index 491bdeb4f..cd0320d30 100644 --- a/agent/consul/config.go +++ b/agent/consul/config.go @@ -7,6 +7,7 @@ import ( "os" "time" + "github.com/hashicorp/consul/agent/checks" "github.com/hashicorp/consul/agent/consul/autopilot" "github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/lib" @@ -368,6 +369,9 @@ type Config struct { // warning and discard the remaining updates. CoordinateUpdateMaxBatches int + // CheckOutputMaxSize control the max size of output of checks + CheckOutputMaxSize int + // RPCHoldTimeout is how long an RPC can be "held" before it is errored. // This is used to paper over a loss of leadership by instead holding RPCs, // so that the caller experiences a slow response rather than an error. @@ -502,6 +506,8 @@ func DefaultConfig() *Config { CoordinateUpdateBatchSize: 128, CoordinateUpdateMaxBatches: 5, + CheckOutputMaxSize: checks.DefaultBufSize, + RPCRate: rate.Inf, RPCMaxBurst: 1000, diff --git a/agent/consul/leader.go b/agent/consul/leader.go index ce8ae41ab..f551c0e19 100644 --- a/agent/consul/leader.go +++ b/agent/consul/leader.go @@ -1363,6 +1363,17 @@ func (s *Server) handleAliveMember(member serf.Member) error { ID: structs.ConsulServiceID, Service: structs.ConsulServiceName, Port: parts.Port, + Weights: &structs.Weights{ + Passing: 1, + Warning: 1, + }, + Meta: map[string]string{ + "raft_version": strconv.Itoa(parts.RaftVersion), + "serf_protocol_current": strconv.FormatUint(uint64(member.ProtocolCur), 10), + "serf_protocol_min": strconv.FormatUint(uint64(member.ProtocolMin), 10), + "serf_protocol_max": strconv.FormatUint(uint64(member.ProtocolMax), 10), + "version": parts.Build.String(), + }, } // Attempt to join the consul server diff --git a/agent/http.go b/agent/http.go index acbfdb3e4..9025cc088 100644 --- a/agent/http.go +++ b/agent/http.go @@ -1,17 +1,21 @@ package agent import ( + "bytes" "encoding/json" "fmt" "io" + "io/ioutil" "net" "net/http" "net/http/pprof" "net/url" + "os" "reflect" "regexp" "strconv" "strings" + "text/template" "time" "github.com/NYTimes/gziphandler" @@ -83,6 +87,66 @@ type HTTPServer struct { // proto is filled by the agent to "http" or "https". proto string } +type templatedFile struct { + templated *bytes.Reader + name string + mode os.FileMode + modTime time.Time +} + +func newTemplatedFile(buf *bytes.Buffer, raw http.File) *templatedFile { + info, _ := raw.Stat() + return &templatedFile{ + templated: bytes.NewReader(buf.Bytes()), + name: info.Name(), + mode: info.Mode(), + modTime: info.ModTime(), + } +} + +func (t *templatedFile) Read(p []byte) (n int, err error) { + return t.templated.Read(p) +} + +func (t *templatedFile) Seek(offset int64, whence int) (int64, error) { + return t.templated.Seek(offset, whence) +} + +func (t *templatedFile) Close() error { + return nil +} + +func (t *templatedFile) Readdir(count int) ([]os.FileInfo, error) { + return nil, errors.New("not a directory") +} + +func (t *templatedFile) Stat() (os.FileInfo, error) { + return t, nil +} + +func (t *templatedFile) Name() string { + return t.name +} + +func (t *templatedFile) Size() int64 { + return int64(t.templated.Len()) +} + +func (t *templatedFile) Mode() os.FileMode { + return t.mode +} + +func (t *templatedFile) ModTime() time.Time { + return t.modTime +} + +func (t *templatedFile) IsDir() bool { + return false +} + +func (t *templatedFile) Sys() interface{} { + return nil +} type redirectFS struct { fs http.FileSystem @@ -96,6 +160,25 @@ func (fs *redirectFS) Open(name string) (http.File, error) { return file, err } +type templatedIndexFS struct { + fs http.FileSystem + ContentPath string +} + +func (fs *templatedIndexFS) Open(name string) (http.File, error) { + file, err := fs.fs.Open(name) + if err == nil && name == "/index.html" { + content, _ := ioutil.ReadAll(file) + file.Seek(0, 0) + t, _ := template.New("fmtedindex").Parse(string(content)) + var out bytes.Buffer + err = t.Execute(&out, fs) + file = newTemplatedFile(&out, file) + } + + return file, err +} + // endpoint is a Consul-specific HTTP handler that takes the usual arguments in // but returns a response object and error, both of which are handled in a // common manner by Consul's HTTP server. @@ -207,7 +290,6 @@ func (s *HTTPServer) handler(enableDebug bool) http.Handler { handleFuncMetrics(pattern, http.HandlerFunc(wrapper)) } - mux.HandleFunc("/", s.Index) for pattern, fn := range endpoints { thisFn := fn @@ -227,7 +309,6 @@ func (s *HTTPServer) handler(enableDebug bool) http.Handler { if s.IsUIEnabled() { var uifs http.FileSystem - // Use the custom UI dir if provided. if s.agent.config.UIDir != "" { uifs = http.Dir(s.agent.config.UIDir) @@ -235,10 +316,9 @@ func (s *HTTPServer) handler(enableDebug bool) http.Handler { fs := assetFS() uifs = fs } - uifs = &redirectFS{fs: uifs} - + uifs = &redirectFS{fs: &templatedIndexFS{fs: uifs, ContentPath: s.agent.config.UIContentPath}} mux.Handle("/robots.txt", http.FileServer(uifs)) - mux.Handle("/ui/", http.StripPrefix("/ui/", http.FileServer(uifs))) + mux.Handle(s.agent.config.UIContentPath, http.StripPrefix(s.agent.config.UIContentPath, http.FileServer(uifs))) } // Wrap the whole mux with a handler that bans URLs with non-printable @@ -489,7 +569,7 @@ func (s *HTTPServer) Index(resp http.ResponseWriter, req *http.Request) { } // Redirect to the UI endpoint - http.Redirect(resp, req, "/ui/", http.StatusMovedPermanently) // 301 + http.Redirect(resp, req, s.agent.config.UIContentPath, http.StatusMovedPermanently) // 301 } // decodeBody is used to decode a JSON request body diff --git a/agent/structs/check_definition.go b/agent/structs/check_definition.go index 4252b4449..aaadda7c7 100644 --- a/agent/structs/check_definition.go +++ b/agent/structs/check_definition.go @@ -37,6 +37,7 @@ type CheckDefinition struct { Timeout time.Duration TTL time.Duration DeregisterCriticalServiceAfter time.Duration + OutputMaxSize int } func (c *CheckDefinition) HealthCheck(node string) *HealthCheck { @@ -72,6 +73,7 @@ func (c *CheckDefinition) CheckType() *CheckType { GRPCUseTLS: c.GRPCUseTLS, Header: c.Header, Method: c.Method, + OutputMaxSize: c.OutputMaxSize, TCP: c.TCP, Interval: c.Interval, DockerContainerID: c.DockerContainerID, diff --git a/agent/structs/check_type.go b/agent/structs/check_type.go index 43b76057c..9b1b055da 100644 --- a/agent/structs/check_type.go +++ b/agent/structs/check_type.go @@ -45,6 +45,7 @@ type CheckType struct { // service, if any, to be deregistered if this check is critical for // longer than this duration. DeregisterCriticalServiceAfter time.Duration + OutputMaxSize int } type CheckTypes []*CheckType @@ -67,6 +68,9 @@ func (c *CheckType) Validate() error { if !intervalCheck && !c.IsAlias() && c.TTL <= 0 { return fmt.Errorf("TTL must be > 0 for TTL checks") } + if c.OutputMaxSize < 0 { + return fmt.Errorf("MaxOutputMaxSize must be positive") + } return nil } diff --git a/agent/structs/structs.go b/agent/structs/structs.go index 37a1671a7..80ba8b153 100644 --- a/agent/structs/structs.go +++ b/agent/structs/structs.go @@ -999,6 +999,7 @@ type HealthCheckDefinition struct { Method string `json:",omitempty"` TCP string `json:",omitempty"` Interval time.Duration `json:",omitempty"` + OutputMaxSize uint `json:",omitempty"` Timeout time.Duration `json:",omitempty"` DeregisterCriticalServiceAfter time.Duration `json:",omitempty"` } @@ -1007,11 +1008,13 @@ func (d *HealthCheckDefinition) MarshalJSON() ([]byte, error) { type Alias HealthCheckDefinition exported := &struct { Interval string `json:",omitempty"` + OutputMaxSize uint `json:",omitempty"` Timeout string `json:",omitempty"` DeregisterCriticalServiceAfter string `json:",omitempty"` *Alias }{ Interval: d.Interval.String(), + OutputMaxSize: d.OutputMaxSize, Timeout: d.Timeout.String(), DeregisterCriticalServiceAfter: d.DeregisterCriticalServiceAfter.String(), Alias: (*Alias)(d), diff --git a/build-support/functions/10-util.sh b/build-support/functions/10-util.sh index dcfc2561b..97463804b 100644 --- a/build-support/functions/10-util.sh +++ b/build-support/functions/10-util.sh @@ -974,7 +974,7 @@ function ui_version { return 1 fi - local ui_version="$(grep '$' "$1" | sed 's/^$/\1/')" || return 1 + local ui_version="$(grep '' "$1" | sed 's//\1/' | xargs)" || return 1 echo "$ui_version" return 0 } diff --git a/ui-v2/GNUmakefile b/ui-v2/GNUmakefile index 682077582..0b000172c 100644 --- a/ui-v2/GNUmakefile +++ b/ui-v2/GNUmakefile @@ -2,7 +2,13 @@ ROOT:=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) all: build -deps: node_modules +deps: node_modules clean + +clean: + rm -rf ./tmp + +build-ci: deps + yarn run build-ci --output-path=dist build: deps yarn run build @@ -19,6 +25,9 @@ test: deps test-view: deps yarn run test:view +test-parallel: deps + yarn test-parallel + lint: deps yarn run lint:js @@ -31,4 +40,4 @@ steps: node_modules: yarn.lock package.json yarn install -.PHONY: all deps build start test test-view lint format +.PHONY: all deps build start test test-view lint format clean diff --git a/ui-v2/app/index.html b/ui-v2/app/index.html index eb3753a26..75e518e9b 100644 --- a/ui-v2/app/index.html +++ b/ui-v2/app/index.html @@ -6,51 +6,12 @@ Consul by HashiCorp - + {{content-for "// all HTML content is populated via ./lib/startup/index"}} {{content-for "head"}} - - - - - - {{content-for "head-footer"}} - {{content-for "body"}} - - - - {{content-for "body-footer"}} diff --git a/ui-v2/lib/startup/index.js b/ui-v2/lib/startup/index.js index ad80f2e13..27d415904 100644 --- a/ui-v2/lib/startup/index.js +++ b/ui-v2/lib/startup/index.js @@ -3,16 +3,17 @@ module.exports = { name: 'startup', contentFor: function(type, config) { - const enterprise = config.CONSUL_BINARY_TYPE !== 'oss' && config.CONSUL_BINARY_TYPE !== ''; + const vars = { + appName: config.modulePrefix, + environment: config.environment, + rootURL: config.environment === 'production' ? '{{.ContentPath}}' : config.rootURL, + config: config, + }; switch (type) { case 'head': - return ``; + return require('./templates/head.html.js')(vars); case 'body': - return `${ - enterprise - ? `` - : '' - }`; + return require('./templates/body.html.js')(vars); case 'root-class': return 'ember-loading'; } diff --git a/ui-v2/lib/startup/templates/body.html.js b/ui-v2/lib/startup/templates/body.html.js new file mode 100644 index 000000000..36cc1b63c --- /dev/null +++ b/ui-v2/lib/startup/templates/body.html.js @@ -0,0 +1,42 @@ +module.exports = ({ appName, environment, rootURL, config }) => ` + + ${ + config.CONSUL_BINARY_TYPE !== 'oss' && config.CONSUL_BINARY_TYPE !== '' + ? `` + : `` + } + + ${environment === 'test' ? `` : ``} + + + + ${environment === 'test' ? `` : ``} +`; diff --git a/ui-v2/lib/startup/templates/head.html.js b/ui-v2/lib/startup/templates/head.html.js new file mode 100644 index 000000000..37d2bdab1 --- /dev/null +++ b/ui-v2/lib/startup/templates/head.html.js @@ -0,0 +1,37 @@ +module.exports = ({ appName, environment, rootURL, config }) => ` + + + + + + + ${ + environment === 'test' ? `` : `` + } +`; diff --git a/ui-v2/tests/index.html b/ui-v2/tests/index.html index bb4c9e254..9fccbf208 100644 --- a/ui-v2/tests/index.html +++ b/ui-v2/tests/index.html @@ -6,14 +6,9 @@ ConsulUi Tests - {{content-for "head"}} {{content-for "test-head"}} - - - - {{content-for "head-footer"}} {{content-for "test-head-footer"}} @@ -22,10 +17,6 @@ {{content-for "test-body"}} - - - - {{content-for "body-footer"}} {{content-for "test-body-footer"}} diff --git a/website/source/api/agent/check.html.md b/website/source/api/agent/check.html.md index dfb04d853..a5ad23a5e 100644 --- a/website/source/api/agent/check.html.md +++ b/website/source/api/agent/check.html.md @@ -183,6 +183,12 @@ The table below shows this endpoint's support for case of a Script, HTTP, TCP, or gRPC check. Can be specified in the form of "10s" or "5m" (i.e., 10 seconds or 5 minutes, respectively). +- `OutputMaxSize` `(positive int: 4096)` - Allow to put a maximum size of text + for the given check. This value must be greater than 0, by default, the value + is 4k. + The value can be further limited for all checks of a given agent using the + `check_output_max_size` flag in the agent. + - `TLSSkipVerify` `(bool: false)` - Specifies if the certificate for an HTTPS check should not be verified. diff --git a/website/source/docs/agent/options.html.md b/website/source/docs/agent/options.html.md index 10905bd95..12b32b1b9 100644 --- a/website/source/docs/agent/options.html.md +++ b/website/source/docs/agent/options.html.md @@ -132,6 +132,14 @@ will exit with an error at startup. [go-sockaddr](https://godoc.org/github.com/hashicorp/go-sockaddr/template) template +* `-check_output_max_size` - + Override the default limit of 4k for maximum size of checks, this is a positive + value. + By limiting this size, it allows to put less pressure on Consul servers when + many checks are having a very large output in their checks. + In order to completely disable check output capture, it is possible to + use `discard_check_output`. + * `-client` - The address to which Consul will bind client interfaces, including the HTTP and DNS servers. By default, this is "127.0.0.1", allowing only loopback connections. In Consul @@ -457,6 +465,8 @@ will exit with an error at startup. the Web UI resources for Consul. This will automatically enable the Web UI. The directory must be readable to the agent. Starting with Consul version 0.7.0 and later, the Web UI assets are included in the binary so this flag is no longer necessary; specifying only the `-ui` flag is enough to enable the Web UI. Specifying both the '-ui' and '-ui-dir' flags will result in an error. +* `-ui-content-path` - This flag provides the option to change the path the Consul UI loads from and will be displayed in the browser. By default, the path is `/ui/`, for example `http://localhost:8500/ui/`. Only alphanumerics, `-`, and `_` are allowed in a custom path. `/v1/` is not allowed as it would overwrite the API endpoint. + ## Configuration Files In addition to the command-line options, configuration can be put into diff --git a/website/source/docs/connect/proxies/envoy.md b/website/source/docs/connect/proxies/envoy.md index d07109d3c..5095b695c 100644 --- a/website/source/docs/connect/proxies/envoy.md +++ b/website/source/docs/connect/proxies/envoy.md @@ -216,15 +216,15 @@ definition](/docs/connect/registration/service-registration.html) or ## Advanced Configuration -To support more flexibility when configuring Envoy, several "lower-level" options exist that -exist that require knowledge of Envoy's configuration format. -Many allow configuring a subsection of either the bootstrap or +To support more flexibility when configuring Envoy, several "lower-level" options exist +that require knowledge of Envoy's configuration format. +Many options allow configuring a subsection of either the bootstrap or dynamic configuration using your own custom protobuf config. We separate these into two sets, [Advanced Bootstrap Options](#advanced-bootstrap-options) and [Escape Hatch -Overrides](#escape-hatch-overrides). Both require writing Envoy config in it's -protobuf JSON encoding. Advanced options are smaller chunks that might +Overrides](#escape-hatch-overrides). Both require writing Envoy config in the +protobuf JSON encoding. Advanced options cover smaller chunks that might commonly need to be set for tasks like configuring tracing. In contrast, escape hatches give almost complete control over the proxy setup, but require operators to manually code the entire configuration in protobuf JSON.