Files
grafana/pkg/tests/apis/provisioning/job_validation_test.go
T
Roberto Jiménez Sánchez a0751b6e71 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
2025-12-19 11:44:15 +00:00

183 lines
5.5 KiB
Go

package provisioning
import (
"context"
"fmt"
"net/http"
"testing"
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/util/testutil"
)
func TestIntegrationProvisioning_JobValidation(t *testing.T) {
testutil.SkipIntegrationTestInShortMode(t)
helper := runGrafana(t)
ctx := context.Background()
// Create a test repository first
const repo = "job-validation-test-repo"
testRepo := TestRepo{
Name: repo,
Target: "folder",
Copies: map[string]string{},
ExpectedDashboards: 0,
ExpectedFolders: 1, // folder sync creates a folder
}
helper.CreateRepo(t, testRepo)
tests := []struct {
name string
jobSpec map[string]interface{}
expectedErr string
}{
{
name: "job without action",
jobSpec: map[string]interface{}{
"repository": repo,
},
expectedErr: "spec.action: Required value: action must be specified",
},
{
name: "job with invalid action",
jobSpec: map[string]interface{}{
"action": "invalid-action",
"repository": repo,
},
expectedErr: "spec.action: Invalid value: \"invalid-action\": invalid action",
},
{
name: "pull job without pull options",
jobSpec: map[string]interface{}{
"action": string(provisioning.JobActionPull),
"repository": repo,
},
expectedErr: "spec.pull: Required value: pull options required for pull action",
},
{
name: "push job without push options",
jobSpec: map[string]interface{}{
"action": string(provisioning.JobActionPush),
"repository": repo,
},
expectedErr: "spec.push: Required value: push options required for push action",
},
{
name: "push job with invalid branch name",
jobSpec: map[string]interface{}{
"action": string(provisioning.JobActionPush),
"repository": repo,
"push": map[string]interface{}{
"branch": "feature..branch", // Invalid: consecutive dots
"message": "Test commit",
},
},
expectedErr: "spec.push.branch: Invalid value: \"feature..branch\": invalid git branch name",
},
{
name: "push job with path traversal",
jobSpec: map[string]interface{}{
"action": string(provisioning.JobActionPush),
"repository": repo,
"push": map[string]interface{}{
"path": "../../etc/passwd", // Invalid: path traversal
"message": "Test commit",
},
},
expectedErr: "spec.push.path: Invalid value: \"../../etc/passwd\"",
},
{
name: "delete job without paths or resources",
jobSpec: map[string]interface{}{
"action": string(provisioning.JobActionDelete),
"repository": repo,
"delete": map[string]interface{}{},
},
expectedErr: "spec.delete: Required value: at least one path or resource must be specified",
},
{
name: "delete job with invalid path",
jobSpec: map[string]interface{}{
"action": string(provisioning.JobActionDelete),
"repository": repo,
"delete": map[string]interface{}{
"paths": []string{"../invalid/path"},
},
},
expectedErr: "spec.delete.paths[0]: Invalid value: \"../invalid/path\"",
},
{
name: "move job without target path",
jobSpec: map[string]interface{}{
"action": string(provisioning.JobActionMove),
"repository": repo,
"move": map[string]interface{}{
"paths": []string{"dashboard.json"},
},
},
expectedErr: "spec.move.targetPath: Required value: target path is required",
},
{
name: "move job without paths or resources",
jobSpec: map[string]interface{}{
"action": string(provisioning.JobActionMove),
"repository": repo,
"move": map[string]interface{}{
"targetPath": "new-location/",
},
},
expectedErr: "spec.move: Required value: at least one path or resource must be specified",
},
{
name: "move job with invalid target path",
jobSpec: map[string]interface{}{
"action": string(provisioning.JobActionMove),
"repository": repo,
"move": map[string]interface{}{
"paths": []string{"dashboard.json"},
"targetPath": "../../../etc/", // Invalid: path traversal
},
},
expectedErr: "spec.move.targetPath: Invalid value: \"../../../etc/\"",
},
{
name: "migrate job without migrate options",
jobSpec: map[string]interface{}{
"action": string(provisioning.JobActionMigrate),
"repository": repo,
},
expectedErr: "spec.migrate: Required value: migrate options required for migrate action",
},
}
for i, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Create the job object directly
jobObj := &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "provisioning.grafana.app/v0alpha1",
"kind": "Job",
"metadata": map[string]interface{}{
"name": fmt.Sprintf("test-job-validation-%d", i),
"namespace": "default",
},
"spec": tt.jobSpec,
},
}
// Try to create the job - should fail with validation error
_, err := helper.Jobs.Resource.Create(ctx, jobObj, metav1.CreateOptions{})
require.Error(t, err, "expected validation error for invalid job spec")
// Verify it's a validation error with correct status code
statusError := helper.RequireApiErrorStatus(err, metav1.StatusReasonInvalid, http.StatusUnprocessableEntity)
require.Contains(t, statusError.Message, tt.expectedErr, "error message should contain expected validation message")
})
}
}