Update UI Config passing to not use an inline script (#8645)
* Update UI Config passing to not use an inline script * Update agent/http.go * Fix incorrect placeholder name
This commit is contained in:
parent
296340e13f
commit
0062106c46
File diff suppressed because one or more lines are too long
109
agent/http.go
109
agent/http.go
|
@ -15,7 +15,6 @@ import (
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/NYTimes/gziphandler"
|
"github.com/NYTimes/gziphandler"
|
||||||
|
@ -88,64 +87,64 @@ type HTTPServer struct {
|
||||||
denylist *Denylist
|
denylist *Denylist
|
||||||
}
|
}
|
||||||
|
|
||||||
type templatedFile struct {
|
// bufferedFile implements os.File and allows us to modify a file from disk by
|
||||||
|
// writing out the new version into a buffer and then serving file reads from
|
||||||
|
// that. It assumes you are modifying a real file and presents the actual file's
|
||||||
|
// info when queried.
|
||||||
|
type bufferedFile struct {
|
||||||
templated *bytes.Reader
|
templated *bytes.Reader
|
||||||
name string
|
info os.FileInfo
|
||||||
mode os.FileMode
|
|
||||||
modTime time.Time
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTemplatedFile(buf *bytes.Buffer, raw http.File) *templatedFile {
|
func newBufferedFile(buf *bytes.Buffer, raw http.File) *bufferedFile {
|
||||||
info, _ := raw.Stat()
|
info, _ := raw.Stat()
|
||||||
return &templatedFile{
|
return &bufferedFile{
|
||||||
templated: bytes.NewReader(buf.Bytes()),
|
templated: bytes.NewReader(buf.Bytes()),
|
||||||
name: info.Name(),
|
info: info,
|
||||||
mode: info.Mode(),
|
|
||||||
modTime: info.ModTime(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *templatedFile) Read(p []byte) (n int, err error) {
|
func (t *bufferedFile) Read(p []byte) (n int, err error) {
|
||||||
return t.templated.Read(p)
|
return t.templated.Read(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *templatedFile) Seek(offset int64, whence int) (int64, error) {
|
func (t *bufferedFile) Seek(offset int64, whence int) (int64, error) {
|
||||||
return t.templated.Seek(offset, whence)
|
return t.templated.Seek(offset, whence)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *templatedFile) Close() error {
|
func (t *bufferedFile) Close() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *templatedFile) Readdir(count int) ([]os.FileInfo, error) {
|
func (t *bufferedFile) Readdir(count int) ([]os.FileInfo, error) {
|
||||||
return nil, errors.New("not a directory")
|
return nil, errors.New("not a directory")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *templatedFile) Stat() (os.FileInfo, error) {
|
func (t *bufferedFile) Stat() (os.FileInfo, error) {
|
||||||
return t, nil
|
return t, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *templatedFile) Name() string {
|
func (t *bufferedFile) Name() string {
|
||||||
return t.name
|
return t.info.Name()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *templatedFile) Size() int64 {
|
func (t *bufferedFile) Size() int64 {
|
||||||
return int64(t.templated.Len())
|
return int64(t.templated.Len())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *templatedFile) Mode() os.FileMode {
|
func (t *bufferedFile) Mode() os.FileMode {
|
||||||
return t.mode
|
return t.info.Mode()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *templatedFile) ModTime() time.Time {
|
func (t *bufferedFile) ModTime() time.Time {
|
||||||
return t.modTime
|
return t.info.ModTime()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *templatedFile) IsDir() bool {
|
func (t *bufferedFile) IsDir() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *templatedFile) Sys() interface{} {
|
func (t *bufferedFile) Sys() interface{} {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,28 +160,56 @@ func (fs *redirectFS) Open(name string) (http.File, error) {
|
||||||
return file, err
|
return file, err
|
||||||
}
|
}
|
||||||
|
|
||||||
type templatedIndexFS struct {
|
type settingsInjectedIndexFS struct {
|
||||||
fs http.FileSystem
|
fs http.FileSystem
|
||||||
templateVars func() map[string]interface{}
|
UISettings map[string]interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fs *templatedIndexFS) Open(name string) (http.File, error) {
|
func (fs *settingsInjectedIndexFS) Open(name string) (http.File, error) {
|
||||||
file, err := fs.fs.Open(name)
|
file, err := fs.fs.Open(name)
|
||||||
if err != nil || name != "/index.html" {
|
if err != nil || name != "/index.html" {
|
||||||
return file, err
|
return file, err
|
||||||
}
|
}
|
||||||
|
|
||||||
content, _ := ioutil.ReadAll(file)
|
content, err := ioutil.ReadAll(file)
|
||||||
file.Seek(0, 0)
|
|
||||||
t, err := template.New("fmtedindex").Parse(string(content))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("failed reading index.html: %s", err)
|
||||||
}
|
}
|
||||||
var out bytes.Buffer
|
file.Seek(0, 0)
|
||||||
if err := t.Execute(&out, fs.templateVars()); err != nil {
|
|
||||||
return nil, err
|
// Replace the placeholder in the meta ENV with the actual UI config settings.
|
||||||
|
// Ember passes the ENV with URL encoded JSON in a meta tag. We are replacing
|
||||||
|
// a key and value that is the encoded version of
|
||||||
|
// `"CONSUL_UI_SETTINGS_PLACEHOLDER":"__CONSUL_UI_SETTINGS_GO_HERE__"`
|
||||||
|
// with a URL-encoded JSON blob representing the actual config.
|
||||||
|
|
||||||
|
// First built an escaped, JSON blob from the settings passed.
|
||||||
|
bs, err := json.Marshal(fs.UISettings)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed marshalling UI settings JSON: %s", err)
|
||||||
}
|
}
|
||||||
return newTemplatedFile(&out, file), nil
|
// We want to remove the first and last chars which will be the { and } since
|
||||||
|
// we are injecting these variabled into the middle of an existing object.
|
||||||
|
bs = bytes.Trim(bs, "{}")
|
||||||
|
|
||||||
|
// We use PathEscape because we don't want spaces to be turned into "+" like
|
||||||
|
// QueryEscape does.
|
||||||
|
escaped := url.PathEscape(string(bs))
|
||||||
|
|
||||||
|
content = bytes.Replace(content,
|
||||||
|
[]byte("%22CONSUL_UI_SETTINGS_PLACEHOLDER%22%3A%22__CONSUL_UI_SETTINGS_GO_HERE__%22"),
|
||||||
|
[]byte(escaped), 1)
|
||||||
|
|
||||||
|
// We also need to inject the content path. This used to be a go template
|
||||||
|
// hence the syntax but for now simple string replacement is fine esp. since
|
||||||
|
// all the other templated stuff above can't easily be done that was as we are
|
||||||
|
// replacing an entire placeholder element in an encoded JSON blob with
|
||||||
|
// multiple encoded JSON elements.
|
||||||
|
if path, ok := fs.UISettings["CONSUL_CONTENT_PATH"].(string); ok {
|
||||||
|
content = bytes.Replace(content, []byte("{{.ContentPath}}"), []byte(path), -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return newBufferedFile(bytes.NewBuffer(content), file), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// endpoint is a Consul-specific HTTP handler that takes the usual arguments in
|
// endpoint is a Consul-specific HTTP handler that takes the usual arguments in
|
||||||
|
@ -332,7 +359,7 @@ func (s *HTTPServer) handler(enableDebug bool) http.Handler {
|
||||||
uifs = fs
|
uifs = fs
|
||||||
}
|
}
|
||||||
|
|
||||||
uifs = &redirectFS{fs: &templatedIndexFS{fs: uifs, templateVars: s.GenerateHTMLTemplateVars}}
|
uifs = &redirectFS{fs: &settingsInjectedIndexFS{fs: uifs, UISettings: s.GetUIENVFromConfig()}}
|
||||||
// create a http handler using the ui file system
|
// create a http handler using the ui file system
|
||||||
// and the headers specified by the http_config.response_headers user config
|
// and the headers specified by the http_config.response_headers user config
|
||||||
uifsWithHeaders := serveHandlerWithHeaders(
|
uifsWithHeaders := serveHandlerWithHeaders(
|
||||||
|
@ -366,13 +393,13 @@ func (s *HTTPServer) handler(enableDebug bool) http.Handler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *HTTPServer) GenerateHTMLTemplateVars() map[string]interface{} {
|
func (s *HTTPServer) GetUIENVFromConfig() map[string]interface{} {
|
||||||
vars := map[string]interface{}{
|
vars := map[string]interface{}{
|
||||||
"ContentPath": s.agent.config.UIContentPath,
|
"CONSUL_CONTENT_PATH": s.agent.config.UIContentPath,
|
||||||
"ACLsEnabled": s.agent.config.ACLsEnabled,
|
"CONSUL_ACLS_ENABLED": s.agent.config.ACLsEnabled,
|
||||||
}
|
}
|
||||||
|
|
||||||
s.addEnterpriseHTMLTemplateVars(vars)
|
s.addEnterpriseUIENVVars(vars)
|
||||||
|
|
||||||
return vars
|
return vars
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,7 +55,7 @@ func (s *HTTPServer) rewordUnknownEnterpriseFieldError(err error) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *HTTPServer) addEnterpriseHTMLTemplateVars(vars map[string]interface{}) {}
|
func (s *HTTPServer) addEnterpriseUIENVVars(vars map[string]interface{}) {}
|
||||||
|
|
||||||
func parseACLAuthMethodEnterpriseMeta(req *http.Request, _ *structs.ACLAuthMethodEnterpriseMeta) error {
|
func parseACLAuthMethodEnterpriseMeta(req *http.Request, _ *structs.ACLAuthMethodEnterpriseMeta) error {
|
||||||
if methodNS := req.URL.Query().Get("authmethod-ns"); methodNS != "" {
|
if methodNS := req.URL.Query().Get("authmethod-ns"); methodNS != "" {
|
||||||
|
|
|
@ -130,10 +130,13 @@ module.exports = function(environment, $ = process.env) {
|
||||||
// Make sure all templated variables check for existence first
|
// Make sure all templated variables check for existence first
|
||||||
// before outputting them, this means they all should be conditionals
|
// before outputting them, this means they all should be conditionals
|
||||||
ENV = Object.assign({}, ENV, {
|
ENV = Object.assign({}, ENV, {
|
||||||
CONSUL_ACLS_ENABLED: '{{ if .ACLsEnabled }}{{.ACLsEnabled}}{{ else }}false{{ end }}',
|
// This ENV var is a special placeholder that Consul will replace
|
||||||
CONSUL_SSO_ENABLED: '{{ if .SSOEnabled }}{{.SSOEnabled}}{{ else }}false{{ end }}',
|
// entirely with multiple vars from the runtime config for example
|
||||||
CONSUL_NSPACES_ENABLED:
|
// CONSUL_ACLs_ENABLED and CONSUL_NSPACES_ENABLED. The actual key here
|
||||||
'{{ if .NamespacesEnabled }}{{.NamespacesEnabled}}{{ else }}false{{ end }}',
|
// won't really exist in the actual ember ENV when it's being served
|
||||||
|
// through Consul. See settingsInjectedIndexFS.Open in Go code for the
|
||||||
|
// details.
|
||||||
|
CONSUL_UI_SETTINGS_PLACEHOLDER: "__CONSUL_UI_SETTINGS_GO_HERE__",
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,35 +1,5 @@
|
||||||
module.exports = ({ appName, environment, rootURL, config }) => `
|
module.exports = ({ appName, environment, rootURL, config }) => `
|
||||||
<!-- CONSUL_VERSION: ${config.CONSUL_VERSION} -->
|
<!-- CONSUL_VERSION: ${config.CONSUL_VERSION} -->
|
||||||
<script>
|
|
||||||
var setConfig = function(appName, config) {
|
|
||||||
var $meta = document.querySelector('meta[name="' + appName + '/config/environment"]');
|
|
||||||
var defaultConfig = JSON.parse(decodeURIComponent($meta.getAttribute('content')));
|
|
||||||
(
|
|
||||||
function set(blob, config) {
|
|
||||||
Object.keys(config).forEach(
|
|
||||||
function(key) {
|
|
||||||
var value = config[key];
|
|
||||||
if(Object.prototype.toString.call(value) === '[object Object]') {
|
|
||||||
set(blob[key], config[key]);
|
|
||||||
} else {
|
|
||||||
blob[key] = config[key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
)(defaultConfig, config);
|
|
||||||
$meta.setAttribute('content', encodeURIComponent(JSON.stringify(defaultConfig)));
|
|
||||||
}
|
|
||||||
setConfig(
|
|
||||||
'${appName}',
|
|
||||||
{
|
|
||||||
rootURL: '${rootURL}',
|
|
||||||
CONSUL_ACLS_ENABLED: ${config.CONSUL_ACLS_ENABLED},
|
|
||||||
CONSUL_NSPACES_ENABLED: ${config.CONSUL_NSPACES_ENABLED},
|
|
||||||
CONSUL_SSO_ENABLED: ${config.CONSUL_SSO_ENABLED}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
</script>
|
|
||||||
<link rel="icon" type="image/png" href="${rootURL}assets/favicon-32x32.png" sizes="32x32">
|
<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 rel="icon" type="image/png" href="${rootURL}assets/favicon-16x16.png" sizes="16x16">
|
||||||
<link integrity="" rel="stylesheet" href="${rootURL}assets/vendor.css">
|
<link integrity="" rel="stylesheet" href="${rootURL}assets/vendor.css">
|
||||||
|
|
Loading…
Reference in New Issue