Provisioning: Allow disabling of image rendering instance wide (#111359)

This commit is contained in:
Stephanie Hingtgen
2025-09-19 12:40:14 +03:00
committed by GitHub
parent 19be779274
commit 15ee224da5
13 changed files with 113 additions and 47 deletions
@@ -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
View File
@@ -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
+17 -2
View File
@@ -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 {
+1
View File
@@ -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
View File
@@ -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,