Provisioning: Allow disabling of image rendering instance wide (#111359)
This commit is contained in:
@@ -17,6 +17,9 @@ type RepositoryViewList struct {
|
||||
// The valid targets (can disable instance or folder types)
|
||||
AllowedTargets []SyncTargetType `json:"allowedTargets,omitempty"`
|
||||
|
||||
// Whether image rendering is allowed for dashboard previews
|
||||
AllowImageRendering bool `json:"allowImageRendering"`
|
||||
|
||||
// AvailableRepositoryTypes is the list of repository types supported in this instance (e.g. git, bitbucket, github, etc)
|
||||
AvailableRepositoryTypes []RepositoryType `json:"availableRepositoryTypes,omitempty"`
|
||||
|
||||
|
||||
@@ -1750,6 +1750,14 @@ func schema_pkg_apis_provisioning_v0alpha1_RepositoryViewList(ref common.Referen
|
||||
},
|
||||
},
|
||||
},
|
||||
"allowImageRendering": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Whether image rendering is allowed for dashboard previews",
|
||||
Default: false,
|
||||
Type: []string{"boolean"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"availableRepositoryTypes": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "AvailableRepositoryTypes is the list of repository types supported in this instance (e.g. git, bitbucket, github, etc)",
|
||||
@@ -1785,7 +1793,7 @@ func schema_pkg_apis_provisioning_v0alpha1_RepositoryViewList(ref common.Referen
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"items"},
|
||||
Required: []string{"allowImageRendering", "items"},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
|
||||
+5
-1
@@ -2238,4 +2238,8 @@ fail_tests_on_console = true
|
||||
# List of targets that can be controlled by a repository, separated by |.
|
||||
# Instance means the whole grafana instance will be controlled by a repository.
|
||||
# Folder limits it to a folder within the grafana instance.
|
||||
allowed_targets = instance|folder
|
||||
allowed_targets = instance|folder
|
||||
|
||||
# Whether image rendering is allowed for dashboard previews.
|
||||
# Requires image rendering service to be configured.
|
||||
allow_image_rendering = true
|
||||
@@ -85,7 +85,8 @@ type APIBuilder struct {
|
||||
// TODO: Set this up in the standalone API server
|
||||
onlyApiServer bool
|
||||
|
||||
allowedTargets []provisioning.SyncTargetType
|
||||
allowedTargets []provisioning.SyncTargetType
|
||||
allowImageRendering bool
|
||||
|
||||
features featuremgmt.FeatureToggles
|
||||
usageStats usagestats.Service
|
||||
@@ -133,6 +134,7 @@ func NewAPIBuilder(
|
||||
extraWorkers []jobs.Worker,
|
||||
jobHistoryConfig *JobHistoryConfig,
|
||||
allowedTargets []provisioning.SyncTargetType,
|
||||
allowImageRendering bool,
|
||||
newStandaloneClientFactoryFunc func(loopbackConfigProvider apiserver.RestConfigProvider) resources.ClientFactory, // optional, only used for standalone apiserver
|
||||
) *APIBuilder {
|
||||
var clients resources.ClientFactory
|
||||
@@ -161,6 +163,7 @@ func NewAPIBuilder(
|
||||
jobHistoryConfig: jobHistoryConfig,
|
||||
extraWorkers: extraWorkers,
|
||||
allowedTargets: allowedTargets,
|
||||
allowImageRendering: allowImageRendering,
|
||||
}
|
||||
|
||||
for _, builder := range extraBuilders {
|
||||
@@ -244,6 +247,7 @@ func RegisterAPIService(
|
||||
extraWorkers,
|
||||
createJobHistoryConfigFromSettings(cfg),
|
||||
allowedTargets,
|
||||
cfg.ProvisioningAllowImageRendering,
|
||||
nil,
|
||||
)
|
||||
apiregistration.RegisterAPI(builder)
|
||||
@@ -558,7 +562,18 @@ func (b *APIBuilder) Validate(ctx context.Context, a admission.Attributes, o adm
|
||||
cfg := repo.Config()
|
||||
|
||||
if !slices.Contains(b.allowedTargets, cfg.Spec.Sync.Target) {
|
||||
return fmt.Errorf("sync target %s is not supported", cfg.Spec.Sync.Target)
|
||||
list = append(list,
|
||||
field.Invalid(
|
||||
field.NewPath("spec", "target"),
|
||||
cfg.Spec.Sync.Target,
|
||||
"sync target is not supported"))
|
||||
}
|
||||
|
||||
if !b.allowImageRendering && cfg.Spec.GitHub != nil && cfg.Spec.GitHub.GenerateDashboardPreviews {
|
||||
list = append(list,
|
||||
field.Invalid(field.NewPath("spec", "generateDashboardPreviews"),
|
||||
cfg.Spec.GitHub.GenerateDashboardPreviews,
|
||||
"image rendering is not enabled"))
|
||||
}
|
||||
|
||||
if a.GetOperation() == admission.Update {
|
||||
|
||||
@@ -167,6 +167,7 @@ func (b *APIBuilder) handleSettings(w http.ResponseWriter, r *http.Request) {
|
||||
// FIXME: this shouldn't be here in provisioning but at the dual writer or something about the storage
|
||||
LegacyStorage: legacyStorage,
|
||||
AvailableRepositoryTypes: b.repoFactory.Types(),
|
||||
AllowImageRendering: b.allowImageRendering,
|
||||
}
|
||||
|
||||
for i, val := range all {
|
||||
|
||||
+13
-11
@@ -133,17 +133,18 @@ type Cfg struct {
|
||||
ProvisioningPath string
|
||||
PermittedProvisioningPaths []string
|
||||
// Provisioning config
|
||||
ProvisioningDisableControllers bool
|
||||
ProvisioningAllowedTargets []string
|
||||
ProvisioningRepositoryTypes []string
|
||||
ProvisioningLokiURL string
|
||||
ProvisioningLokiUser string
|
||||
ProvisioningLokiPassword string
|
||||
ProvisioningLokiTenantID string
|
||||
DataPath string
|
||||
LogsPath string
|
||||
PluginsPath string
|
||||
EnterpriseLicensePath string
|
||||
ProvisioningDisableControllers bool
|
||||
ProvisioningAllowedTargets []string
|
||||
ProvisioningAllowImageRendering bool
|
||||
ProvisioningRepositoryTypes []string
|
||||
ProvisioningLokiURL string
|
||||
ProvisioningLokiUser string
|
||||
ProvisioningLokiPassword string
|
||||
ProvisioningLokiTenantID string
|
||||
DataPath string
|
||||
LogsPath string
|
||||
PluginsPath string
|
||||
EnterpriseLicensePath string
|
||||
|
||||
// SMTP email settings
|
||||
Smtp SmtpSettings
|
||||
@@ -2124,6 +2125,7 @@ func (cfg *Cfg) readProvisioningSettings(iniFile *ini.File) error {
|
||||
if len(cfg.ProvisioningAllowedTargets) == 0 {
|
||||
cfg.ProvisioningAllowedTargets = []string{"instance", "folder"}
|
||||
}
|
||||
cfg.ProvisioningAllowImageRendering = iniFile.Section("provisioning").Key("allow_image_rendering").MustBool(true)
|
||||
|
||||
// Read job history configuration
|
||||
cfg.ProvisioningLokiURL = valueAsString(iniFile.Section("provisioning"), "loki_url", "")
|
||||
|
||||
@@ -4349,9 +4349,15 @@
|
||||
"description": "Summary shows a view of the configuration that is sanitized and is OK for logged in users to see",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"allowImageRendering",
|
||||
"items"
|
||||
],
|
||||
"properties": {
|
||||
"allowImageRendering": {
|
||||
"description": "Whether image rendering is allowed for dashboard previews",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"allowedTargets": {
|
||||
"description": "The valid targets (can disable instance or folder types)",
|
||||
"type": "array",
|
||||
|
||||
@@ -1468,6 +1468,8 @@ export type RepositoryView = {
|
||||
workflows: ('branch' | 'write')[];
|
||||
};
|
||||
export type RepositoryViewList = {
|
||||
/** Whether image rendering is allowed for dashboard previews */
|
||||
allowImageRendering: boolean;
|
||||
/** The valid targets (can disable instance or folder types) */
|
||||
allowedTargets?: ('folder' | 'instance')[];
|
||||
/** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */
|
||||
|
||||
@@ -2,8 +2,9 @@ import { UseFormRegister } from 'react-hook-form';
|
||||
|
||||
import { Trans, t } from '@grafana/i18n';
|
||||
import { Checkbox, ControlledCollapse, Field, Text, TextLink } from '@grafana/ui';
|
||||
import { useGetFrontendSettingsQuery } from 'app/api/clients/provisioning/v0alpha1';
|
||||
|
||||
import { checkImageRenderer, checkPublicAccess } from '../GettingStarted/features';
|
||||
import { checkImageRenderer, checkPublicAccess, checkImageRenderingAllowed } from '../GettingStarted/features';
|
||||
import { GETTING_STARTED_URL } from '../constants';
|
||||
import { RepositoryFormData } from '../types';
|
||||
|
||||
@@ -12,41 +13,45 @@ export interface ConfigFormGithubCollapseProps {
|
||||
}
|
||||
|
||||
export function ConfigFormGithubCollapse({ register }: ConfigFormGithubCollapseProps) {
|
||||
const settings = useGetFrontendSettingsQuery();
|
||||
const isPublic = checkPublicAccess();
|
||||
const hasImageRenderer = checkImageRenderer();
|
||||
const imageRenderingAllowed = checkImageRenderingAllowed(settings.data);
|
||||
|
||||
return (
|
||||
<ControlledCollapse
|
||||
label={t('provisioning.config-form-github-collapse.label-git-hub-features', 'GitHub features')}
|
||||
isOpen={true}
|
||||
>
|
||||
<Field>
|
||||
<Checkbox
|
||||
disabled={!hasImageRenderer || !isPublic}
|
||||
label={t('provisioning.finish-step.label-enable-previews', 'Enable dashboard previews in pull requests')}
|
||||
description={
|
||||
<>
|
||||
<Trans i18nKey="provisioning.finish-step.description-enable-previews">
|
||||
Adds an image preview of dashboard changes in pull requests. Images of your Grafana dashboards will be
|
||||
shared in your Git repository and visible to anyone with repository access.
|
||||
</Trans>{' '}
|
||||
<Text italic>
|
||||
<Trans i18nKey="provisioning.finish-step.description-image-rendering">
|
||||
Requires image rendering.{' '}
|
||||
<TextLink
|
||||
variant="bodySmall"
|
||||
external
|
||||
href="https://grafana.com/grafana/plugins/grafana-image-renderer"
|
||||
>
|
||||
Set up image rendering
|
||||
</TextLink>
|
||||
</Trans>
|
||||
</Text>
|
||||
</>
|
||||
}
|
||||
{...register('generateDashboardPreviews')}
|
||||
/>
|
||||
</Field>
|
||||
{imageRenderingAllowed && (
|
||||
<Field>
|
||||
<Checkbox
|
||||
disabled={!hasImageRenderer || !isPublic}
|
||||
label={t('provisioning.finish-step.label-enable-previews', 'Enable dashboard previews in pull requests')}
|
||||
description={
|
||||
<>
|
||||
<Trans i18nKey="provisioning.finish-step.description-enable-previews">
|
||||
Adds an image preview of dashboard changes in pull requests. Images of your Grafana dashboards will be
|
||||
shared in your Git repository and visible to anyone with repository access.
|
||||
</Trans>{' '}
|
||||
<Text italic>
|
||||
<Trans i18nKey="provisioning.finish-step.description-image-rendering">
|
||||
Requires image rendering.{' '}
|
||||
<TextLink
|
||||
variant="bodySmall"
|
||||
external
|
||||
href="https://grafana.com/grafana/plugins/grafana-image-renderer"
|
||||
>
|
||||
Set up image rendering
|
||||
</TextLink>
|
||||
</Trans>
|
||||
</Text>
|
||||
</>
|
||||
}
|
||||
{...register('generateDashboardPreviews')}
|
||||
/>
|
||||
</Field>
|
||||
)}
|
||||
|
||||
{!isPublic && (
|
||||
<Field label={t('provisioning.config-form-github-collapse.label-realtime-feedback', 'Realtime feedback')}>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { FeatureToggles } from '@grafana/data';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { RepositoryViewList } from 'app/api/clients/provisioning/v0alpha1';
|
||||
|
||||
export const requiredFeatureToggles: Array<keyof FeatureToggles> = ['provisioning', 'kubernetesDashboards'];
|
||||
|
||||
@@ -28,21 +29,34 @@ export const checkImageRenderer = (): boolean => {
|
||||
return Boolean(config.rendererAvailable);
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if image rendering is allowed by provisioning configuration
|
||||
* @param settings - Provisioning settings from the backend
|
||||
* @returns true if image rendering is allowed for provisioning workflows
|
||||
*/
|
||||
export const checkImageRenderingAllowed = (settings?: RepositoryViewList): boolean => {
|
||||
// Default to true if settings are not available
|
||||
return settings?.allowImageRendering !== false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the configuration status of all features
|
||||
* @param settings - Optional provisioning settings from the backend
|
||||
* @returns Object containing the status of required and optional features
|
||||
*/
|
||||
export const getConfigurationStatus = () => {
|
||||
export const getConfigurationStatus = (settings?: RepositoryViewList) => {
|
||||
const hasRequiredFeatures = checkRequiredFeatures();
|
||||
const hasPublicAccess = checkPublicAccess();
|
||||
const hasImageRenderer = checkImageRenderer();
|
||||
const imageRenderingAllowed = checkImageRenderingAllowed(settings);
|
||||
|
||||
return {
|
||||
hasRequiredFeatures,
|
||||
hasPublicAccess,
|
||||
hasImageRenderer,
|
||||
imageRenderingAllowed,
|
||||
missingOnlyOptionalFeatures: hasRequiredFeatures && (!hasPublicAccess || !hasImageRenderer),
|
||||
missingRequiredFeatures: !hasRequiredFeatures,
|
||||
everythingConfigured: hasRequiredFeatures && hasPublicAccess && hasImageRenderer,
|
||||
everythingConfigured: hasRequiredFeatures && hasPublicAccess && hasImageRenderer && imageRenderingAllowed,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -212,6 +212,7 @@ describe('BootstrapStep', () => {
|
||||
setup({
|
||||
settingsData: {
|
||||
legacyStorage: true,
|
||||
allowImageRendering: true,
|
||||
items: [],
|
||||
availableRepositoryTypes: [],
|
||||
},
|
||||
@@ -243,6 +244,7 @@ describe('BootstrapStep', () => {
|
||||
setup({
|
||||
settingsData: {
|
||||
legacyStorage: true,
|
||||
allowImageRendering: true,
|
||||
items: [],
|
||||
availableRepositoryTypes: [],
|
||||
},
|
||||
|
||||
@@ -3,8 +3,9 @@ import { useFormContext } from 'react-hook-form';
|
||||
|
||||
import { Trans, t } from '@grafana/i18n';
|
||||
import { Checkbox, Field, Input, Stack, Text, TextLink } from '@grafana/ui';
|
||||
import { useGetFrontendSettingsQuery } from 'app/api/clients/provisioning/v0alpha1';
|
||||
|
||||
import { checkImageRenderer, checkPublicAccess } from '../GettingStarted/features';
|
||||
import { checkImageRenderer, checkImageRenderingAllowed, checkPublicAccess } from '../GettingStarted/features';
|
||||
import { isGitProvider } from '../utils/repositoryTypes';
|
||||
|
||||
import { getGitProviderFields } from './fields';
|
||||
@@ -12,6 +13,7 @@ import { WizardFormData } from './types';
|
||||
|
||||
export function FinishStep() {
|
||||
const { register, watch, setValue } = useFormContext<WizardFormData>();
|
||||
const settings = useGetFrontendSettingsQuery();
|
||||
|
||||
const [type, readOnly] = watch(['repository.type', 'repository.readOnly']);
|
||||
|
||||
@@ -19,6 +21,7 @@ export function FinishStep() {
|
||||
const isGitBased = isGitProvider(type);
|
||||
const isPublic = checkPublicAccess();
|
||||
const hasImageRenderer = checkImageRenderer();
|
||||
const imageRenderingAllowed = checkImageRenderingAllowed(settings.data);
|
||||
|
||||
// Set sync enabled by default
|
||||
useEffect(() => {
|
||||
@@ -80,7 +83,7 @@ export function FinishStep() {
|
||||
</Field>
|
||||
)}
|
||||
|
||||
{isGithub && (
|
||||
{isGithub && imageRenderingAllowed && (
|
||||
<Field noMargin>
|
||||
<Checkbox
|
||||
{...register('repository.generateDashboardPreviews')}
|
||||
|
||||
@@ -116,6 +116,7 @@ describe('ProvisioningWizard', () => {
|
||||
data: {
|
||||
items: [],
|
||||
legacyStorage: false,
|
||||
allowImageRendering: true,
|
||||
availableRepositoryTypes: ['github', 'gitlab', 'bitbucket', 'git', 'local'],
|
||||
},
|
||||
isLoading: false,
|
||||
|
||||
Reference in New Issue
Block a user