Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CDN: Adds support for serving assets over a CDN #30691

Merged
merged 17 commits into from Feb 1, 2021
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions conf/defaults.ini
Expand Up @@ -66,6 +66,9 @@ cert_key =
# Unix socket path
socket = /tmp/grafana.sock

# CDN Url
cdn_url =

#################################### Database ############################
[database]
# You can configure the database connection by specifying type, host, name, user and password
Expand Down
3 changes: 3 additions & 0 deletions conf/sample.ini
Expand Up @@ -67,6 +67,9 @@
# Unix socket path
;socket =

# CDN Url
;cdn_url =

#################################### Database ####################################
[database]
# You can configure the database connection by specifying type, host, name, user and password
Expand Down
9 changes: 9 additions & 0 deletions docs/sources/administration/configuration.md
Expand Up @@ -259,6 +259,15 @@ Path to the certificate key file (if `protocol` is set to `https` or `h2`).

Path where the socket should be created when `protocol=socket`. Make sure that Grafana has appropriate permissions before you change this setting.

### cdn_url

> **Note**: Available in Grafana v7.4 and later versions.

Specify a full HTTP URL address to the root of your Grafana CDN assets. Grafana will add edition and version paths.

For example, given a cdn url like `https://cdn.myserver.com` grafana will try to load a javascript file from
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
For example, given a cdn url like `https://cdn.myserver.com` grafana will try to load a javascript file from
For example, given a CDN URL like `https://cdn.myserver.com` grafana will try to load a javascript file from

`http://cdn.myserver.com/oss/v7.4.0/public/build/app.<hash>.js`.

<hr />

## [database]
Expand Down
1 change: 1 addition & 0 deletions pkg/api/dtos/index.go
Expand Up @@ -25,6 +25,7 @@ type IndexViewData struct {
AppleTouchIcon template.URL
AppTitle string
Sentry *setting.Sentry
ContentDeliveryURL string
// Nonce is a cryptographic identifier for use with Content Security Policy.
Nonce string
}
Expand Down
1 change: 1 addition & 0 deletions pkg/api/index.go
Expand Up @@ -428,6 +428,7 @@ func (hs *HTTPServer) setIndexViewData(c *models.ReqContext) (*dtos.IndexViewDat
NavTree: navTree,
Sentry: &hs.Cfg.Sentry,
Nonce: c.RequestNonce,
ContentDeliveryURL: hs.Cfg.GetContentDeliveryURL((hs.License.Edition())),
}

if setting.DisableGravatar {
Expand Down
30 changes: 27 additions & 3 deletions pkg/setting/setting.go
Expand Up @@ -197,6 +197,7 @@ type Cfg struct {
SocketPath string
RouterLogging bool
Domain string
CDNRootURL *url.URL

// build
BuildVersion string
Expand Down Expand Up @@ -767,7 +768,7 @@ func (cfg *Cfg) Load(args *CommandLineArgs) error {
provisioning := valueAsString(iniFile.Section("paths"), "provisioning", "")
cfg.ProvisioningPath = makeAbsolute(provisioning, HomePath)

if err := readServerSettings(iniFile, cfg); err != nil {
if err := cfg.readServerSettings(iniFile); err != nil {
return err
}

Expand Down Expand Up @@ -1251,7 +1252,7 @@ func readSnapshotsSettings(cfg *Cfg, iniFile *ini.File) error {
return nil
}

func readServerSettings(iniFile *ini.File, cfg *Cfg) error {
func (cfg *Cfg) readServerSettings(iniFile *ini.File) error {
server := iniFile.Section("server")
var err error
AppUrl, AppSubUrl, err = parseAppUrlAndSubUrl(server)
Expand All @@ -1263,8 +1264,8 @@ func readServerSettings(iniFile *ini.File, cfg *Cfg) error {
cfg.AppURL = AppUrl
cfg.AppSubURL = AppSubUrl
cfg.ServeFromSubPath = ServeFromSubPath

cfg.Protocol = HTTPScheme

protocolStr := valueAsString(server, "protocol", "http")

if protocolStr == "https" {
Expand Down Expand Up @@ -1297,9 +1298,32 @@ func readServerSettings(iniFile *ini.File, cfg *Cfg) error {
return err
}

cdnURL := valueAsString(server, "cdn_url", "")
if cdnURL != "" {
cfg.CDNRootURL, err = url.Parse(cdnURL)
if err != nil {
return err
}
}

return nil
}

// GetContentDeliveryURL returns full content delivery URL with /<edition>/<version> added to URL
func (cfg *Cfg) GetContentDeliveryURL(edition string) string {
if cfg.CDNRootURL != nil {
url := *cfg.CDNRootURL
preReleaseFolder := ""
if strings.Contains(cfg.BuildVersion, "pre") || strings.Contains(cfg.BuildVersion, "alpha") {
preReleaseFolder = "master"
}
url.Path = path.Join(url.Path, strings.ToLower(edition), preReleaseFolder, cfg.BuildVersion)
return url.String()
}

return ""
}

func (cfg *Cfg) readDataSourcesSettings() {
datasources := cfg.Raw.Section("datasources")
cfg.DataSourceLimit = datasources.Key("datasource_limit").MustInt(5000)
Expand Down
33 changes: 33 additions & 0 deletions pkg/setting/setting_test.go
Expand Up @@ -2,6 +2,7 @@ package setting

import (
"bufio"
"net/url"
"os"
"path"
"path/filepath"
Expand Down Expand Up @@ -389,3 +390,35 @@ func TestAuthDurationSettings(t *testing.T) {
require.NoError(t, err)
require.Equal(t, maxLifetimeDurationTest, cfg.LoginMaxLifetime)
}

func TestGetCDNPath(t *testing.T) {
marefr marked this conversation as resolved.
Show resolved Hide resolved
var err error
cfg := NewCfg()
cfg.BuildVersion = "v7.5.0-11124"
cfg.CDNRootURL, err = url.Parse("http://cdn.grafana.com")
require.NoError(t, err)

require.Equal(t, "http://cdn.grafana.com/oss/v7.5.0-11124", cfg.GetContentDeliveryURL("oss"))
require.Equal(t, "http://cdn.grafana.com/enterprise/v7.5.0-11124", cfg.GetContentDeliveryURL("Enterprise"))
}

func TestGetCDNPathWithPreReleaseVersionAndSubPath(t *testing.T) {
var err error
cfg := NewCfg()
cfg.BuildVersion = "v7.5.0-11124pre"
cfg.CDNRootURL, err = url.Parse("http://cdn.grafana.com/sub")
require.NoError(t, err)
require.Equal(t, "http://cdn.grafana.com/sub/oss/master/v7.5.0-11124pre", cfg.GetContentDeliveryURL("oss"))
require.Equal(t, "http://cdn.grafana.com/sub/enterprise/master/v7.5.0-11124pre", cfg.GetContentDeliveryURL("Enterprise"))
}

// Adding a case for this in case we switch to proper semver version strings
func TestGetCDNPathWithAlphaVersion(t *testing.T) {
var err error
cfg := NewCfg()
cfg.BuildVersion = "v7.5.0-alpha.11124"
cfg.CDNRootURL, err = url.Parse("http://cdn.grafana.com")
require.NoError(t, err)
require.Equal(t, "http://cdn.grafana.com/oss/master/v7.5.0-alpha.11124", cfg.GetContentDeliveryURL("oss"))
require.Equal(t, "http://cdn.grafana.com/enterprise/master/v7.5.0-alpha.11124", cfg.GetContentDeliveryURL("Enterprise"))
}
10 changes: 9 additions & 1 deletion public/app/index.ts
@@ -1,4 +1,12 @@
import app from './app';
declare let __webpack_public_path__: string;

/**
* Check if we are hosting files on cdn and set webpack public path
*/
if ((window as any).public_cdn_path) {
__webpack_public_path__ = (window as any).public_cdn_path;
}

import app from './app';
app.initEchoSrv();
app.init();
122 changes: 65 additions & 57 deletions public/views/index-template.html
Expand Up @@ -3,16 +3,15 @@
<head>
<script nonce="[[.Nonce]]">
// https://github.com/GoogleChromeLabs/tti-polyfill
!(function() {
!(function () {
if ('PerformanceLongTaskTiming' in window) {
var g = (window.__tti = { e: [] });
g.o = new PerformanceObserver(function(l) {
g.o = new PerformanceObserver(function (l) {
g.e = g.e.concat(l.getEntries());
});
g.o.observe({ entryTypes: ['longtask'] });
}
})();

</script>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
Expand All @@ -25,16 +24,15 @@

<link
rel="preload"
href="public/fonts/roboto/RxZJdnzeo3R5zSexge8UUVtXRa8TVwTICgirnJhmVJw.woff2"
href="[[.ContentDeliveryURL]]/public/fonts/roboto/RxZJdnzeo3R5zSexge8UUVtXRa8TVwTICgirnJhmVJw.woff2"
as="font"
crossorigin
/>

<link rel="icon" type="image/png" href="[[.FavIcon]]" />
<link rel="apple-touch-icon" sizes="180x180" href="[[.AppleTouchIcon]]" />
<link rel="mask-icon" href="public/img/grafana_mask_icon.svg" color="#F05A28" />

<link rel="stylesheet" href="public/build/grafana.[[ .Theme ]].<%= webpack.hash %>.css" />
<link rel="mask-icon" href="[[.ContentDeliveryURL]]/public/img/grafana_mask_icon.svg" color="#F05A28" />
<link rel="stylesheet" href="[[.ContentDeliveryURL]]/public/build/grafana.[[ .Theme ]].<%= webpack.hash %>.css" />

<script nonce="[[.Nonce]]">
performance.mark('css done blocking');
Expand Down Expand Up @@ -199,14 +197,17 @@
</p>
<p>
1. This could be caused by your reverse proxy settings.<br /><br />
2. If you host grafana under subpath make sure your grafana.ini root_url setting includes subpath. If not using a reverse proxy make sure to set serve_from_sub_path to true.<br />
2. If you host grafana under subpath make sure your grafana.ini root_url setting includes subpath. If not
using a reverse proxy make sure to set serve_from_sub_path to true.<br />
torkelo marked this conversation as resolved.
Show resolved Hide resolved
<br />
3. If you have a local dev build make sure you build frontend using: yarn start, yarn start:hot, or yarn
build<br />
<br />
4. Sometimes restarting grafana-server can help<br />
<br />
5. You might be using a non-supported browser. To make sure read the documentation on <a href="https://grafana.com/docs/grafana/latest/installation/requirements/#supported-web-browsers">supported browsers</a>.
5. Check if you are using a non-supported browser. For more information, refer to the list of <a
href="https://grafana.com/docs/grafana/latest/installation/requirements/#supported-web-browsers">
supported browsers</a>.
</p>
</div>
</div>
Expand All @@ -217,7 +218,7 @@
<search-wrapper></search-wrapper>

<div class="main-view">
<div ng-view class="scroll-canvas"></div>
<div ng-view class="scroll-canvas"></div>
</div>
</grafana-app>

Expand All @@ -235,57 +236,64 @@
preloader[0].className = "preloader preloader--done";
}
};

window.public_cdn_path = '[[.ContentDeliveryURL]]/public/build/';
</script>

[[if .GoogleTagManagerId]]
<!-- Google Tag Manager -->
<script nonce="[[.Nonce]]">
dataLayer = [
{
IsSignedIn: '[[.User.IsSignedIn]]',
Email: '[[.User.Email]]',
Name: '[[.User.Name]]',
UserId: '[[.User.Id]]',
OrgId: '[[.User.OrgId]]',
OrgName: '[[.User.OrgName]]',
},
];
</script>
<noscript>
<iframe
src="//www.googletagmanager.com/ns.html?id=[[.GoogleTagManagerId]]"
height="0"
width="0"
style="display:none;visibility:hidden"
></iframe>
</noscript>
<script nonce="[[.Nonce]]">
(function(w, d, s, l, i) {
w[l] = w[l] || [];
w[l].push({ 'gtm.start': new Date().getTime(), event: 'gtm.js' });
var f = d.getElementsByTagName(s)[0],
j = d.createElement(s),
dl = l != 'dataLayer' ? '&l=' + l : '';
j.async = true;
j.src = '//www.googletagmanager.com/gtm.js?id=' + i + dl;
f.parentNode.insertBefore(j, f);
})(window, document, 'script', 'dataLayer', '[[.GoogleTagManagerId]]');
</script>
<!-- End Google Tag Manager -->
[[end]]
<!-- Google Tag Manager -->
<script nonce="[[.Nonce]]">
dataLayer = [
{
IsSignedIn: '[[.User.IsSignedIn]]',
Email: '[[.User.Email]]',
Name: '[[.User.Name]]',
UserId: '[[.User.Id]]',
OrgId: '[[.User.OrgId]]',
OrgName: '[[.User.OrgName]]',
},
];
</script>
<noscript>
<iframe
src="//www.googletagmanager.com/ns.html?id=[[.GoogleTagManagerId]]"
height="0"
width="0"
style="display: none; visibility: hidden"
></iframe>
</noscript>
<script nonce="[[.Nonce]]">
(function (w, d, s, l, i) {
w[l] = w[l] || [];
w[l].push({ 'gtm.start': new Date().getTime(), event: 'gtm.js' });
var f = d.getElementsByTagName(s)[0],
j = d.createElement(s),
dl = l != 'dataLayer' ? '&l=' + l : '';
j.async = true;
j.src = '//www.googletagmanager.com/gtm.js?id=' + i + dl;
f.parentNode.insertBefore(j, f);
})(window, document, 'script', 'dataLayer', '[[.GoogleTagManagerId]]');
</script>
<!-- End Google Tag Manager -->
[[end]]

<%
for (key in htmlWebpackPlugin.files.chunks) { %><%
if (htmlWebpackPlugin.files.jsIntegrity) { %>
<script nonce="[[.Nonce]]"
src="<%= htmlWebpackPlugin.files.chunks[key].entry %>"
type="text/javascript"
integrity="<%= htmlWebpackPlugin.files.jsIntegrity[htmlWebpackPlugin.files.js.indexOf(htmlWebpackPlugin.files.chunks[key].entry)] %>"
crossorigin="<%= webpackConfig.output.crossOriginLoading %>"></script><%
} else { %>
<script nonce="[[.Nonce]]" src="<%= htmlWebpackPlugin.files.chunks[key].entry %>" type="text/javascript"></script><%
} %><%
} %>
<% for (key in htmlWebpackPlugin.files.chunks) { %>
<% if (htmlWebpackPlugin.files.jsIntegrity) { %>
<script
nonce="[[.Nonce]]"
src="[[.ContentDeliveryURL]]/<%= htmlWebpackPlugin.files.chunks[key].entry %>"
type="text/javascript"
integrity="<%= htmlWebpackPlugin.files.jsIntegrity[htmlWebpackPlugin.files.js.indexOf(htmlWebpackPlugin.files.chunks[key].entry)] %>"
crossorigin="<%= webpackConfig.output.crossOriginLoading %>">
</script>
<% } else { %>
<script
nonce="[[.Nonce]]"
src="[[.ContentDeliveryURL]]/<%= htmlWebpackPlugin.files.chunks[key].entry %>"
type="text/javascript">
</script>
<% } %>
<% } %>
<script nonce="[[.Nonce]]">
performance.mark('js done blocking');
</script>
Expand Down