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..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{ @@ -336,13 +337,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..ef887f5bcc3 --- /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 + +([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..fb07905070b 100644
--- a/website/data/docs-nav-data.json
+++ b/website/data/docs-nav-data.json
@@ -200,6 +200,10 @@
"title": "tls",
"path": "configuration/tls"
},
+ {
+ "title": "ui",
+ "path": "configuration/ui"
+ },
{
"title": "vault",
"path": "configuration/vault"