Skip to content

Commit

Permalink
config: UI configuration block with Vault/Consul links (#11555)
Browse files Browse the repository at this point in the history
Add `ui` block to agent configuration to enable/disable the web UI and
provide the web UI with links to Vault/Consul.
  • Loading branch information
tgross committed Nov 24, 2021
1 parent 504e939 commit 1160817
Show file tree
Hide file tree
Showing 6 changed files with 305 additions and 3 deletions.
12 changes: 12 additions & 0 deletions command/agent/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:"-"`
Expand Down Expand Up @@ -926,6 +929,7 @@ func DefaultConfig() *Config {
AdvertiseAddrs: &AdvertiseAddrs{},
Consul: config.DefaultConsulConfig(),
Vault: config.DefaultVaultConfig(),
UI: config.DefaultUIConfig(),
Client: &ClientConfig{
Enabled: false,
MaxKillTimeout: "30s",
Expand Down Expand Up @@ -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
Expand Down
15 changes: 12 additions & 3 deletions command/agent/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 = "<html><p>Nomad UI is disabled</p></html>"

// allowCORS sets permissive CORS headers for a handler
allowCORS = cors.New(cors.Options{
Expand Down Expand Up @@ -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())

Expand Down
130 changes: 130 additions & 0 deletions nomad/structs/config/ui.go
Original file line number Diff line number Diff line change
@@ -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
}
78 changes: 78 additions & 0 deletions nomad/structs/config/ui_test.go
Original file line number Diff line number Diff line change
@@ -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)
})
}

}
69 changes: 69 additions & 0 deletions website/content/docs/configuration/ui.mdx
Original file line number Diff line number Diff line change
@@ -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

<Placement groups={['ui']} />

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.
## `ui` Parameters
- `enabled` `(bool: true)` - Specifies whether the web UI is
enabled. If disabled, the `/ui/` path will return an empty web page.

- `consul` <code>([Consul]: nil)</code> - Configures integrations
between the Nomad web UI and the Consul web UI.

- `vault` <code>([Vault]: nil)</code> - 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
4 changes: 4 additions & 0 deletions website/data/docs-nav-data.json
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,10 @@
"title": "tls",
"path": "configuration/tls"
},
{
"title": "ui",
"path": "configuration/ui"
},
{
"title": "vault",
"path": "configuration/vault"
Expand Down

0 comments on commit 1160817

Please sign in to comment.