diff --git a/api/edge/edge.go b/api/edge/edge.go
index ebeee170f..864fbf39a 100644
--- a/api/edge/edge.go
+++ b/api/edge/edge.go
@@ -1,6 +1,9 @@
package edge
-import "github.com/portainer/portainer/api/filesystem"
+import (
+ portainer "github.com/portainer/portainer/api"
+ "github.com/portainer/portainer/api/filesystem"
+)
type (
@@ -40,6 +43,9 @@ type (
SupportRelativePath bool
// Mount point for relative path
FilesystemPath string
+ // Used only for EE
+ // EnvVars is a list of environment variables to inject into the stack
+ EnvVars []portainer.Pair
}
// RegistryCredentials holds the credentials for a Docker registry.
diff --git a/app/edge/views/edge-stacks/createEdgeStackView/create-edge-stack-view.controller.js b/app/edge/views/edge-stacks/createEdgeStackView/create-edge-stack-view.controller.js
index 44047571c..f17755926 100644
--- a/app/edge/views/edge-stacks/createEdgeStackView/create-edge-stack-view.controller.js
+++ b/app/edge/views/edge-stacks/createEdgeStackView/create-edge-stack-view.controller.js
@@ -4,6 +4,7 @@ import { STACK_NAME_VALIDATION_REGEX } from '@/react/constants';
import { confirmWebEditorDiscard } from '@@/modals/confirm';
import { baseEdgeStackWebhookUrl } from '@/portainer/helpers/webhookHelper';
import { EnvironmentType } from '@/react/portainer/environments/types';
+import { isBE } from '@/react/portainer/feature-flags/feature-flags.service';
export default class CreateEdgeStackViewController {
/* @ngInject */
@@ -19,16 +20,17 @@ export default class CreateEdgeStackViewController {
RepositoryAuthentication: false,
RepositoryUsername: '',
RepositoryPassword: '',
- Env: [],
ComposeFilePathInRepository: '',
Groups: [],
DeploymentType: 0,
UseManifestNamespaces: false,
TLSSkipVerify: false,
+ envVars: [],
};
this.EditorType = EditorType;
this.EnvironmentType = EnvironmentType;
+ this.isBE = isBE;
this.state = {
Method: 'editor',
@@ -54,6 +56,13 @@ export default class CreateEdgeStackViewController {
this.onChangeGroups = this.onChangeGroups.bind(this);
this.hasType = this.hasType.bind(this);
this.onChangeDeploymentType = this.onChangeDeploymentType.bind(this);
+ this.onEnvVarChange = this.onEnvVarChange.bind(this);
+ }
+
+ onEnvVarChange(envVars) {
+ return this.$scope.$evalAsync(() => {
+ this.formValues.envVars = envVars;
+ });
}
buildAnalyticsProperties() {
@@ -183,7 +192,7 @@ export default class CreateEdgeStackViewController {
}
createStackFromFileContent(name) {
- const { StackFileContent, Groups, DeploymentType, UseManifestNamespaces } = this.formValues;
+ const { StackFileContent, Groups, DeploymentType, UseManifestNamespaces, envVars } = this.formValues;
return this.EdgeStackService.createStackFromFileContent({
name,
@@ -191,24 +200,26 @@ export default class CreateEdgeStackViewController {
EdgeGroups: Groups,
DeploymentType,
UseManifestNamespaces,
+ envVars,
});
}
createStackFromFileUpload(name) {
- const { StackFile, Groups, DeploymentType, UseManifestNamespaces } = this.formValues;
+ const { StackFile, Groups, DeploymentType, UseManifestNamespaces, envVars } = this.formValues;
return this.EdgeStackService.createStackFromFileUpload(
{
Name: name,
EdgeGroups: Groups,
DeploymentType,
UseManifestNamespaces,
+ envVars,
},
StackFile
);
}
createStackFromGitRepository(name) {
- const { Groups, DeploymentType, UseManifestNamespaces } = this.formValues;
+ const { Groups, DeploymentType, UseManifestNamespaces, envVars } = this.formValues;
const repositoryOptions = {
RepositoryURL: this.formValues.RepositoryURL,
RepositoryReferenceName: this.formValues.RepositoryReferenceName,
@@ -224,6 +235,7 @@ export default class CreateEdgeStackViewController {
EdgeGroups: Groups,
DeploymentType,
UseManifestNamespaces,
+ envVars,
},
repositoryOptions
);
diff --git a/app/edge/views/edge-stacks/createEdgeStackView/create-edge-stack-view.html b/app/edge/views/edge-stacks/createEdgeStackView/create-edge-stack-view.html
index 63b8bdab5..234057816 100644
--- a/app/edge/views/edge-stacks/createEdgeStackView/create-edge-stack-view.html
+++ b/app/edge/views/edge-stacks/createEdgeStackView/create-edge-stack-view.html
@@ -66,6 +66,10 @@
state="$ctrl.state"
>
+
+
+
+
Actions
diff --git a/app/edge/views/edge-stacks/editEdgeStackView/editEdgeStackViewController.js b/app/edge/views/edge-stacks/editEdgeStackView/editEdgeStackViewController.js
index bf9c27043..503cfa0ec 100644
--- a/app/edge/views/edge-stacks/editEdgeStackView/editEdgeStackViewController.js
+++ b/app/edge/views/edge-stacks/editEdgeStackView/editEdgeStackViewController.js
@@ -102,6 +102,7 @@ export class EditEdgeStackViewController {
deploymentType: values.deploymentType,
updateVersion,
webhook: values.webhookEnabled ? this.stack.Webhook || createWebhookId() : '',
+ envVars: values.envVars,
});
this.Notifications.success('Success', 'Stack successfully deployed');
this.state.isStackDeployed = true;
diff --git a/app/portainer/services/fileUpload.js b/app/portainer/services/fileUpload.js
index 37653f41c..bc9664c3f 100644
--- a/app/portainer/services/fileUpload.js
+++ b/app/portainer/services/fileUpload.js
@@ -111,12 +111,13 @@ angular.module('portainer.app').factory('FileUploadService', [
});
};
- service.createEdgeStack = function createEdgeStack({ EdgeGroups, ...payload }, file) {
+ service.createEdgeStack = function createEdgeStack({ EdgeGroups, envVars, ...payload }, file) {
return Upload.upload({
url: `api/edge_stacks/create/file`,
data: {
file,
EdgeGroups: Upload.json(EdgeGroups),
+ EnvVars: Upload.json(envVars),
...payload,
},
ignoreLoadingBar: true,
diff --git a/app/react/edge/edge-stacks/ItemView/EditEdgeStackForm/EditEdgeStackForm.tsx b/app/react/edge/edge-stacks/ItemView/EditEdgeStackForm/EditEdgeStackForm.tsx
index 0bae932ac..5681d0c77 100644
--- a/app/react/edge/edge-stacks/ItemView/EditEdgeStackForm/EditEdgeStackForm.tsx
+++ b/app/react/edge/edge-stacks/ItemView/EditEdgeStackForm/EditEdgeStackForm.tsx
@@ -15,6 +15,10 @@ import { TextTip } from '@@/Tip/TextTip';
import { SwitchField } from '@@/form-components/SwitchField';
import { LoadingButton } from '@@/buttons';
import { FormError } from '@@/form-components/FormError';
+import {
+ EnvironmentVariablesPanel,
+ envVarValidation,
+} from '@@/form-components/EnvironmentVariablesFieldset';
import { PrivateRegistryFieldsetWrapper } from './PrivateRegistryFieldsetWrapper';
import { FormValues } from './types';
@@ -61,6 +65,7 @@ export function EditEdgeStackForm({
prePullImage: edgeStack.PrePullImage,
retryDeploy: edgeStack.RetryDeploy,
webhookEnabled: !!edgeStack.Webhook,
+ envVars: edgeStack.EnvVars || [],
};
return (
@@ -181,6 +186,13 @@ function InnerForm({
onFieldError={(error) => setFieldError('privateRegistryId', error)}
error={errors.privateRegistryId}
/>
+
+
setFieldValue('envVars', value)}
+ values={values.envVars}
+ errors={errors.envVars}
+ />
+
{values.deploymentType === DeploymentType.Compose && (
<>
@@ -271,5 +283,6 @@ function formValidation(): SchemaOf {
.required()
.min(1, 'At least one edge group is required'),
webhookEnabled: boolean().default(false),
+ envVars: envVarValidation(),
});
}
diff --git a/app/react/edge/edge-stacks/ItemView/EditEdgeStackForm/GitForm/GitForm.tsx b/app/react/edge/edge-stacks/ItemView/EditEdgeStackForm/GitForm/GitForm.tsx
index 4726ce1a0..5c44a820b 100644
--- a/app/react/edge/edge-stacks/ItemView/EditEdgeStackForm/GitForm/GitForm.tsx
+++ b/app/react/edge/edge-stacks/ItemView/EditEdgeStackForm/GitForm/GitForm.tsx
@@ -31,6 +31,8 @@ import { LoadingButton } from '@@/buttons';
import { FormSection } from '@@/form-components/FormSection';
import { TextTip } from '@@/Tip/TextTip';
import { FormError } from '@@/form-components/FormError';
+import { EnvironmentVariablesPanel } from '@@/form-components/EnvironmentVariablesFieldset';
+import { EnvVar } from '@@/form-components/EnvironmentVariablesFieldset/types';
import { useValidateEnvironmentTypes } from '../useEdgeGroupHasType';
import { atLeastTwo } from '../atLeastTwo';
@@ -43,6 +45,7 @@ interface FormValues {
autoUpdate: AutoUpdateModel;
refName: string;
authentication: GitAuthModel;
+ envVars: EnvVar[];
}
export function GitForm({ stack }: { stack: EdgeStack }) {
@@ -63,6 +66,7 @@ export function GitForm({ stack }: { stack: EdgeStack }) {
autoUpdate: parseAutoUpdateResponse(stack.AutoUpdate),
refName: stack.GitConfig.ReferenceName,
authentication: parseAuthResponse(stack.GitConfig.Authentication),
+ envVars: stack.EnvVars || [],
};
const webhookId = stack.AutoUpdate?.Webhook || createWebhookId();
@@ -253,6 +257,12 @@ function InnerForm({
}
errors={errors.authentication}
/>
+
+ setFieldValue('envVars', value)}
+ values={values.envVars}
+ errors={errors.envVars}
+ />
diff --git a/app/react/edge/edge-stacks/ItemView/EditEdgeStackForm/types.ts b/app/react/edge/edge-stacks/ItemView/EditEdgeStackForm/types.ts
index 85b6328cd..25401b674 100644
--- a/app/react/edge/edge-stacks/ItemView/EditEdgeStackForm/types.ts
+++ b/app/react/edge/edge-stacks/ItemView/EditEdgeStackForm/types.ts
@@ -1,6 +1,8 @@
import { EdgeGroup } from '@/react/edge/edge-groups/types';
import { DeploymentType } from '@/react/edge/edge-stacks/types';
+import { EnvVar } from '@@/form-components/EnvironmentVariablesFieldset/types';
+
export interface FormValues {
edgeGroups: EdgeGroup['Id'][];
deploymentType: DeploymentType;
@@ -10,4 +12,5 @@ export interface FormValues {
prePullImage: boolean;
retryDeploy: boolean;
webhookEnabled: boolean;
+ envVars: EnvVar[];
}
diff --git a/app/react/edge/edge-stacks/types.ts b/app/react/edge/edge-stacks/types.ts
index 5d1ac61ae..88ab61ff7 100644
--- a/app/react/edge/edge-stacks/types.ts
+++ b/app/react/edge/edge-stacks/types.ts
@@ -5,6 +5,8 @@ import {
} from '@/react/portainer/gitops/types';
import { RegistryId } from '@/react/portainer/registries/types';
+import { EnvVar } from '@@/form-components/EnvironmentVariablesFieldset/types';
+
import { EdgeGroup } from '../edge-groups/types';
interface EdgeStackStatusDetails {
@@ -57,6 +59,7 @@ export type EdgeStack = {
Prune: boolean;
RetryDeploy: boolean;
Webhook?: string;
+ EnvVars?: EnvVar[];
};
export enum EditorType {