Compare commits

...

10 Commits

Author SHA1 Message Date
Felix Han
dde3eb384f fix(name): moved copy object to upper level of the function 2021-09-17 17:16:34 +12:00
Felix Han
ed0c14d51c fix(name): fixed namespace creation issue when a registry attached. 2021-09-17 14:50:15 +12:00
zees-dev
a098e24cca using new app metadata property to distinguish helm apps (#5624) 2021-09-16 16:09:33 +12:00
zees-dev
5d8c23e3a6 helm templates blog post link fix (#5625) 2021-09-16 10:00:18 +12:00
zees-dev
52f9320952 fix webpack dev server (#5630) 2021-09-15 17:54:44 +12:00
zees-dev
e3f7561ced portainer version updates (#5612) 2021-09-14 10:20:26 +12:00
zees-dev
c7760b7d48 - setting port 9443 as primary (#5610)
- updated markdown files
- updated dockerfiles
- updated test files
- updated webpack
2021-09-14 09:46:59 +12:00
Yi Chen
1633eceed5 fix(swagger) Fix openapi issues (#5123)
* * fix api version
* fix license info
* fix error response schema
* fix other typos & mistakes

* * remove unused tag

* * fix helm issues
2021-09-13 15:42:53 +12:00
Matt Hook
e437a3b570 fix(docs): fix yarn build docs broken for helm (#5606)
* fix(docs): fix yarn build docs broken for helm

* ensure correct version of swag is used

* remove line that prevented swag from updating
2021-09-13 14:14:07 +12:00
Dmitry Salakhov
396a921b12 fix(stacks): allow root based compose file paths (#5564) 2021-09-13 11:11:22 +12:00
58 changed files with 162 additions and 103 deletions

View File

@@ -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:

View File

@@ -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

View File

@@ -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/>.

View File

@@ -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
}

View File

@@ -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`})
}

View File

@@ -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"

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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()

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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")

View File

@@ -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")

View File

@@ -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")

View File

@@ -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 {

View File

@@ -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")

View File

@@ -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")

View File

@@ -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")

View File

@@ -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")

View File

@@ -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")

View File

@@ -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)

View File

@@ -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")

View File

@@ -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")

View File

@@ -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")

View File

@@ -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()

View File

@@ -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")

View File

@@ -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

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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]
}

View File

@@ -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)

View File

@@ -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 {

View File

@@ -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

View File

@@ -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 {

View File

@@ -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)

View File

@@ -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 {

View File

@@ -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

View File

@@ -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 {

View File

@@ -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)

View File

@@ -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 {

View File

@@ -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"

View File

@@ -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"

View File

@@ -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

View File

@@ -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>

View File

@@ -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;

View File

@@ -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;

View File

@@ -16,6 +16,7 @@ const _KubernetesApplication = Object.freeze({
CreationDate: 0,
Pods: [],
Containers: [],
Metadata: {},
Limits: {},
ServiceType: '',
ServiceId: '',

View File

@@ -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) {

View File

@@ -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
);

View File

@@ -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"
/>

View File

@@ -6,6 +6,7 @@ VOLUME /data
WORKDIR /
EXPOSE 9000
EXPOSE 9443
EXPOSE 8000
ENTRYPOINT ["/portainer"]

View File

@@ -6,6 +6,7 @@ VOLUME /data
WORKDIR /
EXPOSE 9000
EXPOSE 9443
EXPOSE 8000
ENTRYPOINT ["/portainer"]

View File

@@ -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

View File

@@ -19,6 +19,7 @@ USER ContainerAdministrator
COPY dist /
EXPOSE 9000
EXPOSE 9443
EXPOSE 8000
ENTRYPOINT ["/portainer.exe"]

View File

@@ -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",

View File

@@ -1,6 +1,6 @@
{
"projectId": "cgz5j5",
"baseUrl": "http://localhost:9000",
"baseUrl": "https://localhost:9443",
"video": false,
"experimentalNetworkStubbing": true
}

View File

@@ -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',
},
];