Provisioning: Default to folder sync only and block new instance sync repositories (#115569)
* Default to folder sync only and block new instance sync repositories - Change default allowed_targets to folder-only in backend configuration - Modify validation to only enforce allowedTargets on CREATE operations - Add deprecation warning for existing instance sync repositories - Update frontend defaults and tests to reflect new behavior Fixes #619 * Update warning message: change 'deprecated' to 'not fully supported' * Fix health check: don't validate allowedTargets for existing repositories Health checks for existing repositories should treat them as UPDATE operations, not CREATE operations, so they don't fail validation for instance sync target. * Fix tests and update i18n translations - Update BootstrapStep tests to reflect folder-only default behavior - Run i18n-extract to update translation file structure * Fix integration tests * Fix tests * Fix provisioning test wizard * Fix fronted test
This commit is contained in:
@@ -673,7 +673,8 @@ func (b *APIBuilder) Validate(ctx context.Context, a admission.Attributes, o adm
|
||||
//
|
||||
// the only time to add configuration checks here is if you need to compare
|
||||
// the incoming change to the current configuration
|
||||
list := b.validator.ValidateRepository(repo)
|
||||
isCreate := a.GetOperation() == admission.Create
|
||||
list := b.validator.ValidateRepository(repo, isCreate)
|
||||
cfg := repo.Config()
|
||||
|
||||
if a.GetOperation() == admission.Update {
|
||||
|
||||
@@ -2167,7 +2167,7 @@ func (cfg *Cfg) readProvisioningSettings(iniFile *ini.File) error {
|
||||
}
|
||||
cfg.ProvisioningAllowedTargets = iniFile.Section("provisioning").Key("allowed_targets").Strings("|")
|
||||
if len(cfg.ProvisioningAllowedTargets) == 0 {
|
||||
cfg.ProvisioningAllowedTargets = []string{"instance", "folder"}
|
||||
cfg.ProvisioningAllowedTargets = []string{"folder"}
|
||||
}
|
||||
cfg.ProvisioningAllowImageRendering = iniFile.Section("provisioning").Key("allow_image_rendering").MustBool(true)
|
||||
cfg.ProvisioningMinSyncInterval = iniFile.Section("provisioning").Key("min_sync_interval").MustDuration(10 * time.Second)
|
||||
|
||||
@@ -44,6 +44,7 @@ func TestIntegrationProvisioning_ExportUnifiedToRepository(t *testing.T) {
|
||||
const repo = "local-repository"
|
||||
testRepo := TestRepo{
|
||||
Name: repo,
|
||||
Target: "instance", // Export is only supported for instance sync
|
||||
Copies: map[string]string{}, // No initial files needed for export test
|
||||
ExpectedDashboards: 4, // 4 dashboards created above (v0, v1, v2alpha1, v2beta1)
|
||||
ExpectedFolders: 0, // No folders expected after sync
|
||||
@@ -177,6 +178,7 @@ func TestIntegrationProvisioning_ExportDashboardsWithStoredVersions(t *testing.T
|
||||
const repo = "version-test-repository"
|
||||
testRepo := TestRepo{
|
||||
Name: repo,
|
||||
Target: "instance", // Export is only supported for instance sync
|
||||
Copies: map[string]string{},
|
||||
ExpectedDashboards: len(tests),
|
||||
ExpectedFolders: 0,
|
||||
|
||||
@@ -695,6 +695,9 @@ func runGrafana(t *testing.T, options ...grafanaOption) *provisioningTestHelper
|
||||
},
|
||||
},
|
||||
PermittedProvisioningPaths: ".|" + provisioningPath,
|
||||
// Allow both folder and instance sync targets for tests
|
||||
// (instance is needed for export jobs, folder for most operations)
|
||||
ProvisioningAllowedTargets: []string{"folder", "instance"},
|
||||
}
|
||||
for _, o := range options {
|
||||
o(&opts)
|
||||
|
||||
@@ -24,10 +24,10 @@ func TestIntegrationProvisioning_JobValidation(t *testing.T) {
|
||||
const repo = "job-validation-test-repo"
|
||||
testRepo := TestRepo{
|
||||
Name: repo,
|
||||
Target: "instance",
|
||||
Target: "folder",
|
||||
Copies: map[string]string{},
|
||||
ExpectedDashboards: 0,
|
||||
ExpectedFolders: 0,
|
||||
ExpectedFolders: 1, // folder sync creates a folder
|
||||
}
|
||||
helper.CreateRepo(t, testRepo)
|
||||
|
||||
|
||||
@@ -24,14 +24,15 @@ func TestIntegrationProvisioning_MoveJob(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
const repo = "move-test-repo"
|
||||
testRepo := TestRepo{
|
||||
Name: repo,
|
||||
Name: repo,
|
||||
Target: "folder",
|
||||
Copies: map[string]string{
|
||||
"testdata/all-panels.json": "dashboard1.json",
|
||||
"testdata/text-options.json": "dashboard2.json",
|
||||
"testdata/timeline-demo.json": "folder/dashboard3.json",
|
||||
},
|
||||
ExpectedDashboards: 3,
|
||||
ExpectedFolders: 1,
|
||||
ExpectedFolders: 2, // folder sync creates a folder for the repo + one nested folder
|
||||
}
|
||||
helper.CreateRepo(t, testRepo)
|
||||
|
||||
@@ -236,6 +237,7 @@ func TestIntegrationProvisioning_MoveJob(t *testing.T) {
|
||||
const refRepo = "move-ref-test-repo"
|
||||
helper.CreateRepo(t, TestRepo{
|
||||
Name: refRepo,
|
||||
Target: "folder",
|
||||
SkipResourceAssertions: true, // HACK: I am not sure why sometimes it's 6 or 3 dashbaords.
|
||||
})
|
||||
|
||||
|
||||
@@ -578,7 +578,13 @@ func TestIntegrationProvisioning_RunLocalRepository(t *testing.T) {
|
||||
const targetPath = "all-panels.json"
|
||||
|
||||
// Set up the repository.
|
||||
helper.CreateRepo(t, TestRepo{Name: repo})
|
||||
helper.CreateRepo(t, TestRepo{
|
||||
Name: repo,
|
||||
Target: "folder",
|
||||
ExpectedDashboards: 0,
|
||||
ExpectedFolders: 1, // folder sync creates a folder for the repo
|
||||
SkipResourceAssertions: false,
|
||||
})
|
||||
|
||||
// Write a file -- this will create it *both* in the local file system, and in grafana
|
||||
t.Run("write all panels", func(t *testing.T) {
|
||||
@@ -744,10 +750,10 @@ func TestIntegrationProvisioning_ImportAllPanelsFromLocalRepository(t *testing.T
|
||||
// Set up the repository and the file to import.
|
||||
testRepo := TestRepo{
|
||||
Name: repo,
|
||||
Target: "instance",
|
||||
Target: "folder",
|
||||
Copies: map[string]string{"testdata/all-panels.json": "all-panels.json"},
|
||||
ExpectedDashboards: 1,
|
||||
ExpectedFolders: 0,
|
||||
ExpectedFolders: 1, // folder sync creates a folder
|
||||
}
|
||||
// We create the repository
|
||||
helper.CreateRepo(t, testRepo)
|
||||
|
||||
@@ -21,13 +21,14 @@ func TestIntegrationProvisioning_Stats(t *testing.T) {
|
||||
const repo = "stats-test-repo1"
|
||||
|
||||
testRepo := TestRepo{
|
||||
Name: repo,
|
||||
Name: repo,
|
||||
Target: "folder",
|
||||
Copies: map[string]string{
|
||||
"testdata/all-panels.json": "dashboard1.json",
|
||||
"testdata/text-options.json": "folder/dashboard2.json",
|
||||
},
|
||||
ExpectedDashboards: 2,
|
||||
ExpectedFolders: 1,
|
||||
ExpectedFolders: 2, // folder sync creates a folder for the repo + one nested folder
|
||||
}
|
||||
helper.CreateRepo(t, testRepo)
|
||||
|
||||
@@ -94,7 +95,7 @@ func TestIntegrationProvisioning_Stats(t *testing.T) {
|
||||
require.Equal(t, int64(2), count, "repo should manage 2 dashboards")
|
||||
} else if group == "folder.grafana.app" && resource == "folders" {
|
||||
count, _, _ := unstructured.NestedInt64(stat, "count")
|
||||
require.Equal(t, int64(1), count, "repo should manage 1 folder")
|
||||
require.Equal(t, int64(2), count, "repo should manage 2 folders (repo folder + nested folder)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -580,6 +580,12 @@ func CreateGrafDir(t *testing.T, opts GrafanaOpts) (string, string) {
|
||||
_, err = pathsSect.NewKey("permitted_provisioning_paths", opts.PermittedProvisioningPaths)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
if len(opts.ProvisioningAllowedTargets) > 0 {
|
||||
provisioningSect, err := getOrCreateSection("provisioning")
|
||||
require.NoError(t, err)
|
||||
_, err = provisioningSect.NewKey("allowed_targets", strings.Join(opts.ProvisioningAllowedTargets, "|"))
|
||||
require.NoError(t, err)
|
||||
}
|
||||
if opts.EnableSCIM {
|
||||
scimSection, err := getOrCreateSection("auth.scim")
|
||||
require.NoError(t, err)
|
||||
@@ -669,6 +675,7 @@ type GrafanaOpts struct {
|
||||
UnifiedStorageEnableSearch bool
|
||||
UnifiedStorageMaxPageSizeBytes int
|
||||
PermittedProvisioningPaths string
|
||||
ProvisioningAllowedTargets []string
|
||||
GrafanaComSSOAPIToken string
|
||||
LicensePath string
|
||||
EnableRecordingRules bool
|
||||
|
||||
Reference in New Issue
Block a user