Fix -ui-content-path without regex (#9569)

* Add templating to inject JSON into an application/json script tag

Plus an external script in order to pick it out and inject the values we
need injecting into ember's environment meta tag.

The UI still uses env style naming (CONSUL_*) but we uses the new style
JSON/golang props behind the scenes.

Co-authored-by: Paul Banks <banks@banksco.de>
This commit is contained in:
John Cowen 2021-01-20 18:40:46 +00:00 committed by GitHub
parent 921c2a2bd8
commit d3ecb6d7a0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 281 additions and 219 deletions

3
.changelog/9569.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:bug
ui: Fixes an issue with setting -ui-content-path flag/config
```

File diff suppressed because one or more lines are too long

View File

@ -6,10 +6,8 @@ import (
"fmt"
"io/ioutil"
"net/http"
"net/url"
"os"
"path"
"regexp"
"strings"
"sync/atomic"
"text/template"
@ -244,54 +242,13 @@ func (h *Handler) renderIndex(cfg *config.RuntimeConfig, fs http.FileSystem) ([]
}
}
// Sadly we can't perform all the replacements we need with Go template
// because some of them end up being rendered into an escaped json encoded
// meta tag by Ember build which messes up the Go template tags. After a few
// iterations of grossness, this seemed like the least bad for now. note we
// have to match the encoded double quotes around the JSON string value that
// is there as a placeholder so the end result is an actual JSON bool not a
// string containing "false" etc.
re := regexp.MustCompile(`%22__RUNTIME_(BOOL|STRING)_([A-Za-z0-9-_]+)__%22`)
content = []byte(re.ReplaceAllStringFunc(string(content), func(str string) string {
// Trim the prefix and suffix
pair := strings.TrimSuffix(strings.TrimPrefix(str, "%22__RUNTIME_"), "__%22")
parts := strings.SplitN(pair, "_", 2)
switch parts[0] {
case "BOOL":
if v, ok := tplData[parts[1]].(bool); ok && v {
return "true"
}
return "false"
case "STRING":
if v, ok := tplData[parts[1]].(string); ok {
if bs, err := json.Marshal(v); err == nil {
return url.PathEscape(string(bs))
}
// Error!
h.logger.Error("Encoding JSON value for UI template failed",
"placeholder", str,
"value", v,
)
// Fall through to return the empty string to make JSON parse
}
return `""` // Empty JSON string
}
// Unknown type is likely an error
h.logger.Error("Unknown placeholder type in UI template",
"placeholder", str,
)
// Return a literal empty string so the JSON still parses
return `""`
}))
tpl, err := template.New("index").Funcs(template.FuncMap{
"jsonEncodeAndEscape": func(data map[string]interface{}) (string, error) {
bs, err := json.Marshal(data)
"jsonEncode": func(data map[string]interface{}) (string, error) {
bs, err := json.MarshalIndent(data, "", " ")
if err != nil {
return "", fmt.Errorf("failed jsonEncodeAndEscape: %w", err)
return "", fmt.Errorf("failed jsonEncode: %w", err)
}
return url.PathEscape(string(bs)), nil
return string(bs), nil
},
}).Parse(string(content))
if err != nil {

View File

@ -1,16 +1,18 @@
package uiserver
import (
"encoding/json"
"bytes"
"io"
"io/ioutil"
"net/http"
"net/http/httptest"
"net/url"
"os"
"path/filepath"
"regexp"
"strings"
"testing"
"golang.org/x/net/html"
"github.com/hashicorp/consul/agent/config"
"github.com/hashicorp/consul/sdk/testutil"
"github.com/stretchr/testify/require"
@ -34,14 +36,16 @@ func TestUIServerIndex(t *testing.T) {
path: "/", // Note /index.html redirects to /
wantStatus: http.StatusOK,
wantContains: []string{"<!-- CONSUL_VERSION:"},
wantNotContains: []string{
"__RUNTIME_BOOL_",
"__RUNTIME_STRING_",
},
wantEnv: map[string]interface{}{
"CONSUL_ACLS_ENABLED": false,
"CONSUL_DATACENTER_LOCAL": "dc1",
},
wantUICfgJSON: `{
"ACLsEnabled": false,
"LocalDatacenter": "dc1",
"ContentPath": "/ui/",
"UIConfig": {
"metrics_provider": "",
"metrics_proxy_enabled": false,
"dashboard_url_templates": null
}
}`,
},
{
// We do this redirect just for UI dir since the app is a single page app
@ -64,24 +68,18 @@ func TestUIServerIndex(t *testing.T) {
wantContains: []string{
"<!-- CONSUL_VERSION:",
},
wantNotContains: []string{
// This is a quick check to be sure that we actually URL encoded the
// JSON ui settings too. The assertions below could pass just fine even
// if we got that wrong because the decode would be a no-op if it wasn't
// URL encoded. But this just ensures that we don't see the raw values
// in the output because the quotes should be encoded.
`"a-very-unlikely-string"`,
},
wantEnv: map[string]interface{}{
"CONSUL_ACLS_ENABLED": false,
},
wantUICfgJSON: `{
"metrics_provider": "foo",
"metrics_provider_options": {
"a-very-unlikely-string":1
},
"metrics_proxy_enabled": false,
"dashboard_url_templates": null
"ACLsEnabled": false,
"LocalDatacenter": "dc1",
"ContentPath": "/ui/",
"UIConfig": {
"metrics_provider": "foo",
"metrics_provider_options": {
"a-very-unlikely-string":1
},
"metrics_proxy_enabled": false,
"dashboard_url_templates": null
}
}`,
},
{
@ -90,9 +88,16 @@ func TestUIServerIndex(t *testing.T) {
path: "/",
wantStatus: http.StatusOK,
wantContains: []string{"<!-- CONSUL_VERSION:"},
wantEnv: map[string]interface{}{
"CONSUL_ACLS_ENABLED": true,
},
wantUICfgJSON: `{
"ACLsEnabled": true,
"LocalDatacenter": "dc1",
"ContentPath": "/ui/",
"UIConfig": {
"metrics_provider": "",
"metrics_proxy_enabled": false,
"dashboard_url_templates": null
}
}`,
},
{
name: "external transformation",
@ -110,13 +115,16 @@ func TestUIServerIndex(t *testing.T) {
wantContains: []string{
"<!-- CONSUL_VERSION:",
},
wantEnv: map[string]interface{}{
"CONSUL_SSO_ENABLED": true,
},
wantUICfgJSON: `{
"metrics_provider": "bar",
"metrics_proxy_enabled": false,
"dashboard_url_templates": null
"ACLsEnabled": false,
"SSOEnabled": true,
"LocalDatacenter": "dc1",
"ContentPath": "/ui/",
"UIConfig": {
"metrics_provider": "bar",
"metrics_proxy_enabled": false,
"dashboard_url_templates": null
}
}`,
},
{
@ -148,13 +156,6 @@ func TestUIServerIndex(t *testing.T) {
for _, want := range tc.wantContains {
require.Contains(t, rec.Body.String(), want)
}
for _, wantNot := range tc.wantNotContains {
require.NotContains(t, rec.Body.String(), wantNot)
}
env := extractEnv(t, rec.Body.String())
for k, v := range tc.wantEnv {
require.Equal(t, v, env[k])
}
if tc.wantUICfgJSON != "" {
require.JSONEq(t, tc.wantUICfgJSON, extractUIConfig(t, rec.Body.String()))
}
@ -162,41 +163,48 @@ func TestUIServerIndex(t *testing.T) {
}
}
func extractMetaJSON(t *testing.T, name, content string) string {
func extractApplicationJSON(t *testing.T, attrName, content string) string {
t.Helper()
// Find and extract the env meta tag. Why yes I _am_ using regexp to parse
// HTML thanks for asking. In this case it's HTML with a very limited format
// so I don't feel too bad but maybe I should.
// https://stackoverflow.com/questions/1732348/regex-match-open-tags-except-xhtml-self-contained-tags/1732454#1732454
re := regexp.MustCompile(`<meta name="` + name + `+" content="([^"]*)"`)
var scriptContent *html.Node
var find func(node *html.Node)
matches := re.FindStringSubmatch(content)
require.Len(t, matches, 2, "didn't find the %s meta tag", name)
// Recurse down the tree and pick out <script attrName=ourAttrName>
find = func(node *html.Node) {
if node.Type == html.ElementNode && node.Data == "script" {
for i := 0; i < len(node.Attr); i++ {
attr := node.Attr[i]
if attr.Key == attrName {
// find the script and save off the content, which in this case is
// the JSON we are looking for, once we have it finish up
scriptContent = node.FirstChild
return
}
}
}
for child := node.FirstChild; child != nil; child = child.NextSibling {
find(child)
}
}
// Unescape the JSON
jsonStr, err := url.PathUnescape(matches[1])
doc, err := html.Parse(strings.NewReader(content))
require.NoError(t, err)
find(doc)
var buf bytes.Buffer
w := io.Writer(&buf)
renderErr := html.Render(w, scriptContent)
require.NoError(t, renderErr)
jsonStr := html.UnescapeString(buf.String())
return jsonStr
}
func extractEnv(t *testing.T, content string) map[string]interface{} {
t.Helper()
js := extractMetaJSON(t, "consul-ui/config/environment", content)
var env map[string]interface{}
err := json.Unmarshal([]byte(js), &env)
require.NoError(t, err)
return env
}
func extractUIConfig(t *testing.T, content string) string {
t.Helper()
return extractMetaJSON(t, "consul-ui/ui_config", content)
return extractApplicationJSON(t, "data-consul-ui-config", content)
}
type cfgFunc func(cfg *config.RuntimeConfig)

View File

@ -43,7 +43,10 @@ export default function(config = {}, win = window, doc = document) {
return {};
}
};
const ui_config = JSON.parse(unescape(doc.getElementsByName('consul-ui/ui_config')[0].content));
const operatorConfig = JSON.parse(
doc.querySelector(`[data-${config.modulePrefix}-config]`).textContent
);
const ui_config = operatorConfig.UIConfig || {};
const scripts = doc.getElementsByTagName('script');
// we use the currently executing script as a reference
// to figure out where we are for other things such as
@ -57,6 +60,18 @@ export default function(config = {}, win = window, doc = document) {
const operator = function(str, env) {
let protocol, dashboards, provider, proxy;
switch (str) {
case 'CONSUL_NSPACES_ENABLED':
return typeof operatorConfig.NamespacesEnabled === 'undefined'
? false
: operatorConfig.NamespacesEnabled;
case 'CONSUL_SSO_ENABLED':
return typeof operatorConfig.SSOEnabled === 'undefined' ? false : operatorConfig.SSOEnabled;
case 'CONSUL_ACLS_ENABLED':
return typeof operatorConfig.ACLsEnabled === 'undefined'
? false
: operatorConfig.ACLsEnabled;
case 'CONSUL_DATACENTER_LOCAL':
return operatorConfig.LocalDatacenter;
case 'CONSUL_UI_CONFIG':
dashboards = {};
provider = env('CONSUL_METRICS_PROVIDER');
@ -155,6 +170,10 @@ export default function(config = {}, win = window, doc = document) {
// these are strings
return user(str) || ui(str);
case 'CONSUL_UI_CONFIG':
case 'CONSUL_DATACENTER_LOCAL':
case 'CONSUL_ACLS_ENABLED':
case 'CONSUL_NSPACES_ENABLED':
case 'CONSUL_SSO_ENABLED':
case 'CONSUL_METRICS_PROVIDER':
case 'CONSUL_METRICS_PROXY_ENABLE':
case 'CONSUL_SERVICE_DASHBOARD_URL':

View File

@ -92,11 +92,13 @@ module.exports = function(environment, $ = process.env) {
CONSUL_UI_DISABLE_ANCHOR_SELECTION: env('CONSUL_UI_DISABLE_ANCHOR_SELECTION', false),
// The following variables are runtime variables that are overwritten when
// the go binary services the index.html page
CONSUL_ACLS_ENABLED: false,
CONSUL_NSPACES_ENABLED: false,
CONSUL_SSO_ENABLED: false,
CONSUL_DATACENTER_LOCAL: env('CONSUL_DATACENTER_LOCAL', 'dc1'),
// the go binary serves the index.html page
operatorConfig: {
ACLsEnabled: false,
NamespacesEnabled: false,
SSOEnabled: false,
LocalDatacenter: env('CONSUL_DATACENTER_LOCAL', 'dc1'),
},
// Static variables used in multiple places throughout the UI
CONSUL_HOME_URL: 'https://www.consul.io',
@ -112,9 +114,12 @@ module.exports = function(environment, $ = process.env) {
locationType: 'none',
// During testing ACLs default to being turned on
CONSUL_ACLS_ENABLED: env('CONSUL_ACLS_ENABLED', true),
CONSUL_NSPACES_ENABLED: env('CONSUL_NSPACES_ENABLED', false),
CONSUL_SSO_ENABLED: env('CONSUL_SSO_ENABLED', false),
operatorConfig: {
ACLsEnabled: env('CONSUL_ACLS_ENABLED', true),
NamespacesEnabled: env('CONSUL_NSPACES_ENABLED', false),
SSOEnabled: env('CONSUL_SSO_ENABLED', false),
LocalDatacenter: env('CONSUL_DATACENTER_LOCAL', 'dc1'),
},
'@hashicorp/ember-cli-api-double': {
'auto-import': false,
@ -143,9 +148,12 @@ module.exports = function(environment, $ = process.env) {
// different staging sites can be built with certain features disabled
// by setting an environment variable to 0 during building (e.g.
// CONSUL_NSPACES_ENABLED=0 make build)
CONSUL_ACLS_ENABLED: env('CONSUL_ACLS_ENABLED', true),
CONSUL_NSPACES_ENABLED: env('CONSUL_NSPACES_ENABLED', true),
CONSUL_SSO_ENABLED: env('CONSUL_SSO_ENABLED', true),
operatorConfig: {
ACLsEnabled: env('CONSUL_ACLS_ENABLED', true),
NamespacesEnabled: env('CONSUL_NSPACES_ENABLED', true),
SSOEnabled: env('CONSUL_SSO_ENABLED', true),
LocalDatacenter: env('CONSUL_DATACENTER_LOCAL', 'dc1'),
},
'@hashicorp/ember-cli-api-double': {
enabled: true,
@ -157,22 +165,9 @@ module.exports = function(environment, $ = process.env) {
break;
case environment === 'production':
ENV = Object.assign({}, ENV, {
// These values are placeholders that are replaced when Consul renders
// the index.html based on runtime config. They can't use Go template
// syntax since this object ends up JSON and URLencoded in an HTML meta
// tag which obscured the Go template tag syntax.
//
// __RUNTIME_BOOL_Xxxx__ will be replaced with either "true" or "false"
// depending on whether the named variable is true or false in the data
// returned from `uiTemplateDataFromConfig`.
//
// __RUNTIME_STRING_Xxxx__ will be replaced with the literal string in
// the named variable in the data returned from
// `uiTemplateDataFromConfig`. It may be empty.
CONSUL_ACLS_ENABLED: '__RUNTIME_BOOL_ACLsEnabled__',
CONSUL_SSO_ENABLED: '__RUNTIME_BOOL_SSOEnabled__',
CONSUL_NSPACES_ENABLED: '__RUNTIME_BOOL_NamespacesEnabled__',
CONSUL_DATACENTER_LOCAL: '__RUNTIME_STRING_LocalDatacenter__',
// in production operatorConfig is populated at consul runtime from
// operator configuration
operatorConfig: {},
});
break;
}

View File

@ -116,6 +116,9 @@ module.exports = function(defaults) {
app.import('vendor/metrics-providers/prometheus.js', {
outputFile: 'assets/metrics-providers/prometheus.js',
});
app.import('vendor/init.js', {
outputFile: 'assets/init.js',
});
let tree = app.toTree();
return tree;
};

View File

@ -1,3 +1,6 @@
// rootURL in production equals `{{.ContentPath}}` and therefore is replaced
// with the value of -ui-content-path. During development rootURL uses the
// value as set in environment.js
module.exports = ({ appName, environment, rootURL, config }) => `
<noscript>
<div style="margin: 0 auto;">
@ -5,11 +8,16 @@ module.exports = ({ appName, environment, rootURL, config }) => `
<p>Please enable JavaScript in your web browser to use Consul UI.</p>
</div>
</noscript>
<svg width="168" height="53" xmlns="http://www.w3.org/2000/svg"><g fill="#919FA8" fill-rule="evenodd"><path d="M26.078 32.12a5.586 5.586 0 1 1 5.577-5.599 5.577 5.577 0 0 1-5.577 5.6M37.009 29.328a2.56 2.56 0 1 1 2.56-2.56 2.551 2.551 0 0 1-2.56 2.56M46.916 31.669a2.56 2.56 0 1 1 .051-.21c-.028.066-.028.13-.051.21M44.588 25.068a2.565 2.565 0 0 1-2.672-.992 2.558 2.558 0 0 1-.102-2.845 2.564 2.564 0 0 1 4.676.764c.072.328.081.667.027 1a2.463 2.463 0 0 1-1.925 2.073M53.932 31.402a2.547 2.547 0 0 1-2.95 2.076 2.559 2.559 0 0 1-2.064-2.965 2.547 2.547 0 0 1 2.948-2.077 2.57 2.57 0 0 1 2.128 2.716.664.664 0 0 0-.05.228M51.857 25.103a2.56 2.56 0 1 1 2.108-2.945c.034.218.043.439.027.658a2.547 2.547 0 0 1-2.135 2.287M49.954 40.113a2.56 2.56 0 1 1 .314-1.037c-.02.366-.128.721-.314 1.037M48.974 16.893a2.56 2.56 0 1 1 .97-3.487c.264.446.375.965.317 1.479a2.56 2.56 0 0 1-1.287 2.008"/><path d="M26.526 52.603c-14.393 0-26.06-11.567-26.06-25.836C.466 12.498 12.133.931 26.526.931a25.936 25.936 0 0 1 15.836 5.307l-3.167 4.117A20.962 20.962 0 0 0 17.304 8.23C10.194 11.713 5.7 18.9 5.714 26.763c-.014 7.862 4.48 15.05 11.59 18.534a20.962 20.962 0 0 0 21.89-2.127l3.168 4.123a25.981 25.981 0 0 1-15.836 5.31z"/>${
config.CONSUL_BINARY_TYPE !== 'oss' && config.CONSUL_BINARY_TYPE !== ''
? `<path data-enterprise-logo d="M61 42.083h3.975v.785H61.87v2.136h2.882v.784H61.87v2.31h3.114v.785H61v-6.8zm6.907 1.018V48.9h-.828v-6.817h1.2l2.94 5.84v-5.84h.829V48.9h-1.193L67.907 43.1zm7.826-.225h-2.012v-.784h4.911v.784h-2.02V48.9h-.879v-6.024zm4.564-.793h3.975v.785h-3.106v2.136h2.882v.784h-2.882v2.31h3.114v.785h-3.992l.009-6.8zm8.605 4.347h-1.657v2.503h-.87v-6.85h2.576c1.45 0 1.963.635 1.963 1.67v.984a1.435 1.435 0 0 1-1.077 1.585l1.756 2.57h-1.002l-1.69-2.462zm0-3.562h-1.657v2.778h1.657c.828 0 1.118-.234 1.118-.901v-.968c.024-.676-.265-.901-1.094-.901l-.024-.008zm4.488-.785h2.485c1.45 0 1.963.635 1.963 1.67v1.009c0 1.05-.505 1.668-1.963 1.668H94.3v2.47h-.87l-.04-6.817zm2.419.785h-1.54v2.803h1.54c.828 0 1.118-.234 1.118-.901v-1.001c0-.668-.282-.893-1.118-.893v-.008zm6.368 3.562h-1.656v2.503h-.87v-6.85h2.576c1.45 0 1.963.635 1.963 1.67v.984a1.435 1.435 0 0 1-1.077 1.585l1.756 2.57h-1.002l-1.69-2.462zm0-3.562h-1.656v2.778h1.656c.829 0 1.118-.234 1.118-.901v-.968c.017-.676-.265-.901-1.101-.901l-.017-.008zm5.392 6.032h-.828v-6.817h.828V48.9zm4.14.1a5.76 5.76 0 0 1-2.012-.359l.141-.717c.605.195 1.236.3 1.872.308 1.085 0 1.308-.283 1.308-1.06 0-.917 0-1-1.4-1.317-1.656-.368-1.83-.685-1.83-2.095 0-1.184.49-1.76 2.162-1.76a7.648 7.648 0 0 1 1.83.225l-.074.743a8.223 8.223 0 0 0-1.74-.192c-1.11 0-1.308.225-1.308 1.01 0 .942 0 .984 1.342 1.318 1.797.45 1.888.717 1.888 2.044.033 1.176-.315 1.852-2.178 1.852zm4.332-6.917h3.95v.785h-3.105v2.136h2.882v.784h-2.882v2.31H120v.785h-3.992l.033-6.8z" fill-rule="nonzero"/>`
: ``
}<path d="M61 30.15V17.948c0-4.962 2.845-7.85 9.495-7.85 2.484 0 5.048.326 7.252.895l-.561 4.433c-2.164-.406-4.688-.691-6.53-.691-3.486 0-4.608 1.22-4.608 4.108v10.412c0 2.888 1.122 4.108 4.607 4.108 1.843 0 4.367-.284 6.53-.691l.562 4.433c-2.204.57-4.768.895-7.252.895C63.845 38 61 35.112 61 30.15zm36.808.04c0 4.068-1.802 7.81-8.493 7.81-6.69 0-8.494-3.742-8.494-7.81v-5.002c0-4.067 1.803-7.81 8.494-7.81 6.69 0 8.493 3.743 8.493 7.81v5.003zm-4.887-5.165c0-2.237-1.002-3.416-3.606-3.416s-3.606 1.18-3.606 3.416v5.328c0 2.237 1.002 3.417 3.606 3.417s3.606-1.18 3.606-3.417v-5.328zm25.79 12.568h-4.887V23.764c0-1.057-.44-1.586-1.563-1.586-1.201 0-3.325.732-5.088 1.668v13.747h-4.887V17.785h3.726l.48 1.668c2.444-1.22 5.53-2.074 7.813-2.074 3.245 0 4.407 2.318 4.407 5.857v14.357zm18.26-5.775c0 3.823-1.162 6.182-7.052 6.182-2.083 0-4.927-.488-6.73-1.139l.68-3.782c1.643.488 3.807.854 5.81.854 2.164 0 2.484-.488 2.484-1.993 0-1.22-.24-1.83-3.405-2.603-4.768-1.18-5.329-2.4-5.329-6.223 0-3.986 1.723-5.735 7.292-5.735 1.803 0 4.166.244 5.85.691l-.482 3.945c-1.482-.284-3.846-.569-5.368-.569-2.124 0-2.484.488-2.484 1.708 0 1.587.12 1.709 2.764 2.4 5.449 1.464 5.97 2.196 5.97 6.264zm4.357-14.033h4.887v13.83c0 1.057.441 1.586 1.563 1.586 1.202 0 3.325-.733 5.088-1.668V17.785h4.888v19.808h-3.726l-.481-1.667c-2.444 1.22-5.529 2.074-7.812 2.074-3.246 0-4.407-2.318-4.407-5.857V17.785zM168 37.593h-4.888V9.691L168 9v28.593z"/></g></svg>
<script type="application/json" data-consul-ui-config>
${environment === 'production' ? `{{jsonEncode .}}` : JSON.stringify(config.operatorConfig)}
</script>
<script src="${rootURL}assets/init.js"></script>
<script src="${rootURL}assets/vendor.js"></script>
${environment === 'test' ? `<script src="${rootURL}assets/test-support.js"></script>` : ``}
<script>

View File

@ -1,11 +1,9 @@
// rootURL in production equals `{{.ContentPath}}` and therefore is replaced
// with the value of -ui-content-path. During development rootURL uses the
// value as set in environment.js
module.exports = ({ appName, environment, rootURL, config }) => `
<!-- CONSUL_VERSION: ${config.CONSUL_VERSION} -->
<meta name="consul-ui/ui_config" content="${
environment === 'production'
? `{{ jsonEncodeAndEscape .UIConfig }}`
: escape(JSON.stringify({}))
}" />
<link rel="icon" type="image/png" href="${rootURL}assets/favicon-32x32.png" sizes="32x32">
<link rel="icon" type="image/png" href="${rootURL}assets/favicon-16x16.png" sizes="16x16">
<link integrity="" rel="stylesheet" href="${rootURL}assets/vendor.css">

View File

@ -11,16 +11,17 @@ test(
{
environment: 'production',
CONSUL_BINARY_TYPE: 'oss',
CONSUL_ACLS_ENABLED: '__RUNTIME_BOOL_ACLsEnabled__',
CONSUL_SSO_ENABLED: '__RUNTIME_BOOL_SSOEnabled__',
CONSUL_NSPACES_ENABLED: '__RUNTIME_BOOL_NamespacesEnabled__',
operatorConfig: {}
},
{
environment: 'test',
CONSUL_BINARY_TYPE: 'oss',
CONSUL_ACLS_ENABLED: true,
CONSUL_NSPACES_ENABLED: false,
CONSUL_SSO_ENABLED: false,
operatorConfig: {
ACLsEnabled: true,
NamespacesEnabled: false,
SSOEnabled: false,
LocalDatacenter: 'dc1',
}
},
{
$: {
@ -28,9 +29,12 @@ test(
},
environment: 'test',
CONSUL_BINARY_TYPE: 'oss',
CONSUL_ACLS_ENABLED: true,
CONSUL_NSPACES_ENABLED: true,
CONSUL_SSO_ENABLED: false,
operatorConfig: {
ACLsEnabled: true,
NamespacesEnabled: true,
SSOEnabled: false,
LocalDatacenter: 'dc1',
}
},
{
$: {
@ -38,16 +42,22 @@ test(
},
environment: 'test',
CONSUL_BINARY_TYPE: 'oss',
CONSUL_ACLS_ENABLED: true,
CONSUL_NSPACES_ENABLED: false,
CONSUL_SSO_ENABLED: true,
operatorConfig: {
ACLsEnabled: true,
NamespacesEnabled: false,
SSOEnabled: true,
LocalDatacenter: 'dc1',
}
},
{
environment: 'staging',
CONSUL_BINARY_TYPE: 'oss',
CONSUL_ACLS_ENABLED: true,
CONSUL_NSPACES_ENABLED: true,
CONSUL_SSO_ENABLED: true,
operatorConfig: {
ACLsEnabled: true,
NamespacesEnabled: true,
SSOEnabled: true,
LocalDatacenter: 'dc1',
}
}
].forEach(
function(item) {
@ -57,7 +67,7 @@ test(
if(key === '$') {
return;
}
t.equal(
t.deepEqual(
env[key],
item[key],
`Expect ${key} to equal ${item[key]} in the ${item.environment} environment ${typeof item.$ !== 'undefined' ? `(with ${JSON.stringify(item.$)})` : ''}`

View File

@ -19,6 +19,11 @@ const makeGetElementsBy = function(str) {
];
};
};
const makeOperatorConfig = function(json) {
return {
textContent: JSON.stringify(json),
};
};
const win = {
performance: {
getEntriesByType: getEntriesByType,
@ -34,6 +39,7 @@ const doc = {
cookie: '',
getElementsByTagName: makeGetElementsBy(''),
getElementsByName: makeGetElementsBy('{}'),
querySelector: () => makeOperatorConfig({}),
};
module('Unit | Utility | getEnvironment', function() {
test('it returns a function', function(assert) {
@ -62,6 +68,7 @@ module('Unit | Utility | getEnvironment', function() {
cookie: '',
getElementsByTagName: makeGetElementsBy(`${expected}/assets/consul-ui.js`),
getElementsByName: makeGetElementsBy('{}'),
querySelector: () => makeOperatorConfig({}),
};
let env = getEnvironment(config, win, doc);
assert.equal(env('CONSUL_BASE_UI_URL'), expected);
@ -70,6 +77,7 @@ module('Unit | Utility | getEnvironment', function() {
cookie: '',
getElementsByTagName: makeGetElementsBy(`${expected}/assets/consul-ui.js`),
getElementsByName: makeGetElementsBy('{}'),
querySelector: () => makeOperatorConfig({}),
};
env = getEnvironment(config, win, doc);
assert.equal(env('CONSUL_BASE_UI_URL'), expected);
@ -144,6 +152,7 @@ module('Unit | Utility | getEnvironment', function() {
cookie: 'CONSUL_NSPACES_ENABLE=1',
getElementsByTagName: makeGetElementsBy(''),
getElementsByName: makeGetElementsBy('{}'),
querySelector: () => makeOperatorConfig({ NamespacesEnabled: true }),
};
let env = getEnvironment(config, win, doc);
assert.ok(env('CONSUL_NSPACES_ENABLED'));
@ -155,6 +164,7 @@ module('Unit | Utility | getEnvironment', function() {
cookie: 'CONSUL_NSPACES_ENABLE=0',
getElementsByTagName: makeGetElementsBy(''),
getElementsByName: makeGetElementsBy('{}'),
querySelector: () => makeOperatorConfig({ NamespacesEnabled: false }),
};
env = getEnvironment(config, win, doc);
assert.notOk(env('CONSUL_NSPACES_ENABLED'));
@ -180,6 +190,7 @@ module('Unit | Utility | getEnvironment', function() {
cookie: 'CONSUL_NSPACES_ENABLE=1',
getElementsByTagName: makeGetElementsBy(''),
getElementsByName: makeGetElementsBy('{}'),
querySelector: () => makeOperatorConfig({ NamespacesEnabled: false }),
};
let env = getEnvironment(config, win, doc);
assert.notOk(env('CONSUL_NSPACES_ENABLED'));
@ -191,6 +202,7 @@ module('Unit | Utility | getEnvironment', function() {
cookie: 'CONSUL_NSPACES_ENABLE=0',
getElementsByTagName: makeGetElementsBy(''),
getElementsByName: makeGetElementsBy('{}'),
querySelector: () => makeOperatorConfig({ NamespacesEnabled: true }),
};
env = getEnvironment(config, win, doc);
assert.ok(env('CONSUL_NSPACES_ENABLED'));

24
ui/packages/consul-ui/vendor/init.js vendored Normal file
View File

@ -0,0 +1,24 @@
(function(doc, appName) {
try {
const $appMeta = doc.querySelector(`[name="${appName}/config/environment"]`);
// pick out the operatorConfig from our application/json script tag
const operatorConfig = JSON.parse(doc.querySelector(`[data-${appName}-config]`).textContent);
// pick out the ember config from its meta tag
const emberConfig = JSON.parse(decodeURIComponent($appMeta.getAttribute('content')));
// rootURL is a special variable that requires settings before ember
// boots via ember's HTML metadata tag, the variable is equivalent to
// the -ui-content-path Consul flag (or `ui_config { content_path = ""}`)
// There will potentially be one or two more 'pre-init' variables that we need.
// Anything not 'pre-init' should use ui_config.
// Check the value to make sure its there and a string
const rootURL =
typeof operatorConfig.ContentPath !== 'string' ? '' : operatorConfig.ContentPath;
if (rootURL.length > 0) {
emberConfig.rootURL = rootURL;
}
$appMeta.setAttribute('content', encodeURIComponent(JSON.stringify(emberConfig)));
} catch (e) {
throw new Error(`Unable to parse ${appName} settings: ${e.message}`);
}
})(document, 'consul-ui');

2
vendor/modules.txt vendored
View File

@ -484,6 +484,8 @@ golang.org/x/lint/golint
golang.org/x/net/bpf
golang.org/x/net/context
golang.org/x/net/context/ctxhttp
golang.org/x/net/html
golang.org/x/net/html/atom
golang.org/x/net/http/httpguts
golang.org/x/net/http2
golang.org/x/net/http2/hpack