Provisioning: Make files API max file size configurable (#123793)

* Provisioning: Make files API max file size configurable

Replace the hard-coded 5 MB cap on writes with a new
[provisioning] max_file_size INI key, and apply the same cap
to reads (raw files and parsed resources) so large dashboards
can round-trip in both directions. 0 disables the limit.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Provisioning: Add integration tests for files API max_file_size

Wire ProvisioningMaxFileSize through testinfra.GrafanaOpts and add
a WithProvisioningMaxFileSize helper in the provisioning common
test package. Add a new pkg/tests/apis/provisioning/maxfilesize
package with integration tests covering: read of an under-cap
raw file, read of an over-cap raw file (HTTP 413), write of an
over-cap resource, and the 0 = unlimited semantics on a separate
server.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Provisioning: Keep NewAPIBuilder signature stable for enterprise

Initialise APIBuilder.maxFileSize to the default 5 MB inside
NewAPIBuilder and let RegisterAPIService overwrite it from
cfg.ProvisioningMaxFileSize after construction, mirroring the
existing pattern used for webhookSecretRotationInterval. This
keeps grafana-enterprise's NewAPIBuilder call site unchanged.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Provisioning: Drop standalone-server max_file_size integration test

The _Disabled variant spun up a second Grafana via RunGrafana, but
inside a package whose shared env already owns a Grafana, the two
instances share the package-wide test database. In Postgres-backed
CI the shared env's controller picks up the second server's sync
jobs and rejects them against its own permitted_provisioning_paths.
"0 = unlimited" is already covered by
TestHandleGetRawFile_MaxFileSize/zero_disables_limit, so drop the
flaky integration variant and document why in a comment.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Provisioning: Address Copilot review on max_file_size

- readBody now returns apierrors.NewRequestEntityTooLargeError on
  overflow so writes surface HTTP 413, matching read-side behavior.
- Document the <=0 = unlimited contract consistently across the INI
  comment, Cfg field, APIBuilder default and the test option helper.
- request_test.go: add zero / negative maxSize cases for readBody.
- maxfilesize integration tests: assert HTTP 413 for the oversized
  POST and verify the under-limit raw GET returns the file verbatim.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Provisioning: Address Copilot follow-up on max_file_size

- Reject oversized files at the repository boundary instead of after
  parsing: handleRequest now wraps the ReaderWriter with a
  sizeLimitedReaderWriter whose Read returns 413 immediately, so
  DualReadWriter.Read no longer parses or DryRuns oversized payloads.
- Centralize the 5 MiB default in setting.ProvisioningMaxFileSizeDefault
  and reference it from both the config parser and the APIBuilder
  default initialiser.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Provisioning: Clarify max_file_size unit as MiB in docs

Copilot flagged that "5 MB" in the field comment and INI doc string
was inconsistent with the bytes value (5242880 = 5 MiB). Reword both
to spell out "5 MiB" so operators see the same units everywhere.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: Alejandro Malavet <alejandro.malavet@grafana.com>
This commit is contained in:
Roberto Jiménez Sánchez
2026-05-12 15:08:36 -04:00
committed by GitHub
co-authored by Claude Opus 4.7 Alejandro Malavet
parent 3ffcf379ca
commit 7c62aaa7ee
11 changed files with 331 additions and 13 deletions
+6
View File
@@ -2433,6 +2433,12 @@ folders_api_version = v1
# Default is 100. If set to 0, the size check is disabled.
max_incremental_changes = 100
# Maximum file size in bytes for files read from or written to a provisioning repository
# through the files API. Applies symmetrically to GET (reads) and POST/PUT writes.
# Default is 5242880 bytes (5 MiB, i.e. 5 * 1024 * 1024). Set to 0 (or any
# non-positive value) for unlimited.
max_file_size = 5242880
# Public-facing root URL of this Grafana instance, used by provisioning to construct URLs
# that must be reachable from external systems (e.g. GitHub PR-comment image fetchers and
# GitHub webhook deliveries). When empty, falls back to [server] root_url. Set this when
+44 -10
View File
@@ -23,11 +23,6 @@ import (
"github.com/grafana/grafana/pkg/registry/apis/provisioning/resources"
)
const (
// Files endpoint max size for dashboards etc (5MB)
filesMaxBodySize = 5 * 1024 * 1024
)
type filesConnector struct {
getter RepoGetter
access auth.AccessChecker
@@ -35,9 +30,12 @@ type filesConnector struct {
clients resources.ClientFactory
folderMetadataEnabled bool
folderAPIVersion string
// maxFileSize caps the size in bytes of files read from or written to the
// repository through this connector. <=0 disables the check.
maxFileSize int64
}
func NewFilesConnector(getter RepoGetter, parsers resources.ParserFactory, clients resources.ClientFactory, access auth.AccessChecker, folderMetadataEnabled bool, folderAPIVersion string) *filesConnector {
func NewFilesConnector(getter RepoGetter, parsers resources.ParserFactory, clients resources.ClientFactory, access auth.AccessChecker, folderMetadataEnabled bool, folderAPIVersion string, maxFileSize int64) *filesConnector {
return &filesConnector{
getter: getter,
parsers: parsers,
@@ -45,9 +43,41 @@ func NewFilesConnector(getter RepoGetter, parsers resources.ParserFactory, clien
access: access,
folderMetadataEnabled: folderMetadataEnabled,
folderAPIVersion: folderAPIVersion,
maxFileSize: maxFileSize,
}
}
// sizeLimitedReaderWriter wraps a repository.ReaderWriter and rejects reads
// that exceed maxBytes. The check fires immediately after the underlying
// repository returns the file bytes, so callers (including DualReadWriter
// which parses the result) never see oversized payloads.
type sizeLimitedReaderWriter struct {
repository.ReaderWriter
maxBytes int64
}
func (s *sizeLimitedReaderWriter) Read(ctx context.Context, path, ref string) (*repository.FileInfo, error) {
info, err := s.ReaderWriter.Read(ctx, path, ref)
if err != nil {
return info, err
}
if s.maxBytes > 0 && info != nil && int64(len(info.Data)) > s.maxBytes {
return nil, apierrors.NewRequestEntityTooLargeError(
fmt.Sprintf("file %q is %d bytes; max allowed is %d bytes", info.Path, len(info.Data), s.maxBytes),
)
}
return info, nil
}
// withSizeLimit returns rw wrapped so its Read method enforces the connector's
// configured max file size. Returns rw unchanged when the cap is disabled.
func (c *filesConnector) withSizeLimit(rw repository.ReaderWriter) repository.ReaderWriter {
if c.maxFileSize <= 0 {
return rw
}
return &sizeLimitedReaderWriter{ReaderWriter: rw, maxBytes: c.maxFileSize}
}
func (*filesConnector) New() runtime.Object {
// This is added as the "ResponseType" regardless what ProducesObject() returns
return &provisioning.ResourceWrapper{}
@@ -105,6 +135,10 @@ func (c *filesConnector) handleRequest(ctx context.Context, name string, r *http
responder.Error(apierrors.NewBadRequest("repository does not support read-writing"))
return
}
// Enforce max_file_size right at the repo boundary so oversized payloads
// are rejected before parsing/DryRun runs in DualReadWriter.Read or before
// the bytes are streamed back from handleGetRawFile.
readWriter = c.withSizeLimit(readWriter)
dualReadWriter, authorizer, err := c.createDualReadWriter(ctx, repo, readWriter)
if err != nil {
@@ -310,7 +344,7 @@ func (c *filesConnector) handlePost(ctx context.Context, r *http.Request, opts r
return dualReadWriter.CreateFolder(ctx, opts)
}
data, err := readBody(r, filesMaxBodySize)
data, err := readBody(r, c.maxFileSize)
if err != nil {
return nil, err
}
@@ -326,7 +360,7 @@ func (c *filesConnector) handlePost(ctx context.Context, r *http.Request, opts r
func (c *filesConnector) handleMove(ctx context.Context, r *http.Request, opts resources.DualWriteOptions, isDir bool, dualReadWriter *resources.DualReadWriter) (*provisioning.ResourceWrapper, error) {
// For move operations, only read body for file moves (not directory moves)
if !isDir {
data, err := readBody(r, filesMaxBodySize)
data, err := readBody(r, c.maxFileSize)
if err != nil {
return nil, err
}
@@ -348,7 +382,7 @@ func (c *filesConnector) handlePut(ctx context.Context, r *http.Request, opts re
return nil, apierrors.NewMethodNotSupported(provisioning.RepositoryResourceInfo.GroupResource(), r.Method)
}
data, err := readBody(r, filesMaxBodySize)
data, err := readBody(r, c.maxFileSize)
if err != nil {
return nil, err
}
@@ -362,7 +396,7 @@ func (c *filesConnector) handlePut(ctx context.Context, r *http.Request, opts re
}
func (c *filesConnector) handleFolderMetadataUpdate(ctx context.Context, r *http.Request, opts resources.DualWriteOptions, dualReadWriter *resources.DualReadWriter) (*provisioning.ResourceWrapper, error) {
data, err := readBody(r, filesMaxBodySize)
data, err := readBody(r, c.maxFileSize)
if err != nil {
return nil, err
}
@@ -536,6 +536,66 @@ func TestHandleGetRawFile_FolderScopedAuth(t *testing.T) {
})
}
func TestHandleGetRawFile_MaxFileSize(t *testing.T) {
const path = "README.md"
repo := &provisioningapi.Repository{
ObjectMeta: metav1.ObjectMeta{Name: "test-repo"},
Spec: provisioningapi.RepositorySpec{
Sync: provisioningapi.SyncOptions{Target: provisioningapi.SyncTargetTypeFolder},
},
}
tests := []struct {
name string
maxFileSize int64
dataSize int
wantTooBig bool
}{
{name: "under limit", maxFileSize: 1024, dataSize: 512},
{name: "exactly at limit", maxFileSize: 1024, dataSize: 1024},
{name: "over limit", maxFileSize: 1024, dataSize: 2048, wantTooBig: true},
{name: "zero disables limit", maxFileSize: 0, dataSize: 10 * 1024 * 1024},
{name: "negative disables limit", maxFileSize: -1, dataSize: 10 * 1024 * 1024},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
mockReadWriter := repository.NewMockReaderWriter(t)
mockAccess := auth.NewMockAccessChecker(t)
mockAccess.EXPECT().Check(mock.Anything, mock.Anything, mock.Anything).Return(nil).Maybe()
mockReadWriter.EXPECT().Config().Return(repo).Maybe()
authorizer := resources.NewAuthorizer(repo, mockReadWriter, mockAccess, false)
mockReadWriter.EXPECT().Read(mock.Anything, path, "").Return(&repository.FileInfo{
Path: path,
Data: make([]byte, tc.dataSize),
Ref: "main",
}, nil)
connector := &filesConnector{access: mockAccess, maxFileSize: tc.maxFileSize}
// handleRequest wraps readWriter with the size limiter at the
// connector boundary. Mirror that here so the unit test exercises
// the same enforcement point as production.
limited := connector.withSizeLimit(mockReadWriter)
_, err := connector.handleGetRawFile(
context.Background(),
resources.DualWriteOptions{Path: path},
limited,
authorizer,
)
if tc.wantTooBig {
require.Error(t, err)
assert.True(t, apierrors.IsRequestEntityTooLargeError(err), "expected 413 RequestEntityTooLarge, got %v", err)
} else {
require.NoError(t, err)
}
})
}
}
func TestIsRawFileIntegration(t *testing.T) {
tests := []struct {
name string
+9 -1
View File
@@ -139,6 +139,7 @@ type APIBuilder struct {
registry prometheus.Registerer
quotaGetter quotas.QuotaGetter
folderMetadataEnabled bool
maxFileSize int64
incrementalPolicy repository.IncrementalSyncPolicy
folderAPIVersion string
webhookSecretRotationInterval time.Duration
@@ -241,6 +242,10 @@ func NewAPIBuilder(
folderMetadataEnabled: folderMetadataEnabled,
folderAPIVersion: folderAPIVersion,
incrementalPolicy: incrementalPolicy,
// Default cap for the files API. Callers (e.g. RegisterAPIService)
// may overwrite b.maxFileSize after construction; any non-positive
// value (<=0) disables the cap.
maxFileSize: setting.ProvisioningMaxFileSizeDefault,
}
for _, builder := range extraBuilders {
@@ -315,6 +320,7 @@ func RegisterAPIService(
jobHistoryConfig := createJobHistoryConfigFromSettings(cfg)
folderMetadataEnabled := features.IsEnabledGlobally(featuremgmt.FlagProvisioningFolderMetadata) //nolint:staticcheck
folderAPIVersion := cfg.ProvisioningFolderAPIVersion
maxFileSize := cfg.ProvisioningMaxFileSize
incrementalPolicy := repository.NewIncrementalSyncPolicy(folderMetadataEnabled, cfg.ProvisioningMaxIncrementalChanges)
// Register v0alpha1 (preferred version)
@@ -353,6 +359,7 @@ func RegisterAPIService(
return nil, err
}
builder.webhookSecretRotationInterval = cfg.ProvisioningWebhookSecretRotationInterval
builder.maxFileSize = maxFileSize
apiregistration.RegisterAPI(builder)
// Register v1beta1
@@ -391,6 +398,7 @@ func RegisterAPIService(
return nil, err
}
v1beta1Builder.webhookSecretRotationInterval = cfg.ProvisioningWebhookSecretRotationInterval
v1beta1Builder.maxFileSize = maxFileSize
apiregistration.RegisterAPI(v1beta1Builder)
// Return the preferred (v0alpha1) builder since it runs controllers/workers
@@ -802,7 +810,7 @@ func (b *APIBuilder) UpdateAPIGroupInfo(apiGroupInfo *genericapiserver.APIGroupI
// connector via ProvisioningAuthorizer.AuthorizeResource, and repository-
// level operations remain Admin-gated by authorizeRepositorySubresource.
filesAccess := auth.NewVerbAwareAccessChecker(b.accessWithViewer, b.accessWithEditor)
storage[provisioning.RepositoryResourceInfo.StoragePath("files")] = NewFilesConnector(b, b.parsers, b.clients, filesAccess, b.folderMetadataEnabled, b.folderAPIVersion)
storage[provisioning.RepositoryResourceInfo.StoragePath("files")] = NewFilesConnector(b, b.parsers, b.clients, filesAccess, b.folderMetadataEnabled, b.folderAPIVersion, b.maxFileSize)
storage[provisioning.RepositoryResourceInfo.StoragePath("refs")] = NewRefsConnector(b)
storage[provisioning.RepositoryResourceInfo.StoragePath("resources")] = &listConnector{
getter: b,
+14 -2
View File
@@ -7,6 +7,8 @@ import (
"io"
"net/http"
"strings"
apierrors "k8s.io/apimachinery/pkg/api/errors"
)
const (
@@ -18,14 +20,24 @@ const (
errMsgRequestTooLarge = "request body too large"
)
// readBody reads the request body and limits the size
// readBody reads the request body and limits the size. A non-positive
// maxSize disables the limit.
func readBody(r *http.Request, maxSize int64) ([]byte, error) {
if maxSize <= 0 {
body, err := io.ReadAll(r.Body)
if err != nil {
return nil, fmt.Errorf("error reading request body: %w", err)
}
return body, nil
}
limitedBody := http.MaxBytesReader(nil, r.Body, maxSize)
body, err := io.ReadAll(limitedBody)
if err != nil {
var maxBytesError *http.MaxBytesError
if errors.As(err, &maxBytesError) {
return nil, fmt.Errorf("%s: max size %d bytes", errMsgRequestTooLarge, maxSize)
return nil, apierrors.NewRequestEntityTooLargeError(
fmt.Sprintf("%s: max size %d bytes", errMsgRequestTooLarge, maxSize),
)
}
return nil, fmt.Errorf("error reading request body: %w", err)
}
@@ -31,6 +31,16 @@ func TestReadBody(t *testing.T) {
body: "1234567890",
maxSize: 10,
},
{
name: "zero maxSize disables the cap",
body: "anything goes when the cap is unlimited",
maxSize: 0,
},
{
name: "negative maxSize disables the cap",
body: "anything goes when the cap is unlimited",
maxSize: -1,
},
}
for _, tt := range tests {
+7
View File
@@ -65,6 +65,11 @@ const zoneInfo = "ZONEINFO"
// Default renderer auth token from [rendering]renderer_token.
const DefaultRendererAuthToken = "-"
// ProvisioningMaxFileSizeDefault is the default value for the
// [provisioning] max_file_size key (5 MiB). It bounds files read from or
// written to a provisioning repository through the files API.
const ProvisioningMaxFileSizeDefault int64 = 5 * 1024 * 1024
var (
customInitPath = "conf/custom.ini"
@@ -163,6 +168,7 @@ type Cfg struct {
ProvisioningMaxRepositories int64 // default 10, 0 in config = unlimited (converted to -1 internally)
ProvisioningFolderAPIVersion string // "v1" (default for on-prem) or "v1beta1"
ProvisioningMaxIncrementalChanges int // default 100, 0 in config = unlimited
ProvisioningMaxFileSize int64 // bytes; default 5 MiB (5242880); <=0 = unlimited
ProvisioningWebhookSecretRotationInterval time.Duration // default 30 days
ProvisioningPublicRootURL string // public-facing root URL of this Grafana instance for provisioning consumers (webhooks, screenshots); falls back to AppURL when empty
DataPath string
@@ -2469,6 +2475,7 @@ func (cfg *Cfg) readProvisioningSettings(iniFile *ini.File) error {
cfg.ProvisioningMaxRepositories = iniFile.Section("provisioning").Key("max_repositories").MustInt64(10)
cfg.ProvisioningFolderAPIVersion = iniFile.Section("provisioning").Key("folders_api_version").MustString("v1")
cfg.ProvisioningMaxIncrementalChanges = iniFile.Section("provisioning").Key("max_incremental_changes").MustInt(100)
cfg.ProvisioningMaxFileSize = iniFile.Section("provisioning").Key("max_file_size").MustInt64(ProvisioningMaxFileSizeDefault)
cfg.ProvisioningWebhookSecretRotationInterval = iniFile.Section("provisioning").Key("webhook_secret_rotation_interval").MustDuration(30 * 24 * time.Hour)
cfg.ProvisioningPublicRootURL = strings.TrimRight(valueAsString(iniFile.Section("provisioning"), "public_root_url", ""), "/")
@@ -1215,6 +1215,17 @@ func WithProvisioningMaxIncrementalChanges(n int) GrafanaOption {
}
}
// WithProvisioningMaxFileSize overrides the per-file size cap enforced by the
// files API on both reads and writes. A small value (e.g. 1024) keeps the
// fixture cheap to generate; any non-positive value (<=0) disables the check
// entirely. Pass an int64 — the helper takes its address so GrafanaOpts can
// distinguish "not set" (nil) from an explicit 0.
func WithProvisioningMaxFileSize(n int64) GrafanaOption {
return func(opts *testinfra.GrafanaOpts) {
opts.ProvisioningMaxFileSize = &n
}
}
// WithoutExportFeatureFlag disables the provisioningExport feature flag.
func WithoutExportFeatureFlag(opts *testinfra.GrafanaOpts) {
// Remove provisioningExport from the enabled feature toggles
@@ -0,0 +1,135 @@
package maxfilesize
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
"testing"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/tests/apis/provisioning/common"
)
// TestIntegrationProvisioning_MaxFileSize_RawRead exercises the read-side
// enforcement of [provisioning] max_file_size on raw files (e.g. README.md).
// A file under the configured cap is served as-is; a file over the cap is
// rejected with HTTP 413 Request Entity Too Large.
func TestIntegrationProvisioning_MaxFileSize_RawRead(t *testing.T) {
helper := sharedHelper(t)
const repo = "max-file-size-raw-read"
helper.CreateLocalRepo(t, common.TestRepo{
Name: repo,
LocalPath: helper.ProvisioningPath,
SyncTarget: "instance",
Workflows: []string{"write"},
SkipResourceAssertions: true,
})
smallReadme := []byte("# small README\n")
largeReadme := bytes.Repeat([]byte("X"), int(testMaxFileSize)+1)
helper.WriteToProvisioningPath(t, "README.md", smallReadme)
helper.WriteToProvisioningPath(t, "huge/README.md", largeReadme)
addr := helper.GetEnv().Server.HTTPServer.Listener.Addr().String()
t.Run("GET small raw file under limit succeeds", func(t *testing.T) {
url := fmt.Sprintf("http://admin:admin@%s/apis/provisioning.grafana.app/v0alpha1/namespaces/default/repositories/%s/files/README.md", addr, repo)
req, err := http.NewRequest(http.MethodGet, url, nil)
require.NoError(t, err)
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
// nolint:errcheck
defer resp.Body.Close()
require.Equal(t, http.StatusOK, resp.StatusCode, "small README should be served")
body, err := io.ReadAll(resp.Body)
require.NoError(t, err)
var wrapper struct {
Resource struct {
File struct {
Content string `json:"content"`
} `json:"file"`
} `json:"resource"`
}
require.NoError(t, json.Unmarshal(body, &wrapper))
require.Equal(t, string(smallReadme), wrapper.Resource.File.Content,
"under-limit raw file should be served verbatim")
})
t.Run("GET raw file over limit returns 413", func(t *testing.T) {
url := fmt.Sprintf("http://admin:admin@%s/apis/provisioning.grafana.app/v0alpha1/namespaces/default/repositories/%s/files/huge/README.md", addr, repo)
req, err := http.NewRequest(http.MethodGet, url, nil)
require.NoError(t, err)
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
// nolint:errcheck
defer resp.Body.Close()
require.Equal(t, http.StatusRequestEntityTooLarge, resp.StatusCode, "oversized README should be rejected with 413")
body, err := io.ReadAll(resp.Body)
require.NoError(t, err)
require.Contains(t, string(body), "max allowed", "error body should advertise the cap")
})
}
// TestIntegrationProvisioning_MaxFileSize_Write exercises the write-side
// enforcement: a POST whose body exceeds [provisioning] max_file_size is
// rejected before the resource is parsed or persisted.
func TestIntegrationProvisioning_MaxFileSize_Write(t *testing.T) {
helper := sharedHelper(t)
ctx := context.Background()
const repo = "max-file-size-write"
helper.CreateLocalRepo(t, common.TestRepo{
Name: repo,
LocalPath: helper.ProvisioningPath,
SyncTarget: "instance",
Workflows: []string{"write"},
SkipResourceAssertions: true,
})
// A minimal but well-formed dashboard JSON, padded to exceed the cap.
// Padding lives inside the title field so the body is still valid JSON.
pad := strings.Repeat("p", int(testMaxFileSize)+1)
oversized, err := json.Marshal(map[string]any{
"apiVersion": "dashboard.grafana.app/v1beta1",
"kind": "Dashboard",
"metadata": map[string]any{"name": "huge"},
"spec": map[string]any{"title": pad},
})
require.NoError(t, err)
require.Greater(t, len(oversized), int(testMaxFileSize), "fixture must exceed the configured cap")
var statusCode int
result := helper.AdminREST.Post().
Namespace("default").
Resource("repositories").
Name(repo).
SubResource("files", "huge.json").
Body(oversized).
SetHeader("Content-Type", "application/json").
Do(ctx).StatusCode(&statusCode)
require.Error(t, result.Error(), "oversized POST should be rejected")
require.Equal(t, http.StatusRequestEntityTooLarge, statusCode,
"oversized POST should return HTTP 413; got %v", result.Error())
require.Contains(t, result.Error().Error(), "request body too large",
"error should advertise the size cap; got %v", result.Error())
}
// The "0 = unlimited" semantics are covered by the unit test
// TestHandleGetRawFile_MaxFileSize/zero_disables_limit. We deliberately do
// not exercise it as a separate integration test here: spinning up a second
// Grafana inside this package would share the package-wide test database
// with the shared env, and the shared env's controllers would then attempt
// to validate the second server's repos against their own (different)
// permitted_provisioning_paths and fail the sync.
@@ -0,0 +1,26 @@
package maxfilesize
import (
"testing"
"github.com/grafana/grafana/pkg/tests/apis/provisioning/common"
)
// testMaxFileSize is the per-file cap installed in the shared test server.
// Small enough that fixtures stay cheap; large enough that ordinary
// fixtures (text-options.json etc.) still fit comfortably under it.
const testMaxFileSize int64 = 4 * 1024
var env = common.NewSharedEnv(
common.WithoutProvisioningFolderMetadata,
common.WithProvisioningMaxFileSize(testMaxFileSize),
)
func sharedHelper(t *testing.T) *common.ProvisioningTestHelper {
t.Helper()
return env.GetCleanHelper(t)
}
func TestMain(m *testing.M) {
env.RunTestMain(m)
}
+9
View File
@@ -800,6 +800,14 @@ func createGrafDir(t *testing.T, tmpDir string, opts GrafanaOpts) (string, strin
_, err = provisioningSect.NewKey("max_incremental_changes", fmt.Sprintf("%d", *opts.ProvisioningMaxIncrementalChanges))
require.NoError(t, err)
}
// nil means "use the ini default" (5 MB). Non-nil writes the value,
// including 0 which disables the per-file size check.
if opts.ProvisioningMaxFileSize != nil {
provisioningSect, err := getOrCreateSection("provisioning")
require.NoError(t, err)
_, err = provisioningSect.NewKey("max_file_size", fmt.Sprintf("%d", *opts.ProvisioningMaxFileSize))
require.NoError(t, err)
}
if opts.EnableSCIM {
scimSection, err := getOrCreateSection("auth.scim")
require.NoError(t, err)
@@ -962,6 +970,7 @@ type GrafanaOpts struct {
ProvisioningMaxRepositories int64
ProvisioningFolderAPIVersion string
ProvisioningMaxIncrementalChanges *int
ProvisioningMaxFileSize *int64
GrafanaComSSOAPIToken string
LicensePath string
EnableRecordingRules bool