From 1d78a293ed8362fd13b62ca6563decc12034a333 Mon Sep 17 00:00:00 2001 From: Tim Gross Date: Mon, 22 Nov 2021 14:04:45 -0500 Subject: [PATCH 1/4] config: UI configuration block with Vault/Consul links Add `ui` block to agent configuration to enable/disable the web UI and provide the web UI with links to Vault/Consul. --- command/agent/config.go | 12 ++ command/agent/http.go | 10 +- nomad/structs/config/ui.go | 130 ++++++++++++++++++++++ nomad/structs/config/ui_test.go | 78 +++++++++++++ website/content/docs/configuration/ui.mdx | 69 ++++++++++++ website/data/docs-nav-data.json | 4 + 6 files changed, 302 insertions(+), 1 deletion(-) create mode 100644 nomad/structs/config/ui.go create mode 100644 nomad/structs/config/ui_test.go create mode 100644 website/content/docs/configuration/ui.mdx diff --git a/command/agent/config.go b/command/agent/config.go index d3ab1e332b8..2a18f0e2a8a 100644 --- a/command/agent/config.go +++ b/command/agent/config.go @@ -132,6 +132,9 @@ type Config struct { // parameters necessary to derive tokens. Vault *config.VaultConfig `hcl:"vault"` + // UI is used to configure the web UI + UI *config.UIConfig `hcl:"ui"` + // NomadConfig is used to override the default config. // This is largely used for testing purposes. NomadConfig *nomad.Config `hcl:"-" json:"-"` @@ -926,6 +929,7 @@ func DefaultConfig() *Config { AdvertiseAddrs: &AdvertiseAddrs{}, Consul: config.DefaultConsulConfig(), Vault: config.DefaultVaultConfig(), + UI: config.DefaultUIConfig(), Client: &ClientConfig{ Enabled: false, MaxKillTimeout: "30s", @@ -1164,6 +1168,14 @@ func (c *Config) Merge(b *Config) *Config { result.Vault = result.Vault.Merge(b.Vault) } + // Apply the UI Configuration + if result.UI == nil && b.UI != nil { + uiConfig := *b.UI + result.UI = &uiConfig + } else if b.UI != nil { + result.UI = result.UI.Merge(b.UI) + } + // Apply the sentinel config if result.Sentinel == nil && b.Sentinel != nil { server := *b.Sentinel diff --git a/command/agent/http.go b/command/agent/http.go index 7124737fe14..83ea36e5d90 100644 --- a/command/agent/http.go +++ b/command/agent/http.go @@ -336,13 +336,21 @@ func (s *HTTPServer) registerHandlers(enableDebug bool) { s.mux.HandleFunc("/v1/namespace", s.wrap(s.NamespaceCreateRequest)) s.mux.HandleFunc("/v1/namespace/", s.wrap(s.NamespaceSpecificRequest)) - if uiEnabled { + uiConfigEnabled := s.agent.config.UI != nil && s.agent.config.UI.Enabled + + if uiEnabled && uiConfigEnabled { s.mux.Handle("/ui/", http.StripPrefix("/ui/", s.handleUI(http.FileServer(&UIAssetWrapper{FileSystem: assetFS()})))) + s.logger.Debug("UI is enabled") } else { // Write the stubHTML s.mux.HandleFunc("/ui/", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte(stubHTML)) }) + if uiEnabled && !uiConfigEnabled { + s.logger.Warn("UI is disabled") + } else { + s.logger.Debug("UI is disabled in this build") + } } s.mux.Handle("/", s.handleRootFallthrough()) diff --git a/nomad/structs/config/ui.go b/nomad/structs/config/ui.go new file mode 100644 index 00000000000..e7e97407533 --- /dev/null +++ b/nomad/structs/config/ui.go @@ -0,0 +1,130 @@ +package config + +// UIConfig contains the operator configuration of the web UI +// Note: +// before extending this configuration, consider reviewing NMD-125 +type UIConfig struct { + + // Enabled is used to enable the web UI + Enabled bool `hcl:"enabled"` + + // Consul configures deep links for Consul UI + Consul *ConsulUIConfig `hcl:"consul"` + + // Vault configures deep links for Vault UI + Vault *VaultUIConfig `hcl:"vault"` +} + +// ConsulUIConfig configures deep links to this cluster's Consul UI +type ConsulUIConfig struct { + + // BaseURL provides the full base URL, ex: + // https://consul.example.com:8500/ui/ + BaseURL string `hcl:"base_url"` +} + +// VaultUIConfig configures deep links to this cluster's Vault UI +type VaultUIConfig struct { + // BaseURL provides the full base URL, ex: + // https://vault.example.com:8200/ui/ + BaseURL string `hcl:"base_url"` +} + +// DefaultUIConfig returns the canonical defaults for the Nomad +// `ui` configuration. +func DefaultUIConfig() *UIConfig { + return &UIConfig{ + Enabled: true, + Consul: &ConsulUIConfig{}, + Vault: &VaultUIConfig{}, + } +} + +// Copy returns a copy of this UI config. +func (old *UIConfig) Copy() *UIConfig { + if old == nil { + return nil + } + + nc := new(UIConfig) + *nc = *old + + if old.Consul != nil { + nc.Consul = old.Consul.Copy() + } + if old.Vault != nil { + nc.Vault = old.Vault.Copy() + } + return nc +} + +// Merge returns a new UI configuration by merging another UI +// configuration into this one +func (this *UIConfig) Merge(other *UIConfig) *UIConfig { + result := this.Copy() + if other == nil { + return result + } + + result.Enabled = other.Enabled + result.Consul = result.Consul.Merge(other.Consul) + result.Vault = result.Vault.Merge(other.Vault) + + return result +} + +// Copy returns a copy of this Consul UI config. +func (old *ConsulUIConfig) Copy() *ConsulUIConfig { + if old == nil { + return nil + } + + nc := new(ConsulUIConfig) + *nc = *old + return nc +} + +// Merge returns a new Consul UI configuration by merging another Consul UI +// configuration into this one +func (this *ConsulUIConfig) Merge(other *ConsulUIConfig) *ConsulUIConfig { + result := this.Copy() + if result == nil { + result = &ConsulUIConfig{} + } + if other == nil { + return result + } + + if other.BaseURL != "" { + result.BaseURL = other.BaseURL + } + return result +} + +// Copy returns a copy of this Vault UI config. +func (old *VaultUIConfig) Copy() *VaultUIConfig { + if old == nil { + return nil + } + + nc := new(VaultUIConfig) + *nc = *old + return nc +} + +// Merge returns a new Vault UI configuration by merging another Vault UI +// configuration into this one +func (this *VaultUIConfig) Merge(other *VaultUIConfig) *VaultUIConfig { + result := this.Copy() + if result == nil { + result = &VaultUIConfig{} + } + if other == nil { + return result + } + + if other.BaseURL != "" { + result.BaseURL = other.BaseURL + } + return result +} diff --git a/nomad/structs/config/ui_test.go b/nomad/structs/config/ui_test.go new file mode 100644 index 00000000000..107bbd9b097 --- /dev/null +++ b/nomad/structs/config/ui_test.go @@ -0,0 +1,78 @@ +package config + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestUIConfig_Merge(t *testing.T) { + + fullConfig := &UIConfig{ + Enabled: true, + Consul: &ConsulUIConfig{ + BaseURL: "http://consul.example.com:8500", + }, + Vault: &VaultUIConfig{ + BaseURL: "http://vault.example.com:8200", + }, + } + + testCases := []struct { + name string + left *UIConfig + right *UIConfig + expect *UIConfig + }{ + { + name: "merge onto empty config", + left: &UIConfig{}, + right: fullConfig, + expect: fullConfig, + }, + { + name: "merge in a nil config", + left: fullConfig, + right: nil, + expect: fullConfig, + }, + { + name: "merge onto zero-values", + left: &UIConfig{ + Enabled: false, + Consul: &ConsulUIConfig{ + BaseURL: "http://consul-other.example.com:8500", + }, + }, + right: fullConfig, + expect: fullConfig, + }, + { + name: "merge from zero-values", + left: &UIConfig{ + Enabled: true, + Consul: &ConsulUIConfig{ + BaseURL: "http://consul-other.example.com:8500", + }, + }, + right: &UIConfig{}, + expect: &UIConfig{ + Enabled: false, + Consul: &ConsulUIConfig{ + BaseURL: "http://consul-other.example.com:8500", + }, + Vault: &VaultUIConfig{}, + }, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + result := tc.left.Merge(tc.right) + require.Equal(t, tc.expect, result) + }) + } + +} diff --git a/website/content/docs/configuration/ui.mdx b/website/content/docs/configuration/ui.mdx new file mode 100644 index 00000000000..e58a346589e --- /dev/null +++ b/website/content/docs/configuration/ui.mdx @@ -0,0 +1,69 @@ +--- +layout: docs +page_title: ui Stanza - Agent Configuration +description: |- + The "ui" stanza configures the Nomad agent's web UI. + +--- + +# `ui` Stanza + + + +The `ui` stanza configures the Nomad agent's [web UI].. + +```hcl +ui { + enabled = true + + consul { + base_url = "https://consul.example.com:8500/ui" + } + + vault { + base_url = "https://vault.example.com:8200/ui" + } +} +``` + +A default `ui` stanza is automatically merged with all Nomad agent +configurations. Note that the UI can be served from any Nomad agent, +and the configuration is individual to each agent. + +- `enabled` `(bool: true)` - Specifies whether the web UI is + enabled. If disabled, the `/ui/` path will return an empty web page. + +- `consul` ([Consul]: nil) - Configures integrations + between the Nomad web UI and the Consul web UI. + +- `vault` ([Vault]: nil) - Configures integrations + between the Nomad web UI and the Vault web UI. + +## `consul` Parameters + +- `base_url` `(string: "")` - Specifies the full base URL to a Consul + web UI (for example: `https://consul.example.com:8500/ui`. This URL + is used to build links from the Nomad web UI to a Consul web + UI. Note that this URL will not typically be the same one used for + the agent's [`consul.address`]; the `consul.address` is the URL used + by the Nomad to communicate with Consul, whereas the + `ui.consul.base_url` is the URL you'll visit in your browser. If + this field is omitted, this integration will be disabled. + +## `vault` Parameters + +- `base_url` `(string: "")` - Specifies the full base URL to a Vault + web UI (for example: `https://vault.example.com:8200/ui`. This URL + is used to build links from the Nomad web UI to a Vault web + UI. Note that this URL will not typically be the same one used for + the agent's [`vault.address`]; the `vault.address` is the URL used + by the Nomad to communicate with Vault, whereas the + `ui.vault.base_url` is the URL you'll visit in your browser. If + this field is omitted, this integration will be disabled. + + +[web UI]: https://learn.hashicorp.com/collections/nomad/web-ui +[Consul]: /docs/configuration/ui#consul-parameters +[Vault]: /docs/configuration/ui#vault-parameters +[`consul.address`]: /docs/configuration/consul#address +[`vault.address`]: /docs/configuration/vault#address diff --git a/website/data/docs-nav-data.json b/website/data/docs-nav-data.json index 9e4463f9a55..62101638b79 100644 --- a/website/data/docs-nav-data.json +++ b/website/data/docs-nav-data.json @@ -203,6 +203,10 @@ { "title": "vault", "path": "configuration/vault" + }, + { + "title": "ui", + "path": "configuration/ui" } ] }, From 49993047cde2bdc7ff448ed5e6035f0c5b4cf548 Mon Sep 17 00:00:00 2001 From: Tim Gross Date: Wed, 24 Nov 2021 10:45:18 -0500 Subject: [PATCH 2/4] Apply suggestions from code review Co-authored-by: Luiz Aoqui --- website/content/docs/configuration/ui.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/content/docs/configuration/ui.mdx b/website/content/docs/configuration/ui.mdx index e58a346589e..ef887f5bcc3 100644 --- a/website/content/docs/configuration/ui.mdx +++ b/website/content/docs/configuration/ui.mdx @@ -10,7 +10,7 @@ description: |- -The `ui` stanza configures the Nomad agent's [web UI].. +The `ui` stanza configures the Nomad agent's [web UI]. ```hcl ui { @@ -29,7 +29,7 @@ ui { A default `ui` stanza is automatically merged with all Nomad agent configurations. Note that the UI can be served from any Nomad agent, and the configuration is individual to each agent. - +## `ui` Parameters - `enabled` `(bool: true)` - Specifies whether the web UI is enabled. If disabled, the `/ui/` path will return an empty web page. From 57f045d22a1954f3ceacc30b3827edc210bf0d91 Mon Sep 17 00:00:00 2001 From: Tim Gross Date: Wed, 24 Nov 2021 10:52:03 -0500 Subject: [PATCH 3/4] website ordering fixup --- website/data/docs-nav-data.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/website/data/docs-nav-data.json b/website/data/docs-nav-data.json index 62101638b79..fb07905070b 100644 --- a/website/data/docs-nav-data.json +++ b/website/data/docs-nav-data.json @@ -200,13 +200,13 @@ "title": "tls", "path": "configuration/tls" }, - { - "title": "vault", - "path": "configuration/vault" - }, { "title": "ui", "path": "configuration/ui" + }, + { + "title": "vault", + "path": "configuration/vault" } ] }, From 8006bd6876ac6079c23aab03f49b29615e917c77 Mon Sep 17 00:00:00 2001 From: Tim Gross Date: Wed, 24 Nov 2021 11:04:45 -0500 Subject: [PATCH 4/4] display message in stub when UI is disabled --- command/agent/http.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/command/agent/http.go b/command/agent/http.go index 83ea36e5d90..6371873d0e6 100644 --- a/command/agent/http.go +++ b/command/agent/http.go @@ -47,8 +47,9 @@ var ( // Set to false by stub_asset if the ui build tag isn't enabled uiEnabled = true - // Overridden if the ui build tag isn't enabled - stubHTML = "" + // Displayed when ui is disabled, but overridden if the ui build + // tag isn't enabled + stubHTML = "

Nomad UI is disabled

" // allowCORS sets permissive CORS headers for a handler allowCORS = cors.New(cors.Options{