1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-07-19 13:29:41 +02:00

feat(edgeconfigs): parse .env config files for interpolation BE-11673 (#514)

This commit is contained in:
andres-portainer 2025-03-15 10:09:22 -03:00 committed by GitHub
parent 66bcf9223a
commit 2c05496962
2 changed files with 96 additions and 70 deletions

View file

@ -15,15 +15,19 @@ type MultiFilterArgs []struct {
} }
// MultiFilterDirForPerDevConfigs filers the given dirEntries with multiple filter args, returns the merged entries for the given device // MultiFilterDirForPerDevConfigs filers the given dirEntries with multiple filter args, returns the merged entries for the given device
func MultiFilterDirForPerDevConfigs(dirEntries []DirEntry, configPath string, multiFilterArgs MultiFilterArgs) []DirEntry { func MultiFilterDirForPerDevConfigs(dirEntries []DirEntry, configPath string, multiFilterArgs MultiFilterArgs) ([]DirEntry, []string) {
var filteredDirEntries []DirEntry var filteredDirEntries []DirEntry
var envFiles []string
for _, multiFilterArg := range multiFilterArgs { for _, multiFilterArg := range multiFilterArgs {
tmp := FilterDirForPerDevConfigs(dirEntries, multiFilterArg.FilterKey, configPath, multiFilterArg.FilterType) tmp, efs := FilterDirForPerDevConfigs(dirEntries, multiFilterArg.FilterKey, configPath, multiFilterArg.FilterType)
filteredDirEntries = append(filteredDirEntries, tmp...) filteredDirEntries = append(filteredDirEntries, tmp...)
envFiles = append(envFiles, efs...)
} }
return deduplicate(filteredDirEntries) return deduplicate(filteredDirEntries), envFiles
} }
func deduplicate(dirEntries []DirEntry) []DirEntry { func deduplicate(dirEntries []DirEntry) []DirEntry {
@ -32,8 +36,7 @@ func deduplicate(dirEntries []DirEntry) []DirEntry {
marks := make(map[string]struct{}) marks := make(map[string]struct{})
for _, dirEntry := range dirEntries { for _, dirEntry := range dirEntries {
_, ok := marks[dirEntry.Name] if _, ok := marks[dirEntry.Name]; !ok {
if !ok {
marks[dirEntry.Name] = struct{}{} marks[dirEntry.Name] = struct{}{}
deduplicatedDirEntries = append(deduplicatedDirEntries, dirEntry) deduplicatedDirEntries = append(deduplicatedDirEntries, dirEntry)
} }
@ -50,20 +53,25 @@ func deduplicate(dirEntries []DirEntry) []DirEntry {
// 3. For filterType dir: // 3. For filterType dir:
// dir entry: A/B/C/<deviceName> // dir entry: A/B/C/<deviceName>
// all entries: A/B/C/<deviceName>/* // all entries: A/B/C/<deviceName>/*
func FilterDirForPerDevConfigs(dirEntries []DirEntry, deviceName, configPath string, filterType portainer.PerDevConfigsFilterType) []DirEntry { func FilterDirForPerDevConfigs(dirEntries []DirEntry, deviceName, configPath string, filterType portainer.PerDevConfigsFilterType) ([]DirEntry, []string) {
var filteredDirEntries []DirEntry var filteredDirEntries []DirEntry
var envFiles []string
for _, dirEntry := range dirEntries { for _, dirEntry := range dirEntries {
if shouldIncludeEntry(dirEntry, deviceName, configPath, filterType) { if shouldIncludeEntry(dirEntry, deviceName, configPath, filterType) {
filteredDirEntries = append(filteredDirEntries, dirEntry) filteredDirEntries = append(filteredDirEntries, dirEntry)
if shouldParseEnvVars(dirEntry, deviceName, configPath, filterType) {
envFiles = append(envFiles, dirEntry.Name)
}
} }
} }
return filteredDirEntries return filteredDirEntries, envFiles
} }
func shouldIncludeEntry(dirEntry DirEntry, deviceName, configPath string, filterType portainer.PerDevConfigsFilterType) bool { func shouldIncludeEntry(dirEntry DirEntry, deviceName, configPath string, filterType portainer.PerDevConfigsFilterType) bool {
// Include all entries outside of dir A // Include all entries outside of dir A
if !isInConfigDir(dirEntry, configPath) { if !isInConfigDir(dirEntry, configPath) {
return true return true
@ -120,6 +128,15 @@ func shouldIncludeDir(dirEntry DirEntry, deviceName, configPath string) bool {
return strings.HasPrefix(dirEntry.Name, filterPrefix) return strings.HasPrefix(dirEntry.Name, filterPrefix)
} }
func shouldParseEnvVars(dirEntry DirEntry, deviceName, configPath string, filterType portainer.PerDevConfigsFilterType) bool {
if !dirEntry.IsFile {
return false
}
return isInConfigDir(dirEntry, configPath) &&
filepath.Base(dirEntry.Name) == deviceName+".env"
}
func appendTailSeparator(path string) string { func appendTailSeparator(path string) string {
return fmt.Sprintf("%s%c", path, os.PathSeparator) return fmt.Sprintf("%s%c", path, os.PathSeparator)
} }

View file

@ -4,14 +4,17 @@ import (
"testing" "testing"
portainer "github.com/portainer/portainer/api" portainer "github.com/portainer/portainer/api"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestMultiFilterDirForPerDevConfigs(t *testing.T) { func TestMultiFilterDirForPerDevConfigs(t *testing.T) {
type args struct { f := func(dirEntries []DirEntry, configPath string, multiFilterArgs MultiFilterArgs, wantDirEntries []DirEntry) {
dirEntries []DirEntry t.Helper()
configPath string
multiFilterArgs MultiFilterArgs dirEntries, _ = MultiFilterDirForPerDevConfigs(dirEntries, configPath, multiFilterArgs)
require.Equal(t, wantDirEntries, dirEntries)
} }
baseDirEntries := []DirEntry{ baseDirEntries := []DirEntry{
@ -26,69 +29,75 @@ func TestMultiFilterDirForPerDevConfigs(t *testing.T) {
{"configs/folder2/config2", "", true, 420}, {"configs/folder2/config2", "", true, 420},
} }
tests := []struct { // Filter file1
name string f(
args args baseDirEntries,
want []DirEntry "configs",
}{ MultiFilterArgs{{"file1", portainer.PerDevConfigsTypeFile}},
{ []DirEntry{baseDirEntries[0], baseDirEntries[1], baseDirEntries[2], baseDirEntries[3]},
name: "filter file1", )
args: args{
baseDirEntries, // Filter folder1
"configs", f(
MultiFilterArgs{{"file1", portainer.PerDevConfigsTypeFile}}, baseDirEntries,
}, "configs",
want: []DirEntry{baseDirEntries[0], baseDirEntries[1], baseDirEntries[2], baseDirEntries[3]}, MultiFilterArgs{{"folder1", portainer.PerDevConfigsTypeDir}},
[]DirEntry{baseDirEntries[0], baseDirEntries[1], baseDirEntries[2], baseDirEntries[5], baseDirEntries[6]},
)
// Filter file1 and folder1
f(
baseDirEntries,
"configs",
MultiFilterArgs{{"folder1", portainer.PerDevConfigsTypeDir}},
[]DirEntry{baseDirEntries[0], baseDirEntries[1], baseDirEntries[2], baseDirEntries[5], baseDirEntries[6]},
)
// Filter file1 and file2
f(
baseDirEntries,
"configs",
MultiFilterArgs{
{"file1", portainer.PerDevConfigsTypeFile},
{"file2", portainer.PerDevConfigsTypeFile},
}, },
{ []DirEntry{baseDirEntries[0], baseDirEntries[1], baseDirEntries[2], baseDirEntries[3], baseDirEntries[4]},
name: "filter folder1", )
args: args{
baseDirEntries, // Filter folder1 and folder2
"configs", f(
MultiFilterArgs{{"folder1", portainer.PerDevConfigsTypeDir}}, baseDirEntries,
}, "configs",
want: []DirEntry{baseDirEntries[0], baseDirEntries[1], baseDirEntries[2], baseDirEntries[5], baseDirEntries[6]}, MultiFilterArgs{
}, {"folder1", portainer.PerDevConfigsTypeDir},
{ {"folder2", portainer.PerDevConfigsTypeDir},
name: "filter file1 and folder1",
args: args{
baseDirEntries,
"configs",
MultiFilterArgs{{"folder1", portainer.PerDevConfigsTypeDir}},
},
want: []DirEntry{baseDirEntries[0], baseDirEntries[1], baseDirEntries[2], baseDirEntries[5], baseDirEntries[6]},
},
{
name: "filter file1 and file2",
args: args{
baseDirEntries,
"configs",
MultiFilterArgs{
{"file1", portainer.PerDevConfigsTypeFile},
{"file2", portainer.PerDevConfigsTypeFile},
},
},
want: []DirEntry{baseDirEntries[0], baseDirEntries[1], baseDirEntries[2], baseDirEntries[3], baseDirEntries[4]},
},
{
name: "filter folder1 and folder2",
args: args{
baseDirEntries,
"configs",
MultiFilterArgs{
{"folder1", portainer.PerDevConfigsTypeDir},
{"folder2", portainer.PerDevConfigsTypeDir},
},
},
want: []DirEntry{baseDirEntries[0], baseDirEntries[1], baseDirEntries[2], baseDirEntries[5], baseDirEntries[6], baseDirEntries[7], baseDirEntries[8]},
}, },
[]DirEntry{baseDirEntries[0], baseDirEntries[1], baseDirEntries[2], baseDirEntries[5], baseDirEntries[6], baseDirEntries[7], baseDirEntries[8]},
)
}
func TestMultiFilterDirForPerDevConfigsEnvFiles(t *testing.T) {
f := func(dirEntries []DirEntry, configPath string, multiFilterArgs MultiFilterArgs, wantEnvFiles []string) {
t.Helper()
_, envFiles := MultiFilterDirForPerDevConfigs(dirEntries, configPath, multiFilterArgs)
require.Equal(t, wantEnvFiles, envFiles)
} }
for _, tt := range tests { baseDirEntries := []DirEntry{
t.Run(tt.name, func(t *testing.T) { {".env", "", true, 420},
assert.Equalf(t, tt.want, MultiFilterDirForPerDevConfigs(tt.args.dirEntries, tt.args.configPath, tt.args.multiFilterArgs), "MultiFilterDirForPerDevConfigs(%v, %v, %v)", tt.args.dirEntries, tt.args.configPath, tt.args.multiFilterArgs) {"docker-compose.yaml", "", true, 420},
}) {"configs", "", false, 420},
{"configs/edge-id/edge-id.env", "", true, 420},
} }
f(
baseDirEntries,
"configs",
MultiFilterArgs{{"edge-id", portainer.PerDevConfigsTypeDir}},
[]string{"configs/edge-id/edge-id.env"},
)
} }
func TestIsInConfigDir(t *testing.T) { func TestIsInConfigDir(t *testing.T) {