From e15da005a59668f0a3c8d4f0ac65935bd988e5f3 Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Thu, 12 Jul 2018 09:17:07 +0200 Subject: [PATCH] feat(templates): support env variables in Compose stacks --- .../handler/stacks/create_compose_stack.go | 12 +++++++++++ api/http/handler/stacks/stack_update.go | 3 +++ api/libcompose/compose_stack.go | 21 +++++++++++++++---- app/portainer/services/api/stackService.js | 14 +++++++------ app/portainer/services/fileUpload.js | 5 +++-- .../stacks/create/createStackController.js | 7 ++++--- .../views/stacks/create/createstack.html | 2 +- .../views/templates/templatesController.js | 13 +++++++++--- 8 files changed, 58 insertions(+), 19 deletions(-) diff --git a/api/http/handler/stacks/create_compose_stack.go b/api/http/handler/stacks/create_compose_stack.go index 748b3c155..ccbf48e57 100644 --- a/api/http/handler/stacks/create_compose_stack.go +++ b/api/http/handler/stacks/create_compose_stack.go @@ -17,6 +17,7 @@ import ( type composeStackFromFileContentPayload struct { Name string StackFileContent string + Env []portainer.Pair } func (payload *composeStackFromFileContentPayload) Validate(r *http.Request) error { @@ -54,6 +55,7 @@ func (handler *Handler) createComposeStackFromFileContent(w http.ResponseWriter, Type: portainer.DockerComposeStack, EndpointID: endpoint.ID, EntryPoint: filesystem.ComposeFileDefaultName, + Env: payload.Env, } stackFolder := strconv.Itoa(int(stack.ID)) @@ -92,6 +94,7 @@ type composeStackFromGitRepositoryPayload struct { RepositoryUsername string RepositoryPassword string ComposeFilePathInRepository string + Env []portainer.Pair } func (payload *composeStackFromGitRepositoryPayload) Validate(r *http.Request) error { @@ -135,6 +138,7 @@ func (handler *Handler) createComposeStackFromGitRepository(w http.ResponseWrite Type: portainer.DockerComposeStack, EndpointID: endpoint.ID, EntryPoint: payload.ComposeFilePathInRepository, + Env: payload.Env, } projectPath := handler.FileService.GetStackProjectPath(strconv.Itoa(int(stack.ID))) @@ -177,6 +181,7 @@ func (handler *Handler) createComposeStackFromGitRepository(w http.ResponseWrite type composeStackFromFileUploadPayload struct { Name string StackFileContent []byte + Env []portainer.Pair } func (payload *composeStackFromFileUploadPayload) Validate(r *http.Request) error { @@ -192,6 +197,12 @@ func (payload *composeStackFromFileUploadPayload) Validate(r *http.Request) erro } payload.StackFileContent = composeFileContent + var env []portainer.Pair + err = request.RetrieveMultiPartFormJSONValue(r, "Env", &env, true) + if err != nil { + return portainer.Error("Invalid Env parameter") + } + payload.Env = env return nil } @@ -220,6 +231,7 @@ func (handler *Handler) createComposeStackFromFileUpload(w http.ResponseWriter, Type: portainer.DockerComposeStack, EndpointID: endpoint.ID, EntryPoint: filesystem.ComposeFileDefaultName, + Env: payload.Env, } stackFolder := strconv.Itoa(int(stack.ID)) diff --git a/api/http/handler/stacks/stack_update.go b/api/http/handler/stacks/stack_update.go index 6ffef9112..de1c560ae 100644 --- a/api/http/handler/stacks/stack_update.go +++ b/api/http/handler/stacks/stack_update.go @@ -15,6 +15,7 @@ import ( type updateComposeStackPayload struct { StackFileContent string + Env []portainer.Pair } func (payload *updateComposeStackPayload) Validate(r *http.Request) error { @@ -112,6 +113,8 @@ func (handler *Handler) updateComposeStack(r *http.Request, stack *portainer.Sta return &httperror.HandlerError{http.StatusBadRequest, "Invalid request payload", err} } + stack.Env = payload.Env + stackFolder := strconv.Itoa(int(stack.ID)) _, err = handler.FileService.StoreStackFileFromBytes(stackFolder, stack.EntryPoint, []byte(payload.StackFileContent)) if err != nil { diff --git a/api/libcompose/compose_stack.go b/api/libcompose/compose_stack.go index 5cf1264b6..a3a6a9e71 100644 --- a/api/libcompose/compose_stack.go +++ b/api/libcompose/compose_stack.go @@ -5,6 +5,7 @@ import ( "path" "path/filepath" + "github.com/portainer/libcompose/config" "github.com/portainer/libcompose/docker" "github.com/portainer/libcompose/docker/client" "github.com/portainer/libcompose/docker/ctx" @@ -35,19 +36,31 @@ func (manager *ComposeStackManager) Up(stack *portainer.Stack, endpoint *portain TLSCAFile: endpoint.TLSCACertPath, TLSCertFile: endpoint.TLSCertPath, TLSKeyFile: endpoint.TLSKeyPath, - APIVersion: "1.24", + APIVersion: portainer.SupportedDockerAPIVersion, }) if err != nil { return err } + env := make(map[string]string) + for _, envvar := range stack.Env { + env[envvar.Name] = envvar.Value + } + composeFilePath := path.Join(stack.ProjectPath, stack.EntryPoint) proj, err := docker.NewProject(&ctx.Context{ ConfigDir: manager.dataPath, Context: project.Context{ ComposeFiles: []string{composeFilePath}, - EnvironmentLookup: &lookup.EnvfileLookup{ - Path: filepath.Join(stack.ProjectPath, ".env"), + EnvironmentLookup: &lookup.ComposableEnvLookup{ + Lookups: []config.EnvironmentLookup{ + &lookup.EnvfileLookup{ + Path: filepath.Join(stack.ProjectPath, ".env"), + }, + &lookup.MapLookup{ + Vars: env, + }, + }, }, ProjectName: stack.Name, }, @@ -69,7 +82,7 @@ func (manager *ComposeStackManager) Down(stack *portainer.Stack, endpoint *porta TLSCAFile: endpoint.TLSCACertPath, TLSCertFile: endpoint.TLSCertPath, TLSKeyFile: endpoint.TLSKeyPath, - APIVersion: "1.24", + APIVersion: portainer.SupportedDockerAPIVersion, }) if err != nil { return err diff --git a/app/portainer/services/api/stackService.js b/app/portainer/services/api/stackService.js index c32f6bec2..c0add5cd7 100644 --- a/app/portainer/services/api/stackService.js +++ b/app/portainer/services/api/stackService.js @@ -223,8 +223,8 @@ function StackServiceFactory($q, Stack, ResourceControlService, FileUploadServic return Stack.update({ endpointId: stack.EndpointId }, { id: stack.Id, StackFileContent: stackFile, Env: env, Prune: prune }).$promise; }; - service.createComposeStackFromFileUpload = function(name, stackFile, endpointId) { - return FileUploadService.createComposeStack(name, stackFile, endpointId); + service.createComposeStackFromFileUpload = function(name, stackFile, env, endpointId) { + return FileUploadService.createComposeStack(name, stackFile, env, endpointId); }; service.createSwarmStackFromFileUpload = function(name, stackFile, env, endpointId) { @@ -245,10 +245,11 @@ function StackServiceFactory($q, Stack, ResourceControlService, FileUploadServic return deferred.promise; }; - service.createComposeStackFromFileContent = function(name, stackFileContent, endpointId) { + service.createComposeStackFromFileContent = function(name, stackFileContent, env, endpointId) { var payload = { Name: name, - StackFileContent: stackFileContent + StackFileContent: stackFileContent, + Env: env }; return Stack.create({ method: 'string', type: 2, endpointId: endpointId }, payload).$promise; }; @@ -277,14 +278,15 @@ function StackServiceFactory($q, Stack, ResourceControlService, FileUploadServic return deferred.promise; }; - service.createComposeStackFromGitRepository = function(name, repositoryOptions, endpointId) { + service.createComposeStackFromGitRepository = function(name, repositoryOptions, env, endpointId) { var payload = { Name: name, RepositoryURL: repositoryOptions.RepositoryURL, ComposeFilePathInRepository: repositoryOptions.ComposeFilePathInRepository, RepositoryAuthentication: repositoryOptions.RepositoryAuthentication, RepositoryUsername: repositoryOptions.RepositoryUsername, - RepositoryPassword: repositoryOptions.RepositoryPassword + RepositoryPassword: repositoryOptions.RepositoryPassword, + Env: env }; return Stack.create({ method: 'repository', type: 2, endpointId: endpointId }, payload).$promise; }; diff --git a/app/portainer/services/fileUpload.js b/app/portainer/services/fileUpload.js index aef41e47a..2321d489c 100644 --- a/app/portainer/services/fileUpload.js +++ b/app/portainer/services/fileUpload.js @@ -41,12 +41,13 @@ angular.module('portainer.app') }); }; - service.createComposeStack = function(stackName, file, endpointId) { + service.createComposeStack = function(stackName, file, env, endpointId) { return Upload.upload({ url: 'api/stacks?method=file&type=2&endpointId=' + endpointId, data: { file: file, - Name: stackName + Name: stackName, + Env: Upload.json(env) }, ignoreLoadingBar: true }); diff --git a/app/portainer/views/stacks/create/createStackController.js b/app/portainer/views/stacks/create/createStackController.js index 4a02d5add..9c17bfe49 100644 --- a/app/portainer/views/stacks/create/createStackController.js +++ b/app/portainer/views/stacks/create/createStackController.js @@ -65,14 +65,15 @@ function ($scope, $state, StackService, Authentication, Notifications, FormValid } function createComposeStack(name, method) { + var env = FormHelper.removeInvalidEnvVars($scope.formValues.Env); var endpointId = EndpointProvider.endpointID(); if (method === 'editor') { var stackFileContent = $scope.formValues.StackFileContent; - return StackService.createComposeStackFromFileContent(name, stackFileContent, endpointId); + return StackService.createComposeStackFromFileContent(name, stackFileContent, env, endpointId); } else if (method === 'upload') { var stackFile = $scope.formValues.StackFile; - return StackService.createComposeStackFromFileUpload(name, stackFile, endpointId); + return StackService.createComposeStackFromFileUpload(name, stackFile, env, endpointId); } else if (method === 'repository') { var repositoryOptions = { RepositoryURL: $scope.formValues.RepositoryURL, @@ -81,7 +82,7 @@ function ($scope, $state, StackService, Authentication, Notifications, FormValid RepositoryUsername: $scope.formValues.RepositoryUsername, RepositoryPassword: $scope.formValues.RepositoryPassword }; - return StackService.createComposeStackFromGitRepository(name, repositoryOptions, endpointId); + return StackService.createComposeStackFromGitRepository(name, repositoryOptions, env, endpointId); } } diff --git a/app/portainer/views/stacks/create/createstack.html b/app/portainer/views/stacks/create/createstack.html index d9fcea05b..84bee5d86 100644 --- a/app/portainer/views/stacks/create/createstack.html +++ b/app/portainer/views/stacks/create/createstack.html @@ -160,7 +160,7 @@ -
+
Environment
diff --git a/app/portainer/views/templates/templatesController.js b/app/portainer/views/templates/templatesController.js index 7c9607715..30c4119ec 100644 --- a/app/portainer/views/templates/templatesController.js +++ b/app/portainer/views/templates/templatesController.js @@ -94,13 +94,20 @@ function ($scope, $q, $state, $transition$, $anchorScroll, ContainerService, Ima function createComposeStackFromTemplate(template, userId, accessControlData) { var stackName = $scope.formValues.name; + for (var i = 0; i < template.Env.length; i++) { + var envvar = template.Env[i]; + if (envvar.preset) { + envvar.value = envvar.default; + } + } + var repositoryOptions = { RepositoryURL: template.Repository.url, ComposeFilePathInRepository: template.Repository.stackfile }; var endpointId = EndpointProvider.endpointID(); - StackService.createComposeStackFromGitRepository(stackName, repositoryOptions, endpointId) + StackService.createComposeStackFromGitRepository(stackName, repositoryOptions, template.Env, endpointId) .then(function success(data) { return ResourceControlService.applyResourceControl('stack', stackName, userId, accessControlData, []); }) @@ -121,8 +128,8 @@ function ($scope, $q, $state, $transition$, $anchorScroll, ContainerService, Ima for (var i = 0; i < template.Env.length; i++) { var envvar = template.Env[i]; - if (envvar.set) { - envvar.value = envvar.set; + if (envvar.preset) { + envvar.value = envvar.default; } }