mirror of
https://github.com/portainer/portainer.git
synced 2025-07-19 05:19:39 +02:00
feat(libstack): expose env vars with PORTAINER_ prefix [BE-11661] (#687)
This commit is contained in:
parent
9fdc535d6b
commit
1abdf42f99
3 changed files with 232 additions and 0 deletions
|
@ -87,6 +87,11 @@ export function EdgeScriptSettingsFieldset({
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
|
<TextTip color="orange" className="mb-2 icon-orange">
|
||||||
|
For security purposes, only environment variables prefixed with
|
||||||
|
'PORTAINER_' will be accessible.
|
||||||
|
</TextTip>
|
||||||
|
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<div className="col-sm-12">
|
<div className="col-sm-12">
|
||||||
<SwitchField
|
<SwitchField
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"maps"
|
"maps"
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"slices"
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -29,6 +30,8 @@ import (
|
||||||
|
|
||||||
const PortainerEdgeStackLabel = "io.portainer.edge_stack_id"
|
const PortainerEdgeStackLabel = "io.portainer.edge_stack_id"
|
||||||
|
|
||||||
|
const portainerEnvVarsPrefix = "PORTAINER_"
|
||||||
|
|
||||||
var mu sync.Mutex
|
var mu sync.Mutex
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -322,11 +325,19 @@ func createProject(ctx context.Context, configFilepaths []string, options libsta
|
||||||
envFiles = append(envFiles, options.EnvFilePath)
|
envFiles = append(envFiles, options.EnvFilePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var osPortainerEnvVars []string
|
||||||
|
for _, ev := range os.Environ() {
|
||||||
|
if strings.HasPrefix(ev, portainerEnvVarsPrefix) {
|
||||||
|
osPortainerEnvVars = append(osPortainerEnvVars, ev)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
projectOptions, err := cli.NewProjectOptions(configFilepaths,
|
projectOptions, err := cli.NewProjectOptions(configFilepaths,
|
||||||
cli.WithWorkingDirectory(workingDir),
|
cli.WithWorkingDirectory(workingDir),
|
||||||
cli.WithName(options.ProjectName),
|
cli.WithName(options.ProjectName),
|
||||||
cli.WithoutEnvironmentResolution,
|
cli.WithoutEnvironmentResolution,
|
||||||
cli.WithResolvedPaths(!slices.Contains(options.ConfigOptions, "--no-path-resolution")),
|
cli.WithResolvedPaths(!slices.Contains(options.ConfigOptions, "--no-path-resolution")),
|
||||||
|
cli.WithEnv(osPortainerEnvVars),
|
||||||
cli.WithEnv(options.Env),
|
cli.WithEnv(options.Env),
|
||||||
cli.WithEnvFiles(envFiles...),
|
cli.WithEnvFiles(envFiles...),
|
||||||
func(o *cli.ProjectOptions) error {
|
func(o *cli.ProjectOptions) error {
|
||||||
|
|
|
@ -719,6 +719,7 @@ func Test_createProject(t *testing.T) {
|
||||||
filesToCreate map[string]string
|
filesToCreate map[string]string
|
||||||
configFilepaths []string
|
configFilepaths []string
|
||||||
options libstack.Options
|
options libstack.Options
|
||||||
|
osEnv map[string]string
|
||||||
expectedProject *types.Project
|
expectedProject *types.Project
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
|
@ -1049,6 +1050,217 @@ func Test_createProject(t *testing.T) {
|
||||||
},
|
},
|
||||||
expectedProject: expectedSimpleComposeProject("/project-dir", nil),
|
expectedProject: expectedSimpleComposeProject("/project-dir", nil),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "OS Env Vars",
|
||||||
|
filesToCreate: map[string]string{
|
||||||
|
"docker-compose.yml": testSimpleComposeConfig,
|
||||||
|
},
|
||||||
|
configFilepaths: []string{dir + "/docker-compose.yml"},
|
||||||
|
options: libstack.Options{
|
||||||
|
ProjectName: projectName,
|
||||||
|
},
|
||||||
|
osEnv: map[string]string{
|
||||||
|
"PORTAINER_WEB_FOLDER": "html-1",
|
||||||
|
"other_var": "something",
|
||||||
|
},
|
||||||
|
expectedProject: expectedSimpleComposeProject("", map[string]string{"PORTAINER_WEB_FOLDER": "html-1"}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Env Vars in compose file, compose env file, env, os, and env_file",
|
||||||
|
filesToCreate: map[string]string{
|
||||||
|
"docker-compose.yml": `services:
|
||||||
|
nginx:
|
||||||
|
container_name: nginx
|
||||||
|
image: nginx:latest
|
||||||
|
env_file: ` + dir + `/compose-stack.env
|
||||||
|
environment:
|
||||||
|
PORTAINER_VAR: compose_file_environment`,
|
||||||
|
"stack.env": "PORTAINER_VAR=env_file",
|
||||||
|
"compose-stack.env": "PORTAINER_VAR=compose_env_file",
|
||||||
|
},
|
||||||
|
configFilepaths: []string{dir + "/docker-compose.yml"},
|
||||||
|
options: libstack.Options{
|
||||||
|
ProjectName: projectName,
|
||||||
|
Env: []string{"PORTAINER_VAR=env"},
|
||||||
|
EnvFilePath: dir + "/stack.env",
|
||||||
|
},
|
||||||
|
osEnv: map[string]string{
|
||||||
|
"PORTAINER_VAR": "os",
|
||||||
|
},
|
||||||
|
expectedProject: &types.Project{
|
||||||
|
Name: projectName,
|
||||||
|
WorkingDir: dir,
|
||||||
|
Services: types.Services{
|
||||||
|
"nginx": {
|
||||||
|
Name: "nginx",
|
||||||
|
ContainerName: "nginx",
|
||||||
|
Environment: types.NewMappingWithEquals([]string{"PORTAINER_VAR=compose_file_environment"}),
|
||||||
|
Image: "nginx:latest",
|
||||||
|
Networks: map[string]*types.ServiceNetworkConfig{"default": nil},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Networks: types.Networks{"default": {Name: "create-project-test_default"}},
|
||||||
|
ComposeFiles: []string{
|
||||||
|
dir + "/docker-compose.yml",
|
||||||
|
},
|
||||||
|
Environment: map[string]string{"COMPOSE_PROJECT_NAME": "create-project-test", "PORTAINER_VAR": "env"},
|
||||||
|
DisabledServices: types.Services{},
|
||||||
|
Profiles: []string{""},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Env Vars in compose env file, env, os, and env_file",
|
||||||
|
filesToCreate: map[string]string{
|
||||||
|
"docker-compose.yml": `services:
|
||||||
|
nginx:
|
||||||
|
container_name: nginx
|
||||||
|
image: nginx:latest
|
||||||
|
env_file: ` + dir + `/compose-stack.env`,
|
||||||
|
"stack.env": "PORTAINER_VAR=env_file",
|
||||||
|
"compose-stack.env": "PORTAINER_VAR=compose_env_file",
|
||||||
|
},
|
||||||
|
configFilepaths: []string{dir + "/docker-compose.yml"},
|
||||||
|
options: libstack.Options{
|
||||||
|
ProjectName: projectName,
|
||||||
|
Env: []string{"PORTAINER_VAR=env"},
|
||||||
|
EnvFilePath: dir + "/stack.env",
|
||||||
|
},
|
||||||
|
osEnv: map[string]string{
|
||||||
|
"PORTAINER_VAR": "os",
|
||||||
|
},
|
||||||
|
expectedProject: &types.Project{
|
||||||
|
Name: projectName,
|
||||||
|
WorkingDir: dir,
|
||||||
|
Services: types.Services{
|
||||||
|
"nginx": {
|
||||||
|
Name: "nginx",
|
||||||
|
ContainerName: "nginx",
|
||||||
|
Environment: types.NewMappingWithEquals([]string{"PORTAINER_VAR=compose_env_file"}),
|
||||||
|
Image: "nginx:latest",
|
||||||
|
Networks: map[string]*types.ServiceNetworkConfig{"default": nil},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Networks: types.Networks{"default": {Name: "create-project-test_default"}},
|
||||||
|
ComposeFiles: []string{
|
||||||
|
dir + "/docker-compose.yml",
|
||||||
|
},
|
||||||
|
Environment: map[string]string{"COMPOSE_PROJECT_NAME": "create-project-test", "PORTAINER_VAR": "env"},
|
||||||
|
DisabledServices: types.Services{},
|
||||||
|
Profiles: []string{""},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Env Vars in env, os, and env_file",
|
||||||
|
filesToCreate: map[string]string{
|
||||||
|
"docker-compose.yml": `services:
|
||||||
|
nginx:
|
||||||
|
container_name: nginx
|
||||||
|
image: nginx:latest`,
|
||||||
|
"stack.env": "PORTAINER_VAR=env_file",
|
||||||
|
},
|
||||||
|
configFilepaths: []string{dir + "/docker-compose.yml"},
|
||||||
|
options: libstack.Options{
|
||||||
|
ProjectName: projectName,
|
||||||
|
Env: []string{"PORTAINER_VAR=env"},
|
||||||
|
EnvFilePath: dir + "/stack.env",
|
||||||
|
},
|
||||||
|
osEnv: map[string]string{
|
||||||
|
"PORTAINER_VAR": "os",
|
||||||
|
},
|
||||||
|
expectedProject: &types.Project{
|
||||||
|
Name: projectName,
|
||||||
|
WorkingDir: dir,
|
||||||
|
Services: types.Services{
|
||||||
|
"nginx": {
|
||||||
|
Name: "nginx",
|
||||||
|
ContainerName: "nginx",
|
||||||
|
Environment: types.MappingWithEquals{},
|
||||||
|
Image: "nginx:latest",
|
||||||
|
Networks: map[string]*types.ServiceNetworkConfig{"default": nil},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Networks: types.Networks{"default": {Name: "create-project-test_default"}},
|
||||||
|
ComposeFiles: []string{
|
||||||
|
dir + "/docker-compose.yml",
|
||||||
|
},
|
||||||
|
Environment: map[string]string{"COMPOSE_PROJECT_NAME": "create-project-test", "PORTAINER_VAR": "env"},
|
||||||
|
DisabledServices: types.Services{},
|
||||||
|
Profiles: []string{""},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Env Vars in os and env_file",
|
||||||
|
filesToCreate: map[string]string{
|
||||||
|
"docker-compose.yml": `services:
|
||||||
|
nginx:
|
||||||
|
container_name: nginx
|
||||||
|
image: nginx:latest`,
|
||||||
|
"stack.env": "PORTAINER_VAR=env_file",
|
||||||
|
},
|
||||||
|
configFilepaths: []string{dir + "/docker-compose.yml"},
|
||||||
|
options: libstack.Options{
|
||||||
|
ProjectName: projectName,
|
||||||
|
EnvFilePath: dir + "/stack.env",
|
||||||
|
},
|
||||||
|
osEnv: map[string]string{
|
||||||
|
"PORTAINER_VAR": "os",
|
||||||
|
},
|
||||||
|
expectedProject: &types.Project{
|
||||||
|
Name: projectName,
|
||||||
|
WorkingDir: dir,
|
||||||
|
Services: types.Services{
|
||||||
|
"nginx": {
|
||||||
|
Name: "nginx",
|
||||||
|
ContainerName: "nginx",
|
||||||
|
Environment: types.MappingWithEquals{},
|
||||||
|
Image: "nginx:latest",
|
||||||
|
Networks: map[string]*types.ServiceNetworkConfig{"default": nil},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Networks: types.Networks{"default": {Name: "create-project-test_default"}},
|
||||||
|
ComposeFiles: []string{
|
||||||
|
dir + "/docker-compose.yml",
|
||||||
|
},
|
||||||
|
Environment: map[string]string{"COMPOSE_PROJECT_NAME": "create-project-test", "PORTAINER_VAR": "os"},
|
||||||
|
DisabledServices: types.Services{},
|
||||||
|
Profiles: []string{""},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Env Vars in env_file",
|
||||||
|
filesToCreate: map[string]string{
|
||||||
|
"docker-compose.yml": `services:
|
||||||
|
nginx:
|
||||||
|
container_name: nginx
|
||||||
|
image: nginx:latest`,
|
||||||
|
"stack.env": "PORTAINER_VAR=env_file",
|
||||||
|
},
|
||||||
|
configFilepaths: []string{dir + "/docker-compose.yml"},
|
||||||
|
options: libstack.Options{
|
||||||
|
ProjectName: projectName,
|
||||||
|
EnvFilePath: dir + "/stack.env",
|
||||||
|
},
|
||||||
|
expectedProject: &types.Project{
|
||||||
|
Name: projectName,
|
||||||
|
WorkingDir: dir,
|
||||||
|
Services: types.Services{
|
||||||
|
"nginx": {
|
||||||
|
Name: "nginx",
|
||||||
|
ContainerName: "nginx",
|
||||||
|
Environment: types.MappingWithEquals{},
|
||||||
|
Image: "nginx:latest",
|
||||||
|
Networks: map[string]*types.ServiceNetworkConfig{"default": nil},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Networks: types.Networks{"default": {Name: "create-project-test_default"}},
|
||||||
|
ComposeFiles: []string{
|
||||||
|
dir + "/docker-compose.yml",
|
||||||
|
},
|
||||||
|
Environment: map[string]string{"COMPOSE_PROJECT_NAME": "create-project-test", "PORTAINER_VAR": "env_file"},
|
||||||
|
DisabledServices: types.Services{},
|
||||||
|
Profiles: []string{""},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testcases {
|
for _, tc := range testcases {
|
||||||
|
@ -1070,6 +1282,10 @@ func Test_createProject(t *testing.T) {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
for k, v := range tc.osEnv {
|
||||||
|
t.Setenv(k, v)
|
||||||
|
}
|
||||||
|
|
||||||
gotProject, err := createProject(ctx, tc.configFilepaths, tc.options)
|
gotProject, err := createProject(ctx, tc.configFilepaths, tc.options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create new project: %v", err)
|
t.Fatalf("Failed to create new project: %v", err)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue