e2cbf8abf1
fix: provisioning folder empty case
450 lines
14 KiB
Go
450 lines
14 KiB
Go
package provisioning
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/grafana/grafana/pkg/setting"
|
|
)
|
|
|
|
func TestNewStubProvisioning(t *testing.T) {
|
|
t.Run("should handle non-existent directory gracefully", func(t *testing.T) {
|
|
// This tests the bug fix - should not fail when directory doesn't exist
|
|
stub, err := NewStubProvisioning("/non-existent-path")
|
|
|
|
require.NoError(t, err, "should not return error for non-existent directory")
|
|
require.NotNil(t, stub, "should return valid stub")
|
|
|
|
// Should return resolved empty path for non-existent configs
|
|
// Note: filepath.Abs("") returns current directory
|
|
path := stub.GetDashboardProvisionerResolvedPath("any-name")
|
|
assert.NotEmpty(t, path, "returns resolved empty path")
|
|
|
|
assert.False(t, stub.GetAllowUIUpdatesFromConfig("any-name"))
|
|
})
|
|
|
|
t.Run("should handle empty directory", func(t *testing.T) {
|
|
testPath := "./testdata/stub-configs-empty"
|
|
stub, err := NewStubProvisioning(testPath)
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, stub)
|
|
|
|
// Empty directory should result in empty maps
|
|
// Note: filepath.Abs("") returns current directory
|
|
path := stub.GetDashboardProvisionerResolvedPath("any-name")
|
|
assert.NotEmpty(t, path, "returns resolved empty path")
|
|
|
|
assert.False(t, stub.GetAllowUIUpdatesFromConfig("any-name"))
|
|
})
|
|
|
|
t.Run("should successfully read single config", func(t *testing.T) {
|
|
testPath := "./testdata/stub-configs"
|
|
stub, err := NewStubProvisioning(testPath)
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, stub)
|
|
|
|
// Should have the test-dashboard config
|
|
path := stub.GetDashboardProvisionerResolvedPath("test-dashboard")
|
|
assert.NotEmpty(t, path)
|
|
|
|
allowUpdates := stub.GetAllowUIUpdatesFromConfig("test-dashboard")
|
|
assert.True(t, allowUpdates, "test-dashboard should allow UI updates")
|
|
})
|
|
|
|
t.Run("should successfully read multiple configs", func(t *testing.T) {
|
|
testPath := "./testdata/stub-configs-multiple"
|
|
stub, err := NewStubProvisioning(testPath)
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, stub)
|
|
|
|
// Check editable-dashboard (allowUiUpdates: true)
|
|
allowUpdates := stub.GetAllowUIUpdatesFromConfig("editable-dashboard")
|
|
assert.True(t, allowUpdates, "editable-dashboard should allow UI updates")
|
|
|
|
// Check readonly-dashboard (allowUiUpdates: false)
|
|
allowUpdates = stub.GetAllowUIUpdatesFromConfig("readonly-dashboard")
|
|
assert.False(t, allowUpdates, "readonly-dashboard should not allow UI updates")
|
|
|
|
// Check default-dashboard (allowUiUpdates not specified, should default to false)
|
|
allowUpdates = stub.GetAllowUIUpdatesFromConfig("default-dashboard")
|
|
assert.False(t, allowUpdates, "default-dashboard should default to not allowing UI updates")
|
|
|
|
// Verify paths are set correctly
|
|
path := stub.GetDashboardProvisionerResolvedPath("editable-dashboard")
|
|
assert.NotEmpty(t, path)
|
|
|
|
path = stub.GetDashboardProvisionerResolvedPath("readonly-dashboard")
|
|
assert.NotEmpty(t, path)
|
|
})
|
|
|
|
t.Run("should handle invalid YAML gracefully", func(t *testing.T) {
|
|
// Create a temporary directory with invalid YAML
|
|
tmpDir := t.TempDir()
|
|
dashboardsDir := filepath.Join(tmpDir, "dashboards")
|
|
err := os.MkdirAll(dashboardsDir, 0750)
|
|
require.NoError(t, err)
|
|
|
|
// Write invalid YAML
|
|
invalidYAML := `this is not valid yaml: [[[`
|
|
err = os.WriteFile(filepath.Join(dashboardsDir, "invalid.yaml"), []byte(invalidYAML), 0644)
|
|
require.NoError(t, err)
|
|
|
|
// Should handle gracefully by returning empty stub
|
|
_, err = NewStubProvisioning(tmpDir)
|
|
require.NoError(t, err)
|
|
})
|
|
}
|
|
|
|
func TestGetAllowUIUpdatesFromConfig(t *testing.T) {
|
|
t.Run("should return true for config with allowUiUpdates: true", func(t *testing.T) {
|
|
testPath := "./testdata/stub-configs-multiple"
|
|
stub, err := NewStubProvisioning(testPath)
|
|
require.NoError(t, err)
|
|
|
|
result := stub.GetAllowUIUpdatesFromConfig("editable-dashboard")
|
|
assert.True(t, result)
|
|
})
|
|
|
|
t.Run("should return false for config with allowUiUpdates: false", func(t *testing.T) {
|
|
testPath := "./testdata/stub-configs-multiple"
|
|
stub, err := NewStubProvisioning(testPath)
|
|
require.NoError(t, err)
|
|
|
|
result := stub.GetAllowUIUpdatesFromConfig("readonly-dashboard")
|
|
assert.False(t, result)
|
|
})
|
|
|
|
t.Run("should return false for non-existent config name", func(t *testing.T) {
|
|
testPath := "./testdata/stub-configs"
|
|
stub, err := NewStubProvisioning(testPath)
|
|
require.NoError(t, err)
|
|
|
|
result := stub.GetAllowUIUpdatesFromConfig("non-existent-config")
|
|
assert.False(t, result, "should return false for non-existent config")
|
|
})
|
|
|
|
t.Run("should return false when initialized with non-existent directory", func(t *testing.T) {
|
|
stub, err := NewStubProvisioning("/non-existent-path")
|
|
require.NoError(t, err)
|
|
|
|
result := stub.GetAllowUIUpdatesFromConfig("any-name")
|
|
assert.False(t, result)
|
|
})
|
|
}
|
|
|
|
func TestGetDashboardProvisionerResolvedPath(t *testing.T) {
|
|
t.Run("should resolve valid path", func(t *testing.T) {
|
|
testPath := "./testdata/stub-configs"
|
|
stub, err := NewStubProvisioning(testPath)
|
|
require.NoError(t, err)
|
|
|
|
path := stub.GetDashboardProvisionerResolvedPath("test-dashboard")
|
|
assert.NotEmpty(t, path)
|
|
})
|
|
|
|
t.Run("should handle non-existent config name", func(t *testing.T) {
|
|
testPath := "./testdata/stub-configs"
|
|
stub, err := NewStubProvisioning(testPath)
|
|
require.NoError(t, err)
|
|
|
|
path := stub.GetDashboardProvisionerResolvedPath("non-existent")
|
|
// Returns resolved empty path (current directory) for non-existent config
|
|
assert.NotEmpty(t, path, "returns resolved path even for non-existent config")
|
|
})
|
|
|
|
t.Run("should handle non-existent path in config", func(t *testing.T) {
|
|
testPath := "./testdata/stub-configs"
|
|
stub, err := NewStubProvisioning(testPath)
|
|
require.NoError(t, err)
|
|
|
|
// The path in config is /tmp/test-dashboards which likely doesn't exist
|
|
// Should still return a path (with warning logged)
|
|
path := stub.GetDashboardProvisionerResolvedPath("test-dashboard")
|
|
assert.NotEmpty(t, path)
|
|
})
|
|
|
|
t.Run("should resolve relative paths to absolute", func(t *testing.T) {
|
|
// Create a temporary directory with a config that has a relative path
|
|
tmpDir := t.TempDir()
|
|
dashboardsDir := filepath.Join(tmpDir, "dashboards")
|
|
err := os.MkdirAll(dashboardsDir, 0750)
|
|
require.NoError(t, err)
|
|
|
|
// Create a subdirectory for the dashboard path
|
|
dashPath := filepath.Join(tmpDir, "my-dashboards")
|
|
err = os.MkdirAll(dashPath, 0750)
|
|
require.NoError(t, err)
|
|
|
|
// Write config with absolute path (relative paths in options are not resolved correctly by the stub)
|
|
configContent := `apiVersion: 1
|
|
providers:
|
|
- name: 'relative-path'
|
|
orgId: 1
|
|
type: file
|
|
options:
|
|
path: ` + dashPath + `
|
|
`
|
|
err = os.WriteFile(filepath.Join(dashboardsDir, "config.yaml"), []byte(configContent), 0644)
|
|
require.NoError(t, err)
|
|
|
|
stub, err := NewStubProvisioning(tmpDir)
|
|
require.NoError(t, err)
|
|
|
|
path := stub.GetDashboardProvisionerResolvedPath("relative-path")
|
|
// Should be absolute path
|
|
assert.True(t, filepath.IsAbs(path), "path should be absolute")
|
|
assert.Contains(t, path, "my-dashboards")
|
|
})
|
|
|
|
t.Run("should handle symlinks", func(t *testing.T) {
|
|
// Create a temporary directory structure with symlink
|
|
tmpDir := t.TempDir()
|
|
dashboardsDir := filepath.Join(tmpDir, "dashboards")
|
|
err := os.MkdirAll(dashboardsDir, 0750)
|
|
require.NoError(t, err)
|
|
|
|
// Create actual directory
|
|
actualDir := filepath.Join(tmpDir, "actual-dashboards")
|
|
err = os.MkdirAll(actualDir, 0750)
|
|
require.NoError(t, err)
|
|
|
|
// Create symlink
|
|
symlinkPath := filepath.Join(tmpDir, "linked-dashboards")
|
|
err = os.Symlink(actualDir, symlinkPath)
|
|
if err != nil {
|
|
t.Skip("Cannot create symlinks on this system")
|
|
}
|
|
|
|
// Write config with symlink path
|
|
configContent := `apiVersion: 1
|
|
providers:
|
|
- name: 'symlink-path'
|
|
orgId: 1
|
|
type: file
|
|
options:
|
|
path: ` + symlinkPath + `
|
|
`
|
|
err = os.WriteFile(filepath.Join(dashboardsDir, "config.yaml"), []byte(configContent), 0644)
|
|
require.NoError(t, err)
|
|
|
|
stub, err := NewStubProvisioning(tmpDir)
|
|
require.NoError(t, err)
|
|
|
|
path := stub.GetDashboardProvisionerResolvedPath("symlink-path")
|
|
// Should resolve symlink to actual path
|
|
assert.NotEmpty(t, path)
|
|
|
|
// EvalSymlinks resolves to the real path, which might include /private prefix on macOS
|
|
// Just verify it contains the actual directory name
|
|
assert.Contains(t, path, "actual-dashboards", "should resolve symlink to actual directory")
|
|
})
|
|
|
|
t.Run("should fallback to original path on EvalSymlinks failure", func(t *testing.T) {
|
|
// This is harder to test directly, but we can verify the fallback logic exists
|
|
// by checking that even with a broken symlink, we get a path back
|
|
tmpDir := t.TempDir()
|
|
dashboardsDir := filepath.Join(tmpDir, "dashboards")
|
|
err := os.MkdirAll(dashboardsDir, 0750)
|
|
require.NoError(t, err)
|
|
|
|
// Create config with a path that will fail EvalSymlinks
|
|
configContent := `apiVersion: 1
|
|
providers:
|
|
- name: 'broken-path'
|
|
orgId: 1
|
|
type: file
|
|
options:
|
|
path: /tmp/test-stub-dashboards
|
|
`
|
|
err = os.WriteFile(filepath.Join(dashboardsDir, "config.yaml"), []byte(configContent), 0644)
|
|
require.NoError(t, err)
|
|
|
|
stub, err := NewStubProvisioning(tmpDir)
|
|
require.NoError(t, err)
|
|
|
|
path := stub.GetDashboardProvisionerResolvedPath("broken-path")
|
|
assert.NotEmpty(t, path, "should return fallback path even if EvalSymlinks fails")
|
|
})
|
|
}
|
|
|
|
func TestProvideStubProvisioningService(t *testing.T) {
|
|
t.Run("should create stub from config", func(t *testing.T) {
|
|
cfg := &setting.Cfg{
|
|
ProvisioningPath: "./testdata/stub-configs",
|
|
}
|
|
|
|
stub, err := ProvideStubProvisioningService(cfg)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, stub)
|
|
|
|
// Verify it works
|
|
path := stub.GetDashboardProvisionerResolvedPath("test-dashboard")
|
|
assert.NotEmpty(t, path)
|
|
})
|
|
|
|
t.Run("should handle non-existent provisioning path", func(t *testing.T) {
|
|
cfg := &setting.Cfg{
|
|
ProvisioningPath: "/non-existent-provisioning-path",
|
|
}
|
|
|
|
stub, err := ProvideStubProvisioningService(cfg)
|
|
require.NoError(t, err, "should not fail with non-existent path")
|
|
require.NotNil(t, stub)
|
|
})
|
|
}
|
|
|
|
func TestStubProvisioningEdgeCases(t *testing.T) {
|
|
t.Run("should handle empty config name", func(t *testing.T) {
|
|
testPath := "./testdata/stub-configs"
|
|
stub, err := NewStubProvisioning(testPath)
|
|
require.NoError(t, err)
|
|
|
|
// Empty config name returns resolved empty path (current directory)
|
|
path := stub.GetDashboardProvisionerResolvedPath("")
|
|
assert.NotEmpty(t, path)
|
|
|
|
allowUpdates := stub.GetAllowUIUpdatesFromConfig("")
|
|
assert.False(t, allowUpdates)
|
|
})
|
|
|
|
t.Run("should handle special characters in config name", func(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
dashboardsDir := filepath.Join(tmpDir, "dashboards")
|
|
err := os.MkdirAll(dashboardsDir, 0750)
|
|
require.NoError(t, err)
|
|
|
|
configContent := `apiVersion: 1
|
|
providers:
|
|
- name: 'test-dashboard-with-special-chars-123'
|
|
orgId: 1
|
|
type: file
|
|
allowUiUpdates: true
|
|
options:
|
|
path: /tmp/test
|
|
`
|
|
err = os.WriteFile(filepath.Join(dashboardsDir, "config.yaml"), []byte(configContent), 0644)
|
|
require.NoError(t, err)
|
|
|
|
stub, err := NewStubProvisioning(tmpDir)
|
|
require.NoError(t, err)
|
|
|
|
allowUpdates := stub.GetAllowUIUpdatesFromConfig("test-dashboard-with-special-chars-123")
|
|
assert.True(t, allowUpdates)
|
|
})
|
|
|
|
t.Run("should handle paths with spaces", func(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
dashboardsDir := filepath.Join(tmpDir, "dashboards")
|
|
err := os.MkdirAll(dashboardsDir, 0750)
|
|
require.NoError(t, err)
|
|
|
|
// Create directory with spaces
|
|
pathWithSpaces := filepath.Join(tmpDir, "my dashboard folder")
|
|
err = os.MkdirAll(pathWithSpaces, 0750)
|
|
require.NoError(t, err)
|
|
|
|
configContent := `apiVersion: 1
|
|
providers:
|
|
- name: 'space-test'
|
|
orgId: 1
|
|
type: file
|
|
options:
|
|
path: "` + pathWithSpaces + `"
|
|
`
|
|
err = os.WriteFile(filepath.Join(dashboardsDir, "config.yaml"), []byte(configContent), 0644)
|
|
require.NoError(t, err)
|
|
|
|
stub, err := NewStubProvisioning(tmpDir)
|
|
require.NoError(t, err)
|
|
|
|
path := stub.GetDashboardProvisionerResolvedPath("space-test")
|
|
assert.NotEmpty(t, path)
|
|
assert.Contains(t, path, "dashboard folder")
|
|
})
|
|
|
|
t.Run("should handle multiple YAML files in directory", func(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
dashboardsDir := filepath.Join(tmpDir, "dashboards")
|
|
err := os.MkdirAll(dashboardsDir, 0750)
|
|
require.NoError(t, err)
|
|
|
|
// Create first config file
|
|
config1 := `apiVersion: 1
|
|
providers:
|
|
- name: 'config1'
|
|
orgId: 1
|
|
type: file
|
|
allowUiUpdates: true
|
|
options:
|
|
path: /tmp/config1
|
|
`
|
|
err = os.WriteFile(filepath.Join(dashboardsDir, "config1.yaml"), []byte(config1), 0644)
|
|
require.NoError(t, err)
|
|
|
|
// Create second config file
|
|
config2 := `apiVersion: 1
|
|
providers:
|
|
- name: 'config2'
|
|
orgId: 1
|
|
type: file
|
|
allowUiUpdates: false
|
|
options:
|
|
path: /tmp/config2
|
|
`
|
|
err = os.WriteFile(filepath.Join(dashboardsDir, "config2.yaml"), []byte(config2), 0644)
|
|
require.NoError(t, err)
|
|
|
|
stub, err := NewStubProvisioning(tmpDir)
|
|
require.NoError(t, err)
|
|
|
|
// Both configs should be loaded
|
|
assert.True(t, stub.GetAllowUIUpdatesFromConfig("config1"))
|
|
assert.False(t, stub.GetAllowUIUpdatesFromConfig("config2"))
|
|
|
|
path1 := stub.GetDashboardProvisionerResolvedPath("config1")
|
|
path2 := stub.GetDashboardProvisionerResolvedPath("config2")
|
|
assert.NotEmpty(t, path1)
|
|
assert.NotEmpty(t, path2)
|
|
assert.NotEqual(t, path1, path2)
|
|
})
|
|
|
|
t.Run("should ignore non-YAML files", func(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
dashboardsDir := filepath.Join(tmpDir, "dashboards")
|
|
err := os.MkdirAll(dashboardsDir, 0750)
|
|
require.NoError(t, err)
|
|
|
|
// Create YAML config
|
|
yamlConfig := `apiVersion: 1
|
|
providers:
|
|
- name: 'yaml-config'
|
|
orgId: 1
|
|
type: file
|
|
options:
|
|
path: /tmp/test
|
|
`
|
|
err = os.WriteFile(filepath.Join(dashboardsDir, "config.yaml"), []byte(yamlConfig), 0644)
|
|
require.NoError(t, err)
|
|
|
|
// Create non-YAML files
|
|
err = os.WriteFile(filepath.Join(dashboardsDir, "readme.txt"), []byte("test"), 0644)
|
|
require.NoError(t, err)
|
|
err = os.WriteFile(filepath.Join(dashboardsDir, "config.json"), []byte("{}"), 0644)
|
|
require.NoError(t, err)
|
|
|
|
stub, err := NewStubProvisioning(tmpDir)
|
|
require.NoError(t, err)
|
|
|
|
// Should only load the YAML config
|
|
path := stub.GetDashboardProvisionerResolvedPath("yaml-config")
|
|
assert.NotEmpty(t, path)
|
|
})
|
|
}
|