142 lines
6.4 KiB
Go
142 lines
6.4 KiB
Go
package provisioning
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
|
|
provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
|
|
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
|
)
|
|
|
|
func TestIntegrationProvisioning_PullJobOwnershipProtection(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("skipping integration test")
|
|
}
|
|
|
|
helper := runGrafana(t)
|
|
ctx := context.Background()
|
|
|
|
// Create two repositories with folder targets and separate paths to avoid file conflicts
|
|
const repo1 = "pulljob-repo-1"
|
|
const repo2 = "pulljob-repo-2"
|
|
|
|
// Create first repository targeting "folder" with its own subdirectory
|
|
helper.CreateRepo(t, TestRepo{
|
|
Name: repo1,
|
|
Path: path.Join(helper.ProvisioningPath, "repo1"),
|
|
Target: "folder",
|
|
Copies: map[string]string{
|
|
"testdata/all-panels.json": "dashboard1.json",
|
|
},
|
|
ExpectedDashboards: 1,
|
|
ExpectedFolders: 1,
|
|
})
|
|
|
|
// Create second repository targeting "folder" with its own subdirectory
|
|
helper.CreateRepo(t, TestRepo{
|
|
Name: repo2,
|
|
Path: path.Join(helper.ProvisioningPath, "repo2"),
|
|
Target: "folder",
|
|
Copies: map[string]string{
|
|
"testdata/timeline-demo.json": "dashboard2.json",
|
|
},
|
|
ExpectedDashboards: 2, // Total across both repos
|
|
ExpectedFolders: 2, // Total across both repos
|
|
})
|
|
|
|
// Test: Pull job should fail when trying to manage resources owned by another repository
|
|
t.Run("pull job should fail when trying to manage resources owned by another repository", func(t *testing.T) {
|
|
// Step 1: Try to add a file with the same UID as repo1's dashboard to repo2's directory
|
|
// This simulates a scenario where repo2 tries to manage a resource that repo1 already owns
|
|
const allPanelsUID = "n1jR8vnnz" // UID from all-panels.json (owned by repo1)
|
|
|
|
// Copy the same file (same UID) to repo2's directory to create ownership conflict
|
|
conflictingFilePath := "repo2/conflicting-dashboard.json"
|
|
helper.CopyToProvisioningPath(t, "testdata/all-panels.json", conflictingFilePath)
|
|
printFileTree(t, helper.ProvisioningPath)
|
|
|
|
// Step 2: Try to pull repo2 - should fail due to ownership conflict
|
|
job := helper.TriggerJobAndWaitForComplete(t, repo2, provisioning.JobSpec{
|
|
Action: provisioning.JobActionPull,
|
|
Pull: &provisioning.SyncJobOptions{},
|
|
})
|
|
|
|
// Step 3: Verify the job failed with ownership conflict error
|
|
jobObj := &provisioning.Job{}
|
|
err := runtime.DefaultUnstructuredConverter.FromUnstructured(job.Object, jobObj)
|
|
require.NoError(t, err)
|
|
|
|
// The job completes with "warning" state instead of "error" state when it doesn't have too many errors
|
|
t.Logf("Job state: %s", jobObj.Status.State)
|
|
t.Logf("Job errors: %v", jobObj.Status.Errors)
|
|
|
|
require.Equal(t, provisioning.JobStateWarning, jobObj.Status.State, "job should complete with warnings due to ownership conflicts")
|
|
require.NotEmpty(t, jobObj.Status.Errors, "should have error details")
|
|
|
|
// Check that error mentions ownership conflict
|
|
found := false
|
|
for _, errMsg := range jobObj.Status.Errors {
|
|
t.Logf("Error message: %s", errMsg)
|
|
if assert.Contains(t, errMsg, fmt.Sprintf("managed by repo '%s'", repo1)) &&
|
|
assert.Contains(t, errMsg, fmt.Sprintf("cannot be modified by repo '%s'", repo2)) {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
require.True(t, found, "should have ownership conflict error")
|
|
|
|
// Step 4: Verify original resource is still owned by repo1 and unchanged
|
|
originalDashboard, err := helper.DashboardsV1.Resource.Get(ctx, allPanelsUID, metav1.GetOptions{})
|
|
require.NoError(t, err, "original dashboard should still exist")
|
|
require.Equal(t, repo1, originalDashboard.GetAnnotations()[utils.AnnoKeyManagerIdentity], "ownership should remain with repo1")
|
|
|
|
// Clean up the conflicting file for subsequent tests
|
|
err = os.Remove(filepath.Join(helper.ProvisioningPath, conflictingFilePath))
|
|
require.NoError(t, err, "should clean up conflicting file")
|
|
})
|
|
|
|
// Test: Repositories should not delete resources owned by other repositories during pull
|
|
t.Run("repositories should not delete resources owned by other repositories during pull", func(t *testing.T) {
|
|
// Both repositories were created with their own resources (repo1 has all-panels.json, repo2 has timeline-demo.json)
|
|
// Verify that pulling one repository doesn't affect the other's resources
|
|
|
|
// Step 1: Verify both repositories have their own resources
|
|
const allPanelsUID = "n1jR8vnnz" // UID from all-panels.json (repo1)
|
|
const timelineUID = "mIJjFy8Kz" // UID from timeline-demo.json (repo2)
|
|
|
|
repo1Dashboard, err := helper.DashboardsV1.Resource.Get(ctx, allPanelsUID, metav1.GetOptions{})
|
|
require.NoError(t, err, "repo1's dashboard should exist")
|
|
require.Equal(t, repo1, repo1Dashboard.GetAnnotations()[utils.AnnoKeyManagerIdentity], "should be owned by repo1")
|
|
|
|
repo2Dashboard, err := helper.DashboardsV1.Resource.Get(ctx, timelineUID, metav1.GetOptions{})
|
|
require.NoError(t, err, "repo2's dashboard should exist")
|
|
require.Equal(t, repo2, repo2Dashboard.GetAnnotations()[utils.AnnoKeyManagerIdentity], "should be owned by repo2")
|
|
|
|
// Step 2: Pull repo1 (which doesn't manage repo2's resource) - should complete successfully
|
|
helper.SyncAndWait(t, repo1, nil)
|
|
|
|
// Step 3: Verify that repo2's resource is still intact after repo1's pull
|
|
persistentRepo2Dashboard, err := helper.DashboardsV1.Resource.Get(ctx, timelineUID, metav1.GetOptions{})
|
|
require.NoError(t, err, "repo2's dashboard should still exist after repo1 pull")
|
|
require.Equal(t, repo2, persistentRepo2Dashboard.GetAnnotations()[utils.AnnoKeyManagerIdentity], "ownership should remain with repo2")
|
|
require.Equal(t, repo2Dashboard.GetResourceVersion(), persistentRepo2Dashboard.GetResourceVersion(), "repo2's resource should not be modified by repo1 pull")
|
|
|
|
// Step 4: Pull repo2 and verify repo1's resource is still intact
|
|
helper.SyncAndWait(t, repo2, nil)
|
|
|
|
persistentRepo1Dashboard, err := helper.DashboardsV1.Resource.Get(ctx, allPanelsUID, metav1.GetOptions{})
|
|
require.NoError(t, err, "repo1's dashboard should still exist after repo2 pull")
|
|
require.Equal(t, repo1, persistentRepo1Dashboard.GetAnnotations()[utils.AnnoKeyManagerIdentity], "ownership should remain with repo1")
|
|
require.Equal(t, repo1Dashboard.GetResourceVersion(), persistentRepo1Dashboard.GetResourceVersion(), "repo1's resource should not be modified by repo2 pull")
|
|
})
|
|
}
|