Commit 9b90ff29 by Alex Khomenko Committed by GitHub

Disable selecting enterprise plugins with no license (#28758)

* Add unlicensed property to plugins

* Disable selecting unlicensed plugin

* Add customizable plugin market place url

* License: workaround enabled only in enterprise

* linter

* Move licensing info to front end

* Update pkg/services/licensing/oss.go

Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com>

* Update pkg/services/licensing/oss.go

Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com>

* Update pkg/setting/setting.go

Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com>

* Update pkg/setting/setting.go

Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com>

* Update pkg/api/frontendsettings.go

Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com>

* Update sample.ini

* Update docs

* Update packages/grafana-runtime/src/config.ts

Co-authored-by: Torkel Ödegaard <torkel@grafana.org>

* Update public/app/features/datasources/state/buildCategories.ts

Co-authored-by: Torkel Ödegaard <torkel@grafana.org>

* Update pkg/api/frontendsettings.go

Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com>

* Update pkg/setting/setting.go

Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com>

* Fix spelling

Co-authored-by: Leonard Gram <leo@xlson.com>
Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com>
Co-authored-by: Torkel Ödegaard <torkel@grafana.org>
parent 62138e8a
...@@ -671,7 +671,7 @@ disable_total_stats = false ...@@ -671,7 +671,7 @@ disable_total_stats = false
basic_auth_username = basic_auth_username =
basic_auth_password = basic_auth_password =
# Metrics environment info adds dimensions to the `grafana_environment_info` metric, which # Metrics environment info adds dimensions to the `grafana_environment_info` metric, which
# can expose more information about the Grafana instance. # can expose more information about the Grafana instance.
[metrics.environment_info] [metrics.environment_info]
#exampleLabel1 = exampleValue1 #exampleLabel1 = exampleValue1
...@@ -769,6 +769,7 @@ enable_alpha = false ...@@ -769,6 +769,7 @@ enable_alpha = false
app_tls_skip_verify_insecure = false app_tls_skip_verify_insecure = false
# Enter a comma-separated list of plugin identifiers to identify plugins that are allowed to be loaded even if they lack a valid signature. # Enter a comma-separated list of plugin identifiers to identify plugins that are allowed to be loaded even if they lack a valid signature.
allow_loading_unsigned_plugins = allow_loading_unsigned_plugins =
marketplace_url = https://grafana.com/grafana/plugins/
#################################### Grafana Image Renderer Plugin ########################## #################################### Grafana Image Renderer Plugin ##########################
[plugin.grafana-image-renderer] [plugin.grafana-image-renderer]
......
...@@ -665,7 +665,7 @@ ...@@ -665,7 +665,7 @@
; basic_auth_username = ; basic_auth_username =
; basic_auth_password = ; basic_auth_password =
# Metrics environment info adds dimensions to the `grafana_environment_info` metric, which # Metrics environment info adds dimensions to the `grafana_environment_info` metric, which
# can expose more information about the Grafana instance. # can expose more information about the Grafana instance.
[metrics.environment_info] [metrics.environment_info]
#exampleLabel1 = exampleValue1 #exampleLabel1 = exampleValue1
...@@ -757,6 +757,7 @@ ...@@ -757,6 +757,7 @@
;app_tls_skip_verify_insecure = false ;app_tls_skip_verify_insecure = false
# Enter a comma-separated list of plugin identifiers to identify plugins that are allowed to be loaded even if they lack a valid signature. # Enter a comma-separated list of plugin identifiers to identify plugins that are allowed to be loaded even if they lack a valid signature.
;allow_loading_unsigned_plugins = ;allow_loading_unsigned_plugins =
;marketplace_url = https://grafana.com/grafana/plugins/
#################################### Grafana Image Renderer Plugin ########################## #################################### Grafana Image Renderer Plugin ##########################
[plugin.grafana-image-renderer] [plugin.grafana-image-renderer]
......
...@@ -1336,6 +1336,10 @@ Set to `true` if you want to test alpha plugins that are not yet ready for gener ...@@ -1336,6 +1336,10 @@ Set to `true` if you want to test alpha plugins that are not yet ready for gener
Enter a comma-separated list of plugin identifiers to identify plugins that are allowed to be loaded even if they lack a valid signature. Enter a comma-separated list of plugin identifiers to identify plugins that are allowed to be loaded even if they lack a valid signature.
### marketplace_url
Custom install/learn more url for enterprise plugins. Defaults to https://grafana.com/grafana/plugins/.
<hr> <hr>
## [plugin.grafana-image-renderer] ## [plugin.grafana-image-renderer]
......
...@@ -55,6 +55,8 @@ export interface LicenseInfo { ...@@ -55,6 +55,8 @@ export interface LicenseInfo {
expiry: number; expiry: number;
licenseUrl: string; licenseUrl: string;
stateInfo: string; stateInfo: string;
hasValidLicense: boolean;
edition: string;
} }
/** /**
......
...@@ -114,6 +114,7 @@ export interface DataSourcePluginMeta<T extends KeyValue = {}> extends PluginMet ...@@ -114,6 +114,7 @@ export interface DataSourcePluginMeta<T extends KeyValue = {}> extends PluginMet
queryOptions?: PluginMetaQueryOptions; queryOptions?: PluginMetaQueryOptions;
sort?: number; sort?: number;
streaming?: boolean; streaming?: boolean;
unlicensed?: boolean;
} }
interface PluginMetaQueryOptions { interface PluginMetaQueryOptions {
......
...@@ -62,6 +62,7 @@ export class GrafanaBootConfig implements GrafanaConfig { ...@@ -62,6 +62,7 @@ export class GrafanaBootConfig implements GrafanaConfig {
rendererAvailable = false; rendererAvailable = false;
http2Enabled = false; http2Enabled = false;
dateFormats?: SystemDateFormatSettings; dateFormats?: SystemDateFormatSettings;
marketplaceUrl?: string;
constructor(options: GrafanaBootConfig) { constructor(options: GrafanaBootConfig) {
this.theme = options.bootData.user.lightTheme ? getTheme(GrafanaThemeType.Light) : getTheme(GrafanaThemeType.Dark); this.theme = options.bootData.user.lightTheme ? getTheme(GrafanaThemeType.Light) : getTheme(GrafanaThemeType.Dark);
......
...@@ -230,14 +230,17 @@ func (hs *HTTPServer) getFrontendSettingsMap(c *models.ReqContext) (map[string]i ...@@ -230,14 +230,17 @@ func (hs *HTTPServer) getFrontendSettingsMap(c *models.ReqContext) (map[string]i
"isEnterprise": hs.License.HasValidLicense(), "isEnterprise": hs.License.HasValidLicense(),
}, },
"licenseInfo": map[string]interface{}{ "licenseInfo": map[string]interface{}{
"hasLicense": hs.License.HasLicense(), "hasLicense": hs.License.HasLicense(),
"expiry": hs.License.Expiry(), "hasValidLicense": hs.License.HasValidLicense(),
"stateInfo": hs.License.StateInfo(), "expiry": hs.License.Expiry(),
"licenseUrl": hs.License.LicenseURL(c.SignedInUser), "stateInfo": hs.License.StateInfo(),
"licenseUrl": hs.License.LicenseURL(c.SignedInUser),
"edition": hs.License.Edition(),
}, },
"featureToggles": hs.Cfg.FeatureToggles, "featureToggles": hs.Cfg.FeatureToggles,
"rendererAvailable": hs.RenderService.IsAvailable(), "rendererAvailable": hs.RenderService.IsAvailable(),
"http2Enabled": hs.Cfg.Protocol == setting.HTTP2Scheme, "http2Enabled": hs.Cfg.Protocol == setting.HTTP2Scheme,
"marketplaceUrl": hs.Cfg.MarketplaceURL,
} }
return jsonObj, nil return jsonObj, nil
......
...@@ -8,7 +8,6 @@ import ( ...@@ -8,7 +8,6 @@ import (
"time" "time"
"github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
......
...@@ -7,6 +7,10 @@ import ( ...@@ -7,6 +7,10 @@ import (
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
) )
const (
openSource = "Open Source"
)
type OSSLicensingService struct { type OSSLicensingService struct {
Cfg *setting.Cfg `inject:""` Cfg *setting.Cfg `inject:""`
HooksService *hooks.HooksService `inject:""` HooksService *hooks.HooksService `inject:""`
...@@ -21,7 +25,7 @@ func (*OSSLicensingService) Expiry() int64 { ...@@ -21,7 +25,7 @@ func (*OSSLicensingService) Expiry() int64 {
} }
func (*OSSLicensingService) Edition() string { func (*OSSLicensingService) Edition() string {
return "Open Source" return openSource
} }
func (*OSSLicensingService) StateInfo() string { func (*OSSLicensingService) StateInfo() string {
......
...@@ -271,6 +271,7 @@ type Cfg struct { ...@@ -271,6 +271,7 @@ type Cfg struct {
PluginsAppsSkipVerifyTLS bool PluginsAppsSkipVerifyTLS bool
PluginSettings PluginSettings PluginSettings PluginSettings
PluginsAllowUnsigned []string PluginsAllowUnsigned []string
MarketplaceURL string
DisableSanitizeHtml bool DisableSanitizeHtml bool
EnterpriseLicensePath string EnterpriseLicensePath string
...@@ -794,6 +795,7 @@ func (cfg *Cfg) Load(args *CommandLineArgs) error { ...@@ -794,6 +795,7 @@ func (cfg *Cfg) Load(args *CommandLineArgs) error {
plug = strings.TrimSpace(plug) plug = strings.TrimSpace(plug)
cfg.PluginsAllowUnsigned = append(cfg.PluginsAllowUnsigned, plug) cfg.PluginsAllowUnsigned = append(cfg.PluginsAllowUnsigned, plug)
} }
cfg.MarketplaceURL = pluginsSection.Key("marketplace_url").MustString("https://grafana.com/grafana/plugins/")
cfg.Protocol = Protocol cfg.Protocol = Protocol
// Read and populate feature toggles list // Read and populate feature toggles list
......
...@@ -129,10 +129,9 @@ interface DataSourceTypeCardProps { ...@@ -129,10 +129,9 @@ interface DataSourceTypeCardProps {
const DataSourceTypeCard: FC<DataSourceTypeCardProps> = props => { const DataSourceTypeCard: FC<DataSourceTypeCardProps> = props => {
const { plugin, onLearnMoreClick } = props; const { plugin, onLearnMoreClick } = props;
const isPhantom = plugin.module === 'phantom'; const isPhantom = plugin.module === 'phantom';
const onClick = !isPhantom ? props.onClick : () => {}; const onClick = !isPhantom && !plugin.unlicensed ? props.onClick : () => {};
// find first plugin info link // find first plugin info link
const learnMoreLink = plugin.info.links && plugin.info.links.length > 0 ? plugin.info.links[0] : null; const learnMoreLink = plugin.info?.links?.length > 0 ? plugin.info.links[0] : null;
return ( return (
<Card <Card
...@@ -154,7 +153,7 @@ const DataSourceTypeCard: FC<DataSourceTypeCardProps> = props => { ...@@ -154,7 +153,7 @@ const DataSourceTypeCard: FC<DataSourceTypeCardProps> = props => {
{learnMoreLink.name} {learnMoreLink.name}
</LinkButton> </LinkButton>
)} )}
{!isPhantom && <Button>Select</Button>} {!isPhantom && <Button disabled={plugin.unlicensed}>Select</Button>}
</> </>
} }
labels={ labels={
......
import { DataSourcePluginMeta, PluginType } from '@grafana/data'; import { DataSourcePluginMeta, PluginType } from '@grafana/data';
import { DataSourcePluginCategory } from 'app/types'; import { DataSourcePluginCategory } from 'app/types';
import { config } from '../../../core/config';
export function buildCategories(plugins: DataSourcePluginMeta[]): DataSourcePluginCategory[] { export function buildCategories(plugins: DataSourcePluginMeta[]): DataSourcePluginCategory[] {
const categories: DataSourcePluginCategory[] = [ const categories: DataSourcePluginCategory[] = [
...@@ -22,10 +23,15 @@ export function buildCategories(plugins: DataSourcePluginMeta[]): DataSourcePlug ...@@ -22,10 +23,15 @@ export function buildCategories(plugins: DataSourcePluginMeta[]): DataSourcePlug
categoryIndex[category.id] = category; categoryIndex[category.id] = category;
} }
const { edition, hasValidLicense } = config.licenseInfo;
for (const plugin of plugins) { for (const plugin of plugins) {
const enterprisePlugin = enterprisePlugins.find(item => item.id === plugin.id);
// Force category for enterprise plugins // Force category for enterprise plugins
if (plugin.enterprise || enterprisePlugins.find(item => item.id === plugin.id)) { if (plugin.enterprise || enterprisePlugin) {
plugin.category = 'enterprise'; plugin.category = 'enterprise';
plugin.unlicensed = edition !== 'Open Source' && !hasValidLicense;
plugin.info.links = enterprisePlugin?.info?.links || plugin.info.links;
} }
// Fix link name // Fix link name
...@@ -197,7 +203,7 @@ function getPhantomPlugin(options: GetPhantomPluginOptions): DataSourcePluginMet ...@@ -197,7 +203,7 @@ function getPhantomPlugin(options: GetPhantomPluginOptions): DataSourcePluginMet
author: { name: 'Grafana Labs' }, author: { name: 'Grafana Labs' },
links: [ links: [
{ {
url: 'https://grafana.com/grafana/plugins/' + options.id, url: config.marketplaceUrl + options.id,
name: 'Install now', name: 'Install now',
}, },
], ],
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment