Compare commits
10 Commits
feat/ee-83
...
fix/EE-167
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dde3eb384f | ||
|
|
ed0c14d51c | ||
|
|
a098e24cca | ||
|
|
5d8c23e3a6 | ||
|
|
52f9320952 | ||
|
|
e3f7561ced | ||
|
|
c7760b7d48 | ||
|
|
1633eceed5 | ||
|
|
e437a3b570 | ||
|
|
396a921b12 |
16
.github/ISSUE_TEMPLATE.md
vendored
16
.github/ISSUE_TEMPLATE.md
vendored
@@ -28,17 +28,15 @@ Briefly describe the problem you are having in a few paragraphs.
|
||||
|
||||
**Steps to reproduce the issue:**
|
||||
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
1. 2. 3.
|
||||
|
||||
Any other info e.g. Why do you consider this to be a bug? What did you expect to happen instead?
|
||||
|
||||
**Technical details:**
|
||||
|
||||
* Portainer version:
|
||||
* Target Docker version (the host/cluster you manage):
|
||||
* Platform (windows/linux):
|
||||
* Command used to start Portainer (`docker run -p 9000:9000 portainer/portainer`):
|
||||
* Target Swarm version (if applicable):
|
||||
* Browser:
|
||||
- Portainer version:
|
||||
- Target Docker version (the host/cluster you manage):
|
||||
- Platform (windows/linux):
|
||||
- Command used to start Portainer (`docker run -p 9443:9443 portainer/portainer`):
|
||||
- Target Swarm version (if applicable):
|
||||
- Browser:
|
||||
|
||||
3
.github/ISSUE_TEMPLATE/Bug_report.md
vendored
3
.github/ISSUE_TEMPLATE/Bug_report.md
vendored
@@ -4,7 +4,6 @@ about: Create a bug report
|
||||
title: ''
|
||||
labels: bug/need-confirmation, kind/bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
@@ -46,7 +45,7 @@ You can see how [here](https://documentation.portainer.io/archive/1.23.2/faq/#ho
|
||||
- Docker version (managed by Portainer):
|
||||
- Kubernetes version (managed by Portainer):
|
||||
- Platform (windows/linux):
|
||||
- Command used to start Portainer (`docker run -p 9000:9000 portainer/portainer`):
|
||||
- Command used to start Portainer (`docker run -p 9443:9443 portainer/portainer`):
|
||||
- Browser:
|
||||
- Use Case (delete as appropriate): Using Portainer at Home, Using Portainer in a Commerical setup.
|
||||
- Have you reviewed our technical documentation and knowledge base? Yes/No
|
||||
|
||||
@@ -91,7 +91,7 @@ Then build and run the project:
|
||||
$ yarn start
|
||||
```
|
||||
|
||||
Portainer can now be accessed at <http://localhost:9000>.
|
||||
Portainer can now be accessed at <https://localhost:9443>.
|
||||
|
||||
Find more detailed steps at <https://documentation.portainer.io/contributing/instructions/>.
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
@@ -58,8 +59,8 @@ func (manager *ComposeStackManager) Up(ctx context.Context, stack *portainer.Sta
|
||||
return errors.Wrap(err, "failed to create env file")
|
||||
}
|
||||
|
||||
filePaths := append([]string{stack.EntryPoint}, stack.AdditionalFiles...)
|
||||
return manager.deployer.Deploy(ctx, stack.ProjectPath, url, stack.Name, filePaths, envFilePath)
|
||||
filePaths := getStackFiles(stack)
|
||||
err = manager.deployer.Deploy(ctx, stack.ProjectPath, url, stack.Name, filePaths, envFilePath)
|
||||
return errors.Wrap(err, "failed to deploy a stack")
|
||||
}
|
||||
|
||||
@@ -73,9 +74,9 @@ func (manager *ComposeStackManager) Down(ctx context.Context, stack *portainer.S
|
||||
defer proxy.Close()
|
||||
}
|
||||
|
||||
filePaths := append([]string{stack.EntryPoint}, stack.AdditionalFiles...)
|
||||
|
||||
return manager.deployer.Remove(ctx, stack.ProjectPath, url, stack.Name, filePaths)
|
||||
filePaths := getStackFiles(stack)
|
||||
err = manager.deployer.Remove(ctx, stack.ProjectPath, url, stack.Name, filePaths)
|
||||
return errors.Wrap(err, "failed to remove a stack")
|
||||
}
|
||||
|
||||
// NormalizeStackName returns a new stack name with unsupported characters replaced
|
||||
@@ -116,3 +117,27 @@ func createEnvFile(stack *portainer.Stack) (string, error) {
|
||||
|
||||
return "stack.env", nil
|
||||
}
|
||||
|
||||
// getStackFiles returns list of stack's confile file paths.
|
||||
// items in the list would be sanitized according to following criterias:
|
||||
// 1. no empty paths
|
||||
// 2. no "../xxx" paths that are trying to escape stack folder
|
||||
// 3. no dir paths
|
||||
// 4. root paths would be made relative
|
||||
func getStackFiles(stack *portainer.Stack) []string {
|
||||
paths := make([]string, 0, len(stack.AdditionalFiles)+1)
|
||||
|
||||
for _, p := range append([]string{stack.EntryPoint}, stack.AdditionalFiles...) {
|
||||
if strings.HasPrefix(p, "/") {
|
||||
p = `.` + p
|
||||
}
|
||||
|
||||
if p == `` || p == `.` || strings.HasPrefix(p, `..`) || strings.HasSuffix(p, string(filepath.Separator)) {
|
||||
continue
|
||||
}
|
||||
|
||||
paths = append(paths, p)
|
||||
}
|
||||
|
||||
return paths
|
||||
}
|
||||
|
||||
@@ -64,3 +64,21 @@ func Test_createEnvFile(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_getStackFiles(t *testing.T) {
|
||||
stack := &portainer.Stack{
|
||||
EntryPoint: "./file", // picks entry point
|
||||
AdditionalFiles: []string{
|
||||
``, // ignores empty string
|
||||
`.`, // ignores .
|
||||
`..`, // ignores ..
|
||||
`./dir/`, // ignrores paths that end with trailing /
|
||||
`/with-root-prefix`, // replaces "root" based paths with relative
|
||||
`./relative`, // keeps relative paths
|
||||
`../escape`, // prevents dir escape
|
||||
},
|
||||
}
|
||||
|
||||
filePaths := getStackFiles(stack)
|
||||
assert.ElementsMatch(t, filePaths, []string{`./file`, `./with-root-prefix`, `./relative`})
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ import (
|
||||
// @description **Access policy**: authenticated
|
||||
// @tags custom_templates
|
||||
// @security jwt
|
||||
// @accept json, multipart/form-data
|
||||
// @accept json,multipart/form-data
|
||||
// @produce json
|
||||
// @param method query string true "method for creating template" Enums(string, file, repository)
|
||||
// @param body_string body customTemplateFromFileContentPayload false "Required when using method=string"
|
||||
|
||||
@@ -41,7 +41,7 @@ func (payload *edgeGroupCreatePayload) Validate(r *http.Request) error {
|
||||
// @produce json
|
||||
// @param body body edgeGroupCreatePayload true "EdgeGroup data"
|
||||
// @success 200 {object} portainer.EdgeGroup
|
||||
// @failure 503 Edge compute features are disabled
|
||||
// @failure 503 "Edge compute features are disabled"
|
||||
// @failure 500
|
||||
// @router /edge_groups [post]
|
||||
func (handler *Handler) edgeGroupCreate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
// @produce json
|
||||
// @param id path int true "EdgeGroup Id"
|
||||
// @success 204
|
||||
// @failure 503 Edge compute features are disabled
|
||||
// @failure 503 "Edge compute features are disabled"
|
||||
// @failure 500
|
||||
// @router /edge_groups/{id} [delete]
|
||||
func (handler *Handler) edgeGroupDelete(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
|
||||
@@ -19,7 +19,7 @@ import (
|
||||
// @produce json
|
||||
// @param id path int true "EdgeGroup Id"
|
||||
// @success 200 {object} portainer.EdgeGroup
|
||||
// @failure 503 Edge compute features are disabled
|
||||
// @failure 503 "Edge compute features are disabled"
|
||||
// @failure 500
|
||||
// @router /edge_groups/{id} [get]
|
||||
func (handler *Handler) edgeGroupInspect(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
|
||||
@@ -24,7 +24,7 @@ type decoratedEdgeGroup struct {
|
||||
// @produce json
|
||||
// @success 200 {array} portainer.EdgeGroup{HasEdgeStack=bool} "EdgeGroups"
|
||||
// @failure 500
|
||||
// @failure 503 Edge compute features are disabled
|
||||
// @failure 503 "Edge compute features are disabled"
|
||||
// @router /edge_groups [get]
|
||||
func (handler *Handler) edgeGroupList(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
edgeGroups, err := handler.DataStore.EdgeGroup().EdgeGroups()
|
||||
|
||||
@@ -44,7 +44,7 @@ func (payload *edgeGroupUpdatePayload) Validate(r *http.Request) error {
|
||||
// @param id path int true "EdgeGroup Id"
|
||||
// @param body body edgeGroupUpdatePayload true "EdgeGroup data"
|
||||
// @success 200 {object} portainer.EdgeGroup
|
||||
// @failure 503 Edge compute features are disabled
|
||||
// @failure 503 "Edge compute features are disabled"
|
||||
// @failure 500
|
||||
// @router /edge_groups/{id} [put]
|
||||
func (handler *Handler) edgeGroupUpdate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
|
||||
@@ -22,10 +22,10 @@ import (
|
||||
// @accept json
|
||||
// @produce json
|
||||
// @param method query string true "Creation Method" Enums(file, string)
|
||||
// @param body body edgeJobCreateFromFileContentPayload true "EdgeGroup data when method is string"
|
||||
// @param body body edgeJobCreateFromFilePayload true "EdgeGroup data when method is file"
|
||||
// @param body_string body edgeJobCreateFromFileContentPayload true "EdgeGroup data when method is string"
|
||||
// @param body_file body edgeJobCreateFromFilePayload true "EdgeGroup data when method is file"
|
||||
// @success 200 {object} portainer.EdgeGroup
|
||||
// @failure 503 Edge compute features are disabled
|
||||
// @failure 503 "Edge compute features are disabled"
|
||||
// @failure 500
|
||||
// @router /edge_jobs [post]
|
||||
func (handler *Handler) edgeJobCreate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
// @success 204
|
||||
// @failure 500
|
||||
// @failure 400
|
||||
// @failure 503 Edge compute features are disabled
|
||||
// @failure 503 "Edge compute features are disabled"
|
||||
// @router /edge_jobs/{id} [delete]
|
||||
func (handler *Handler) edgeJobDelete(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
edgeJobID, err := request.RetrieveNumericRouteVariableValue(r, "id")
|
||||
|
||||
@@ -25,7 +25,7 @@ type edgeJobFileResponse struct {
|
||||
// @success 200 {object} edgeJobFileResponse
|
||||
// @failure 500
|
||||
// @failure 400
|
||||
// @failure 503 Edge compute features are disabled
|
||||
// @failure 503 "Edge compute features are disabled"
|
||||
// @router /edge_jobs/{id}/file [get]
|
||||
func (handler *Handler) edgeJobFile(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
edgeJobID, err := request.RetrieveNumericRouteVariableValue(r, "id")
|
||||
|
||||
@@ -26,7 +26,7 @@ type edgeJobInspectResponse struct {
|
||||
// @success 200 {object} portainer.EdgeJob
|
||||
// @failure 500
|
||||
// @failure 400
|
||||
// @failure 503 Edge compute features are disabled
|
||||
// @failure 503 "Edge compute features are disabled"
|
||||
// @router /edge_jobs/{id} [get]
|
||||
func (handler *Handler) edgeJobInspect(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
edgeJobID, err := request.RetrieveNumericRouteVariableValue(r, "id")
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
// @success 200 {array} portainer.EdgeJob
|
||||
// @failure 500
|
||||
// @failure 400
|
||||
// @failure 503 Edge compute features are disabled
|
||||
// @failure 503 "Edge compute features are disabled"
|
||||
// @router /edge_jobs [get]
|
||||
// GET request on /api/edge_jobs
|
||||
func (handler *Handler) edgeJobList(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
|
||||
@@ -23,7 +23,7 @@ import (
|
||||
// @success 204
|
||||
// @failure 500
|
||||
// @failure 400
|
||||
// @failure 503 Edge compute features are disabled
|
||||
// @failure 503 "Edge compute features are disabled"
|
||||
// @router /edge_jobs/{id}/tasks/{taskID}/logs [delete]
|
||||
func (handler *Handler) edgeJobTasksClear(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
edgeJobID, err := request.RetrieveNumericRouteVariableValue(r, "id")
|
||||
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
// @success 204
|
||||
// @failure 500
|
||||
// @failure 400
|
||||
// @failure 503 Edge compute features are disabled
|
||||
// @failure 503 "Edge compute features are disabled"
|
||||
// @router /edge_jobs/{id}/tasks/{taskID}/logs [post]
|
||||
func (handler *Handler) edgeJobTasksCollect(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
edgeJobID, err := request.RetrieveNumericRouteVariableValue(r, "id")
|
||||
|
||||
@@ -25,7 +25,7 @@ type fileResponse struct {
|
||||
// @success 200 {object} fileResponse
|
||||
// @failure 500
|
||||
// @failure 400
|
||||
// @failure 503 Edge compute features are disabled
|
||||
// @failure 503 "Edge compute features are disabled"
|
||||
// @router /edge_jobs/{id}/tasks/{taskID}/logs [get]
|
||||
func (handler *Handler) edgeJobTaskLogsInspect(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
edgeJobID, err := request.RetrieveNumericRouteVariableValue(r, "id")
|
||||
|
||||
@@ -28,7 +28,7 @@ type taskContainer struct {
|
||||
// @success 200 {array} taskContainer
|
||||
// @failure 500
|
||||
// @failure 400
|
||||
// @failure 503 Edge compute features are disabled
|
||||
// @failure 503 "Edge compute features are disabled"
|
||||
// @router /edge_jobs/{id}/tasks [get]
|
||||
func (handler *Handler) edgeJobTasksList(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
edgeJobID, err := request.RetrieveNumericRouteVariableValue(r, "id")
|
||||
|
||||
@@ -40,7 +40,7 @@ func (payload *edgeJobUpdatePayload) Validate(r *http.Request) error {
|
||||
// @success 200 {object} portainer.EdgeJob
|
||||
// @failure 500
|
||||
// @failure 400
|
||||
// @failure 503 Edge compute features are disabled
|
||||
// @failure 503 "Edge compute features are disabled"
|
||||
// @router /edge_jobs/{id} [post]
|
||||
func (handler *Handler) edgeJobUpdate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
edgeJobID, err := request.RetrieveNumericRouteVariableValue(r, "id")
|
||||
|
||||
@@ -30,7 +30,7 @@ import (
|
||||
// @param body_repository body swarmStackFromGitRepositoryPayload true "Required when using method=repository"
|
||||
// @success 200 {object} portainer.EdgeStack
|
||||
// @failure 500
|
||||
// @failure 503 Edge compute features are disabled
|
||||
// @failure 503 "Edge compute features are disabled"
|
||||
// @router /edge_stacks [post]
|
||||
func (handler *Handler) edgeStackCreate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
method, err := request.RetrieveQueryParameter(r, "method", false)
|
||||
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
// @success 204
|
||||
// @failure 500
|
||||
// @failure 400
|
||||
// @failure 503 Edge compute features are disabled
|
||||
// @failure 503 "Edge compute features are disabled"
|
||||
// @router /edge_stacks/{id} [delete]
|
||||
func (handler *Handler) edgeStackDelete(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
edgeStackID, err := request.RetrieveNumericRouteVariableValue(r, "id")
|
||||
|
||||
@@ -26,7 +26,7 @@ type stackFileResponse struct {
|
||||
// @success 200 {object} stackFileResponse
|
||||
// @failure 500
|
||||
// @failure 400
|
||||
// @failure 503 Edge compute features are disabled
|
||||
// @failure 503 "Edge compute features are disabled"
|
||||
// @router /edge_stacks/{id}/file [get]
|
||||
func (handler *Handler) edgeStackFile(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
stackID, err := request.RetrieveNumericRouteVariableValue(r, "id")
|
||||
|
||||
@@ -21,7 +21,7 @@ import (
|
||||
// @success 200 {object} portainer.EdgeStack
|
||||
// @failure 500
|
||||
// @failure 400
|
||||
// @failure 503 Edge compute features are disabled
|
||||
// @failure 503 "Edge compute features are disabled"
|
||||
// @router /edge_stacks/{id} [get]
|
||||
func (handler *Handler) edgeStackInspect(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
edgeStackID, err := request.RetrieveNumericRouteVariableValue(r, "id")
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
// @success 200 {array} portainer.EdgeStack
|
||||
// @failure 500
|
||||
// @failure 400
|
||||
// @failure 503 Edge compute features are disabled
|
||||
// @failure 503 "Edge compute features are disabled"
|
||||
// @router /edge_stacks [get]
|
||||
func (handler *Handler) edgeStackList(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
edgeStacks, err := handler.DataStore.EdgeStack().EdgeStacks()
|
||||
|
||||
@@ -43,7 +43,7 @@ func (payload *updateEdgeStackPayload) Validate(r *http.Request) error {
|
||||
// @success 200 {object} portainer.EdgeStack
|
||||
// @failure 500
|
||||
// @failure 400
|
||||
// @failure 503 Edge compute features are disabled
|
||||
// @failure 503 "Edge compute features are disabled"
|
||||
// @router /edge_stacks/{id} [put]
|
||||
func (handler *Handler) edgeStackUpdate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
stackID, err := request.RetrieveNumericRouteVariableValue(r, "id")
|
||||
|
||||
@@ -24,7 +24,7 @@ type configResponse struct {
|
||||
// @accept json
|
||||
// @produce json
|
||||
// @param id path string true "Endpoint Id"
|
||||
// @param stackID path string true "EdgeStack Id"
|
||||
// @param stackId path string true "EdgeStack Id"
|
||||
// @success 200 {object} configResponse
|
||||
// @failure 500
|
||||
// @failure 400
|
||||
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
// @failure 400 "Invalid request"
|
||||
// @failure 404 "EndpointGroup not found"
|
||||
// @failure 500 "Server error"
|
||||
// @router /endpoint_groups/:id [get]
|
||||
// @router /endpoint_groups/{id} [get]
|
||||
func (handler *Handler) endpointGroupInspect(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
endpointGroupID, err := request.RetrieveNumericRouteVariableValue(r, "id")
|
||||
if err != nil {
|
||||
|
||||
@@ -41,7 +41,7 @@ func (payload *endpointGroupUpdatePayload) Validate(r *http.Request) error {
|
||||
// @failure 400 "Invalid request"
|
||||
// @failure 404 "EndpointGroup not found"
|
||||
// @failure 500 "Server error"
|
||||
// @router /endpoint_groups/:id [put]
|
||||
// @router /endpoint_groups/{id} [put]
|
||||
func (handler *Handler) endpointGroupUpdate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
endpointGroupID, err := request.RetrieveNumericRouteVariableValue(r, "id")
|
||||
if err != nil {
|
||||
|
||||
@@ -4,14 +4,15 @@ import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
bolterrors "github.com/portainer/portainer/api/bolt/errors"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// @id EndpointAssociationDelete
|
||||
@@ -26,7 +27,7 @@ import (
|
||||
// @failure 400 "Invalid request"
|
||||
// @failure 404 "Endpoint not found"
|
||||
// @failure 500 "Server error"
|
||||
// @router /api/endpoints/:id/association [put]
|
||||
// @router /api/endpoints/{id}/association [put]
|
||||
func (handler *Handler) endpointAssociationDelete(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
endpointID, err := request.RetrieveNumericRouteVariableValue(r, "id")
|
||||
if err != nil {
|
||||
@@ -84,5 +85,5 @@ func (handler *Handler) updateEdgeKey(edgeKey string) (string, error) {
|
||||
|
||||
func getPort(url string) string {
|
||||
items := strings.Split(url, ":")
|
||||
return items[len(items) - 1]
|
||||
return items[len(items)-1]
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ import (
|
||||
// @param tagsPartialMatch query bool false "If true, will return endpoint which has one of tagIds, if false (or missing) will return only endpoints that has all the tags"
|
||||
// @param endpointIds query []int false "will return only these endpoints"
|
||||
// @success 200 {array} portainer.Endpoint "Endpoints"
|
||||
// @failure 500 Server error
|
||||
// @failure 500 "Server error"
|
||||
// @router /endpoints [get]
|
||||
func (handler *Handler) endpointList(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
start, _ := request.RetrieveNumericQueryParameter(r, "start", true)
|
||||
|
||||
@@ -49,7 +49,7 @@ func (payload *endpointSettingsUpdatePayload) Validate(r *http.Request) error {
|
||||
// @failure 400 "Invalid request"
|
||||
// @failure 404 "Endpoint not found"
|
||||
// @failure 500 "Server error"
|
||||
// @router /api/endpoints/:id/settings [put]
|
||||
// @router /api/endpoints/{id}/settings [put]
|
||||
func (handler *Handler) endpointSettingsUpdate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
endpointID, err := request.RetrieveNumericRouteVariableValue(r, "id")
|
||||
if err != nil {
|
||||
|
||||
@@ -72,14 +72,14 @@ type Handler struct {
|
||||
}
|
||||
|
||||
// @title PortainerCE API
|
||||
// @version 2.6.3
|
||||
// @version 2.9.0
|
||||
// @description.markdown api-description.md
|
||||
// @termsOfService
|
||||
|
||||
// @contact.email info@portainer.io
|
||||
|
||||
// @license.name
|
||||
// @license.url
|
||||
// @license.name zlib
|
||||
// @license.url https://github.com/portainer/portainer/blob/develop/LICENSE
|
||||
|
||||
// @host
|
||||
// @BasePath /api
|
||||
@@ -121,8 +121,6 @@ type Handler struct {
|
||||
// @tag.description Manage Portainer settings
|
||||
// @tag.name status
|
||||
// @tag.description Information about the Portainer instance
|
||||
// @tag.name stacks
|
||||
// @tag.description Manage Docker stacks
|
||||
// @tag.name users
|
||||
// @tag.description Manage users
|
||||
// @tag.name tags
|
||||
|
||||
@@ -17,14 +17,15 @@ import (
|
||||
// @security jwt
|
||||
// @accept json
|
||||
// @produce json
|
||||
// @param release query string true "The name of the release/application to uninstall"
|
||||
// @param id path int true "Endpoint identifier"
|
||||
// @param release path string true "The name of the release/application to uninstall"
|
||||
// @param namespace query string true "An optional namespace"
|
||||
// @success 204 "Success"
|
||||
// @failure 400 "Invalid endpoint id or bad request"
|
||||
// @failure 401 "Unauthorized"
|
||||
// @failure 404 "Endpoint or ServiceAccount not found"
|
||||
// @failure 500 "Server error or helm error"
|
||||
// @router /endpoints/:id/kubernetes/helm/{release} [delete]
|
||||
// @router /endpoints/{id}/kubernetes/helm/{release} [delete]
|
||||
func (handler *Handler) helmDelete(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
release, err := request.RetrieveRouteVariableValue(r, "release")
|
||||
if err != nil {
|
||||
|
||||
@@ -36,12 +36,13 @@ var errChartNameInvalid = errors.New("invalid chart name. " +
|
||||
// @security jwt
|
||||
// @accept json
|
||||
// @produce json
|
||||
// @param id path int true "Endpoint identifier"
|
||||
// @param payload body installChartPayload true "Chart details"
|
||||
// @success 201 {object} release.Release "Created"
|
||||
// @failure 401 "Unauthorized"
|
||||
// @failure 404 "Endpoint or ServiceAccount not found"
|
||||
// @failure 500 "Server error"
|
||||
// @router /endpoints/:id/kubernetes/helm [post]
|
||||
// @router /endpoints/{id}/kubernetes/helm [post]
|
||||
func (handler *Handler) helmInstall(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
var payload installChartPayload
|
||||
err := request.DecodeAndValidateJSONPayload(r, &payload)
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
// @security jwt
|
||||
// @accept json
|
||||
// @produce json
|
||||
// @param id path int true "Endpoint identifier"
|
||||
// @param namespace query string true "specify an optional namespace"
|
||||
// @param filter query string true "specify an optional filter"
|
||||
// @param selector query string true "specify an optional selector"
|
||||
@@ -25,7 +26,7 @@ import (
|
||||
// @failure 401 "Unauthorized"
|
||||
// @failure 404 "Endpoint or ServiceAccount not found"
|
||||
// @failure 500 "Server error"
|
||||
// @router /endpoints/:id/kubernetes/helm [get]
|
||||
// @router /endpoints/{id}/kubernetes/helm [get]
|
||||
func (handler *Handler) helmList(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
clusterAccess, httperr := handler.getHelmClusterAccess(r)
|
||||
if httperr != nil {
|
||||
|
||||
@@ -19,7 +19,7 @@ import (
|
||||
// @tags helm_chart
|
||||
// @param repo query string true "Helm repository URL"
|
||||
// @param chart query string true "Chart name"
|
||||
// @param command path string false "chart/values/readme"
|
||||
// @param command path string true "chart/values/readme"
|
||||
// @security jwt
|
||||
// @accept json
|
||||
// @produce text/plain
|
||||
|
||||
@@ -36,12 +36,13 @@ func (p *addHelmRepoUrlPayload) Validate(_ *http.Request) error {
|
||||
// @security jwt
|
||||
// @accept json
|
||||
// @produce json
|
||||
// @param id path int true "Endpoint identifier"
|
||||
// @param payload body addHelmRepoUrlPayload true "Helm Repository"
|
||||
// @success 200 {object} portainer.HelmUserRepository "Success"
|
||||
// @failure 400 "Invalid request"
|
||||
// @failure 403 "Permission denied"
|
||||
// @failure 500 "Server error"
|
||||
// @router /endpoints/:id/kubernetes/helm/repositories [post]
|
||||
// @router /endpoints/{id}/kubernetes/helm/repositories [post]
|
||||
func (handler *Handler) userCreateHelmRepo(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
tokenData, err := security.RetrieveTokenData(r)
|
||||
if err != nil {
|
||||
@@ -99,7 +100,7 @@ func (handler *Handler) userCreateHelmRepo(w http.ResponseWriter, r *http.Reques
|
||||
// @failure 400 "Invalid request"
|
||||
// @failure 403 "Permission denied"
|
||||
// @failure 500 "Server error"
|
||||
// @router /endpoints/:id/kubernetes/helm/repositories [get]
|
||||
// @router /endpoints/{id}/kubernetes/helm/repositories [get]
|
||||
func (handler *Handler) userGetHelmRepos(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
tokenData, err := security.RetrieveTokenData(r)
|
||||
if err != nil {
|
||||
|
||||
@@ -36,7 +36,7 @@ func (handler *Handler) cleanUp(stack *portainer.Stack, doCleanUp *bool) error {
|
||||
// @description **Access policy**: restricted
|
||||
// @tags stacks
|
||||
// @security jwt
|
||||
// @accept json, multipart/form-data
|
||||
// @accept json,multipart/form-data
|
||||
// @produce json
|
||||
// @param type query int true "Stack deployment type. Possible values: 1 (Swarm stack) or 2 (Compose stack)." Enums(1,2)
|
||||
// @param method query string true "Stack deployment method. Possible values: file, string or repository." Enums(string, file, repository)
|
||||
|
||||
@@ -53,7 +53,7 @@ func (payload *stackGitRedployPayload) Validate(r *http.Request) error {
|
||||
// @failure 403 "Permission denied"
|
||||
// @failure 404 "Not found"
|
||||
// @failure 500 "Server error"
|
||||
// @router /stacks/:id/git/redeploy [put]
|
||||
// @router /stacks/{id}/git/redeploy [put]
|
||||
func (handler *Handler) stackGitRedeploy(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
stackID, err := request.RetrieveNumericRouteVariableValue(r, "id")
|
||||
if err != nil {
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
// @description **Access policy**: administrator
|
||||
// @tags teams
|
||||
// @security jwt
|
||||
// @param id path string true "Team Id"
|
||||
// @success 204 "Success"
|
||||
// @failure 400 "Invalid request"
|
||||
// @failure 403 "Permission denied"
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
// @tags team_memberships
|
||||
// @security jwt
|
||||
// @produce json
|
||||
// @param id path string true "Team Id"
|
||||
// @success 200 {array} portainer.TeamMembership "Success"
|
||||
// @failure 400 "Invalid request"
|
||||
// @failure 403 "Permission denied"
|
||||
|
||||
@@ -1455,7 +1455,7 @@ type (
|
||||
|
||||
const (
|
||||
// APIVersion is the version number of the Portainer API
|
||||
APIVersion = "2.6.3"
|
||||
APIVersion = "2.9.0"
|
||||
// DBVersion is the version number of the Portainer database
|
||||
DBVersion = 32
|
||||
// ComposeSyntaxMaxVersion is a maximum supported version of the docker compose syntax
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<span class="small text-muted">
|
||||
<p>
|
||||
<i class="fa fa-exclamation-circle orange-icon" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
This is a first version for Helm charts, for more information see this <a href="#">blog post.</a>
|
||||
This is a first version for Helm charts, for more information see this <a href="https://www.portainer.io/blog/portainer-now-with-helm-support" target="_blank">blog post</a>.
|
||||
</p>
|
||||
</span>
|
||||
</information-panel>
|
||||
|
||||
@@ -56,6 +56,7 @@ class KubernetesApplicationConverter {
|
||||
const containers = data.spec.template ? _.without(_.concat(data.spec.template.spec.containers, data.spec.template.spec.initContainers), undefined) : data.spec.containers;
|
||||
res.Id = data.metadata.uid;
|
||||
res.Name = data.metadata.name;
|
||||
res.Metadata = data.metadata;
|
||||
|
||||
if (data.metadata.labels) {
|
||||
const { labels } = data.metadata;
|
||||
|
||||
@@ -449,8 +449,8 @@ class KubernetesApplicationHelper {
|
||||
// filter out all the applications that are managed by helm
|
||||
// to identify the helm managed applications, we need to check if the applications pod labels include
|
||||
// `app.kubernetes.io/instance` and `app.kubernetes.io/managed-by` = `helm`
|
||||
const helmManagedApps = applications.filter((app) =>
|
||||
app.Pods.flatMap((pod) => pod.Labels).some((label) => label && label[PodKubernetesInstanceLabel] && label[PodManagedByLabel] === 'Helm')
|
||||
const helmManagedApps = applications.filter(
|
||||
(app) => app.Metadata.labels && app.Metadata.labels[PodKubernetesInstanceLabel] && app.Metadata.labels[PodManagedByLabel] === 'Helm'
|
||||
);
|
||||
|
||||
// groups the helm managed applications by helm release name
|
||||
@@ -467,15 +467,12 @@ class KubernetesApplicationHelper {
|
||||
const namespacedHelmReleases = {};
|
||||
helmManagedApps.forEach((app) => {
|
||||
const namespace = app.ResourcePool;
|
||||
const labels = app.Pods.filter((p) => p.Labels).map((p) => p.Labels[PodKubernetesInstanceLabel]);
|
||||
const uniqueLabels = [...new Set(labels)];
|
||||
uniqueLabels.forEach((instanceStr) => {
|
||||
if (namespacedHelmReleases[namespace]) {
|
||||
namespacedHelmReleases[namespace][instanceStr] = [...(namespacedHelmReleases[namespace][instanceStr] || []), app];
|
||||
} else {
|
||||
namespacedHelmReleases[namespace] = { [instanceStr]: [app] };
|
||||
}
|
||||
});
|
||||
const instanceLabel = app.Metadata.labels[PodKubernetesInstanceLabel];
|
||||
if (namespacedHelmReleases[namespace]) {
|
||||
namespacedHelmReleases[namespace][instanceLabel] = [...(namespacedHelmReleases[namespace][instanceLabel] || []), app];
|
||||
} else {
|
||||
namespacedHelmReleases[namespace] = { [instanceLabel]: [app] };
|
||||
}
|
||||
});
|
||||
|
||||
// `helmAppsEntriesList` object structure:
|
||||
@@ -511,5 +508,25 @@ class KubernetesApplicationHelper {
|
||||
|
||||
return helmAppsList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get nested applications -
|
||||
* @param {KubernetesApplication[]} applications Application list
|
||||
* @returns {Object} { helmApplications: [app1, app2, ...], nonHelmApplications: [app3, app4, ...] }
|
||||
*/
|
||||
static getNestedApplications(applications) {
|
||||
const helmApplications = KubernetesApplicationHelper.getHelmApplications(applications);
|
||||
|
||||
// filter out helm managed applications
|
||||
const helmAppNames = [...new Set(helmApplications.map((hma) => hma.Name))]; // distinct helm app names
|
||||
const nonHelmApplications = applications.filter((app) => {
|
||||
if (app.Metadata.labels) {
|
||||
return !helmAppNames.includes(app.Metadata.labels[PodKubernetesInstanceLabel]);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
return { helmApplications, nonHelmApplications };
|
||||
}
|
||||
}
|
||||
export default KubernetesApplicationHelper;
|
||||
|
||||
@@ -16,6 +16,7 @@ const _KubernetesApplication = Object.freeze({
|
||||
CreationDate: 0,
|
||||
Pods: [],
|
||||
Containers: [],
|
||||
Metadata: {},
|
||||
Limits: {},
|
||||
ServiceType: '',
|
||||
ServiceId: '',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import angular from 'angular';
|
||||
import _ from 'lodash-es';
|
||||
import KubernetesStackHelper from 'Kubernetes/helpers/stackHelper';
|
||||
import KubernetesApplicationHelper, { PodKubernetesInstanceLabel } from 'Kubernetes/helpers/application';
|
||||
import KubernetesApplicationHelper from 'Kubernetes/helpers/application';
|
||||
import KubernetesConfigurationHelper from 'Kubernetes/helpers/configurationHelper';
|
||||
import { KubernetesApplicationTypes } from 'Kubernetes/models/application/models';
|
||||
|
||||
@@ -109,18 +109,9 @@ class KubernetesApplicationsController {
|
||||
try {
|
||||
const [applications, configurations] = await Promise.all([this.KubernetesApplicationService.get(), this.KubernetesConfigurationService.get()]);
|
||||
const configuredApplications = KubernetesConfigurationHelper.getApplicationConfigurations(applications, configurations);
|
||||
const helmApplications = KubernetesApplicationHelper.getHelmApplications(configuredApplications);
|
||||
const { helmApplications, nonHelmApplications } = KubernetesApplicationHelper.getNestedApplications(configuredApplications);
|
||||
|
||||
// filter out multi-chart helm managed applications
|
||||
const helmAppNames = [...new Set(helmApplications.map((hma) => hma.Name))]; // distinct helm app names
|
||||
const nonHelmApps = configuredApplications.filter(
|
||||
(app) =>
|
||||
!app.Pods.flatMap((pod) => pod.Labels) // flatten pod labels
|
||||
.filter((label) => label) // filter out empty labels
|
||||
.some((label) => helmAppNames.includes(label[PodKubernetesInstanceLabel])) // check if label key is in helmAppNames
|
||||
);
|
||||
|
||||
this.state.applications = [...nonHelmApps, ...helmApplications];
|
||||
this.state.applications = [...helmApplications, ...nonHelmApplications];
|
||||
this.state.stacks = KubernetesStackHelper.stacksFromApplications(applications);
|
||||
this.state.ports = KubernetesApplicationHelper.portMappingsFromApplications(applications);
|
||||
} catch (err) {
|
||||
|
||||
@@ -20,7 +20,7 @@ class KubernetesSummaryController {
|
||||
$scope.$watch(
|
||||
'$ctrl.formValues',
|
||||
(formValues) => {
|
||||
this.state.resources = this.generateResourceSummaryList(formValues);
|
||||
this.state.resources = this.generateResourceSummaryList(angular.copy(formValues));
|
||||
},
|
||||
true
|
||||
);
|
||||
|
||||
@@ -130,8 +130,8 @@
|
||||
<div class="form-group">
|
||||
<span class="col-sm-12 text-muted small">
|
||||
<p>
|
||||
Allows you to create an environment that can be registered with an Edge agent. The Edge agent will initiate the communications with the Portainer instance. All the
|
||||
required information on how to connect an Edge agent to this environment will be available after environment creation.
|
||||
Allows you to create an environment that can be registered with an Edge agent. The Edge agent will initiate the communications with the Portainer instance. All
|
||||
the required information on how to connect an Edge agent to this environment will be available after environment creation.
|
||||
</p>
|
||||
<p> You can read more about the Edge agent in the userguide available <a href="https://downloads.portainer.io/edge_agent_guide.pdf">here.</a> </p>
|
||||
</span>
|
||||
@@ -309,7 +309,7 @@
|
||||
class="form-control"
|
||||
name="endpoint_url"
|
||||
ng-model="formValues.URL"
|
||||
placeholder="e.g. 10.0.0.10:9000 or portainer.mydomain.com"
|
||||
placeholder="e.g. 10.0.0.10:9443 or portainer.mydomain.com"
|
||||
required
|
||||
data-cy="endpointCreate-portainerServerUrlInput"
|
||||
/>
|
||||
|
||||
@@ -6,6 +6,7 @@ VOLUME /data
|
||||
WORKDIR /
|
||||
|
||||
EXPOSE 9000
|
||||
EXPOSE 9443
|
||||
EXPOSE 8000
|
||||
|
||||
ENTRYPOINT ["/portainer"]
|
||||
|
||||
@@ -6,6 +6,7 @@ VOLUME /data
|
||||
WORKDIR /
|
||||
|
||||
EXPOSE 9000
|
||||
EXPOSE 9443
|
||||
EXPOSE 8000
|
||||
|
||||
ENTRYPOINT ["/portainer"]
|
||||
|
||||
@@ -2,6 +2,7 @@ FROM ubuntu:20.04
|
||||
|
||||
# Expose port for the Portainer UI and Edge server
|
||||
EXPOSE 9000
|
||||
EXPOSE 9443
|
||||
EXPOSE 8000
|
||||
|
||||
WORKDIR /src/portainer
|
||||
|
||||
@@ -19,6 +19,7 @@ USER ContainerAdministrator
|
||||
COPY dist /
|
||||
|
||||
EXPOSE 9000
|
||||
EXPOSE 9443
|
||||
EXPOSE 8000
|
||||
|
||||
ENTRYPOINT ["/portainer.exe"]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Portainer.io",
|
||||
"name": "portainer",
|
||||
"homepage": "http://portainer.io",
|
||||
"version": "2.6.3",
|
||||
"version": "2.9.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:portainer/portainer.git"
|
||||
@@ -20,8 +20,8 @@
|
||||
"build": "grunt clean:all && grunt build",
|
||||
"build:server": "grunt clean:server && grunt build:server",
|
||||
"build:client": "grunt clean:client && grunt build:client",
|
||||
"prebuild:docs": "(! command -v swag &> /dev/null) && go get -u github.com/swaggo/swag/cmd/swag",
|
||||
"build:docs": "cd api && swag init -g ./http/handler/handler.go --markdownFiles ./",
|
||||
"prebuild:docs": "go install github.com/swaggo/swag/cmd/swag@v1.7.1",
|
||||
"build:docs": "cd api && swag init -g ./http/handler/handler.go --parseDependency --parseInternal --parseDepth 2 --markdownFiles ./",
|
||||
"clean": "grunt clean:all",
|
||||
"start": "grunt start",
|
||||
"start:clean": "grunt clean:all && grunt start",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"projectId": "cgz5j5",
|
||||
"baseUrl": "http://localhost:9000",
|
||||
"baseUrl": "https://localhost:9443",
|
||||
"video": false,
|
||||
"experimentalNetworkStubbing": true
|
||||
}
|
||||
|
||||
@@ -29,8 +29,8 @@ describe('startContainerController', function () {
|
||||
Ports: [
|
||||
{
|
||||
IP: '0.0.0.0',
|
||||
PrivatePort: 9000,
|
||||
PublicPort: 9000,
|
||||
PrivatePort: 9443,
|
||||
PublicPort: 9443,
|
||||
Type: 'tcp',
|
||||
},
|
||||
],
|
||||
@@ -46,11 +46,11 @@ describe('startContainerController', function () {
|
||||
var expectedBody = {
|
||||
name: 'container-name',
|
||||
ExposedPorts: {
|
||||
'9000/tcp': {},
|
||||
'9443/tcp': {},
|
||||
},
|
||||
HostConfig: {
|
||||
PortBindings: {
|
||||
'9000/tcp': [
|
||||
'9443/tcp': [
|
||||
{
|
||||
HostPort: '9999',
|
||||
HostIp: '10.20.10.15',
|
||||
@@ -76,7 +76,7 @@ describe('startContainerController', function () {
|
||||
{
|
||||
ip: '10.20.10.15',
|
||||
extPort: '9999',
|
||||
intPort: '9000',
|
||||
intPort: '9443',
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user