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 @@