Compare commits

..

2 Commits

262 changed files with 6231 additions and 10967 deletions

View File

@@ -5,8 +5,7 @@
"@babel/preset-env",
{
"modules": false,
"useBuiltIns": "entry",
"corejs": "2"
"useBuiltIns": "entry"
}
]
]

View File

@@ -48,8 +48,6 @@ You can see how [here](https://documentation.portainer.io/archive/1.23.2/faq/#ho
- Platform (windows/linux):
- Command used to start Portainer (`docker run -p 9000:9000 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
**Additional context**
Add any other context about the problem here.

View File

@@ -6,15 +6,10 @@ labels: ''
assignees: ''
---
Before you start, we need a little bit more information from you:
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
<!--
You can find more information about Portainer support framework policy here: https://old.portainer.io/2019/04/portainer-support-policy/
You can find more information about Portainer support framework policy here: https://www.portainer.io/2019/04/portainer-support-policy/
Do you need help or have a question? Come chat with us on Slack http://portainer.slack.com/

View File

@@ -1,19 +0,0 @@
name: Automatic Rebase
on:
issue_comment:
types: [created]
jobs:
rebase:
name: Rebase
if: github.event.issue.pull_request != '' && contains(github.event.comment.body, '/rebase')
runs-on: ubuntu-latest
steps:
- name: Checkout the latest code
uses: actions/checkout@v2
with:
token: ${{ secrets.GITHUB_TOKEN }}
fetch-depth: 0 # otherwise, you will fail to push refs to dest repo
- name: Automatic Rebase
uses: cirrus-actions/rebase@1.4
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

3
.gitignore vendored
View File

@@ -8,6 +8,3 @@ api/cmd/portainer/portainer*
**/.vscode/tasks.json
.eslintcache
__debug_bin
api/docs

View File

@@ -1,19 +0,0 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Launch",
"type": "go",
"request": "launch",
"mode": "debug",
"program": "${workspaceRoot}/api/cmd/portainer/main.go",
"cwd": "${workspaceRoot}",
"env": {},
"showLog": true,
"args": ["--data", "${env:HOME}/portainer-data", "--assets", "${workspaceRoot}/dist"]
}
]
}

View File

@@ -21,11 +21,11 @@
"description": "Dummy Angularjs Component",
"body": [
"import angular from 'angular';",
"import controller from './${TM_FILENAME_BASE}Controller'",
"import ${TM_FILENAME_BASE/(.*)/${1:/capitalize}/}Controller from './${TM_FILENAME_BASE}Controller'",
"",
"angular.module('portainer.${TM_DIRECTORY/.*\\/app\\/([^\\/]*)(\\/.*)?$/$1/}').component('$TM_FILENAME_BASE', {",
" templateUrl: './$TM_FILENAME_BASE.html',",
" controller,",
" controller: ${TM_FILENAME_BASE/(.*)/${1:/capitalize}/}Controller,",
"});",
""
]
@@ -44,6 +44,25 @@
],
"description": "Dummy ES6+ controller"
},
"Model": {
"scope": "javascript",
"prefix": "mymodel",
"description": "Dummy ES6+ model",
"body": [
"/**",
" * $1 Model",
" */",
"const _$1 = Object.freeze({",
" $0",
"});",
"",
"export class $1 {",
" constructor() {",
" Object.assign(this, JSON.parse(JSON.stringify(_$1)));",
" }",
"}"
]
},
"Service": {
"scope": "javascript",
"prefix": "myservice",
@@ -139,29 +158,5 @@
"export default $1;",
"angular.module('portainer.${TM_DIRECTORY/.*\\/app\\/([^\\/]*)(\\/.*)?$/$1/}').service('$1', $1);"
]
},
"swagger-api-doc": {
"prefix": "swapi",
"scope": "go",
"description": "Snippet for a api doc",
"body": [
"// @id ",
"// @summary ",
"// @description ",
"// @description **Access policy**: ",
"// @tags ",
"// @security jwt",
"// @accept json",
"// @produce json",
"// @param id path int true \"identifier\"",
"// @param body body Object true \"details\"",
"// @success 200 {object} portainer. \"Success\"",
"// @success 204 \"Success\"",
"// @failure 400 \"Invalid request\"",
"// @failure 403 \"Permission denied\"",
"// @failure 404 \" not found\"",
"// @failure 500 \"Server error\"",
"// @router /{id} [get]"
]
}
}

View File

@@ -94,36 +94,3 @@ $ yarn start
Portainer can now be accessed at <http://localhost:9000>.
Find more detailed steps at <https://documentation.portainer.io/contributing/instructions/>.
## Adding api docs
When adding a new resource (or a route handler), we should add a new tag to api/http/handler/handler.go#L136 like this:
```
// @tag.name <Name of resource>
// @tag.description a short description
```
When adding a new route to an existing handler use the following as a template (you can use `swapi` snippet if you're using vscode):
```
// @id
// @summary
// @description
// @description **Access policy**:
// @tags
// @security jwt
// @accept json
// @produce json
// @param id path int true "identifier"
// @param body body Object true "details"
// @success 200 {object} portainer. "Success"
// @success 204 "Success"
// @failure 400 "Invalid request"
// @failure 403 "Permission denied"
// @failure 404 " not found"
// @failure 500 "Server error"
// @router /{id} [get]
```
explanation about each line can be found (here)[https://github.com/swaggo/swag#api-operation]

View File

@@ -1,53 +0,0 @@
Portainer API is an HTTP API served by Portainer. It is used by the Portainer UI and everything you can do with the UI can be done using the HTTP API.
Examples are available at https://gist.github.com/deviantony/77026d402366b4b43fa5918d41bc42f8
You can find out more about Portainer at [http://portainer.io](http://portainer.io) and get some support on [Slack](http://portainer.io/slack/).
# Authentication
Most of the API endpoints require to be authenticated as well as some level of authorization to be used.
Portainer API uses JSON Web Token to manage authentication and thus requires you to provide a token in the **Authorization** header of each request
with the **Bearer** authentication mechanism.
Example:
```
Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJhZG1pbiIsInJvbGUiOjEsImV4cCI6MTQ5OTM3NjE1NH0.NJ6vE8FY1WG6jsRQzfMqeatJ4vh2TWAeeYfDhP71YEE
```
# Security
Each API endpoint has an associated access policy, it is documented in the description of each endpoint.
Different access policies are available:
- Public access
- Authenticated access
- Restricted access
- Administrator access
### Public access
No authentication is required to access the endpoints with this access policy.
### Authenticated access
Authentication is required to access the endpoints with this access policy.
### Restricted access
Authentication is required to access the endpoints with this access policy.
Extra-checks might be added to ensure access to the resource is granted. Returned data might also be filtered.
### Administrator access
Authentication as well as an administrator role are required to access the endpoints with this access policy.
# Execute Docker requests
Portainer **DO NOT** expose specific endpoints to manage your Docker resources (create a container, remove a volume, etc...).
Instead, it acts as a reverse-proxy to the Docker HTTP API. This means that you can execute Docker requests **via** the Portainer HTTP API.
To do so, you can use the `/endpoints/{id}/docker` Portainer API endpoint (which is not documented below due to Swagger limitations). This endpoint has a restricted access policy so you still need to be authenticated to be able to query this endpoint. Any query on this endpoint will be proxied to the Docker API of the associated endpoint (requests and responses objects are the same as documented in the Docker API).
**NOTE**: You can find more information on how to query the Docker API in the [Docker official documentation](https://docs.docker.com/engine/api/v1.30/) as well as in [this Portainer example](https://gist.github.com/deviantony/77026d402366b4b43fa5918d41bc42f8).

View File

@@ -6,7 +6,7 @@ import (
"time"
"github.com/boltdb/bolt"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/bolt/customtemplate"
"github.com/portainer/portainer/api/bolt/dockerhub"
"github.com/portainer/portainer/api/bolt/edgegroup"
@@ -69,14 +69,6 @@ type Store struct {
WebhookService *webhook.Service
}
func (store *Store) edition() portainer.SoftwareEdition {
edition, err := store.VersionService.Edition()
if err == errors.ErrObjectNotFound {
edition = portainer.PortainerCE
}
return edition
}
// NewStore initializes a new Store and the associated services
func NewStore(storePath string, fileService portainer.FileService) (*Store, error) {
store := &Store{
@@ -124,14 +116,6 @@ func (store *Store) IsNew() bool {
return store.isNew
}
// CheckCurrentEdition checks if current edition is community edition
func (store *Store) CheckCurrentEdition() error {
if store.edition() != portainer.PortainerCE {
return errors.ErrWrongDBEdition
}
return nil
}
// MigrateData automatically migrate the data based on the DBVersion.
// This process is only triggered on an existing database, not if the database was just created.
func (store *Store) MigrateData() error {

View File

@@ -4,5 +4,4 @@ import "errors"
var (
ErrObjectNotFound = errors.New("Object not found inside the database")
ErrWrongDBEdition = errors.New("The Portainer database is set for Portainer Business Edition, please follow the instructions in our documention to downgrade it: https://documentation.portainer.io/v2.0-be/downgrade/be-to-ce/")
)

View File

@@ -1,40 +0,0 @@
package migrator
import (
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/bolt/errors"
"github.com/portainer/portainer/api/internal/stackutils"
)
func (m *Migrator) updateStackResourceControlToDB27() error {
resourceControls, err := m.resourceControlService.ResourceControls()
if err != nil {
return err
}
for _, resource := range resourceControls {
if resource.Type != portainer.StackResourceControl {
continue
}
stackName := resource.ResourceID
stack, err := m.stackService.StackByName(stackName)
if err != nil {
if err == errors.ErrObjectNotFound {
continue
}
return err
}
resource.ResourceID = stackutils.ResourceControlID(stack.EndpointID, stack.Name)
err = m.resourceControlService.UpdateResourceControl(resource.ID, &resource)
if err != nil {
return err
}
}
return nil
}

View File

@@ -2,7 +2,7 @@ package migrator
import (
"github.com/boltdb/bolt"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/bolt/endpoint"
"github.com/portainer/portainer/api/bolt/endpointgroup"
"github.com/portainer/portainer/api/bolt/endpointrelation"
@@ -350,13 +350,5 @@ func (m *Migrator) Migrate() error {
}
}
// Portainer 2.2.0
if m.currentDBVersion < 27 {
err := m.updateStackResourceControlToDB27()
if err != nil {
return err
}
}
return m.versionService.StoreDBVersion(portainer.DBVersion)
}

View File

@@ -1,9 +1,7 @@
package team
import (
"strings"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/bolt/errors"
"github.com/portainer/portainer/api/bolt/internal"
@@ -60,7 +58,7 @@ func (service *Service) TeamByName(name string) (*portainer.Team, error) {
return err
}
if strings.EqualFold(t.Name, name) {
if t.Name == name {
team = &t
break
}

View File

@@ -1,11 +1,10 @@
package user
import (
"strings"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/bolt/errors"
"github.com/portainer/portainer/api/bolt/internal"
"strings"
"github.com/boltdb/bolt"
)
@@ -62,7 +61,7 @@ func (service *Service) UserByUsername(username string) (*portainer.User, error)
return err
}
if strings.EqualFold(u.Username, username) {
if strings.ToLower(u.Username) == username {
user = &u
break
}

View File

@@ -4,7 +4,6 @@ import (
"strconv"
"github.com/boltdb/bolt"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/bolt/errors"
"github.com/portainer/portainer/api/bolt/internal"
)
@@ -14,7 +13,6 @@ const (
BucketName = "version"
versionKey = "DB_VERSION"
instanceKey = "INSTANCE_ID"
editionKey = "EDITION"
)
// Service represents a service to manage stored versions.
@@ -58,21 +56,6 @@ func (service *Service) DBVersion() (int, error) {
return strconv.Atoi(string(data))
}
// Edition retrieves the stored portainer edition.
func (service *Service) Edition() (portainer.SoftwareEdition, error) {
editionData, err := service.getKey(editionKey)
if err != nil {
return 0, err
}
edition, err := strconv.Atoi(string(editionData))
if err != nil {
return 0, err
}
return portainer.SoftwareEdition(edition), nil
}
// StoreDBVersion store the database version.
func (service *Service) StoreDBVersion(version int) error {
return service.db.Update(func(tx *bolt.Tx) error {
@@ -116,36 +99,3 @@ func (service *Service) StoreInstanceID(ID string) error {
return bucket.Put([]byte(instanceKey), data)
})
}
func (service *Service) getKey(key string) ([]byte, error) {
var data []byte
err := service.db.View(func(tx *bolt.Tx) error {
bucket := tx.Bucket([]byte(BucketName))
value := bucket.Get([]byte(key))
if value == nil {
return errors.ErrObjectNotFound
}
data = make([]byte, len(value))
copy(data, value)
return nil
})
if err != nil {
return nil, err
}
return data, nil
}
func (service *Service) setKey(key string, value string) error {
return service.db.Update(func(tx *bolt.Tx) error {
bucket := tx.Bucket([]byte(BucketName))
data := []byte(value)
return bucket.Put([]byte(key), data)
})
}

View File

@@ -12,7 +12,6 @@ import (
"github.com/portainer/portainer/api/cli"
"github.com/portainer/portainer/api/crypto"
"github.com/portainer/portainer/api/docker"
"github.com/portainer/portainer/api/exec"
"github.com/portainer/portainer/api/filesystem"
"github.com/portainer/portainer/api/git"
@@ -33,12 +32,12 @@ func initCLI() *portainer.CLIFlags {
var cliService portainer.CLIService = &cli.Service{}
flags, err := cliService.ParseFlags(portainer.APIVersion)
if err != nil {
log.Fatalf("failed parsing flags: %v", err)
log.Fatal(err)
}
err = cliService.ValidateFlags(flags)
if err != nil {
log.Fatalf("failed validating flags:%v", err)
log.Fatal(err)
}
return flags
}
@@ -46,7 +45,7 @@ func initCLI() *portainer.CLIFlags {
func initFileService(dataStorePath string) portainer.FileService {
fileService, err := filesystem.NewService(dataStorePath, "")
if err != nil {
log.Fatalf("failed creating file service: %v", err)
log.Fatal(err)
}
return fileService
}
@@ -54,22 +53,22 @@ func initFileService(dataStorePath string) portainer.FileService {
func initDataStore(dataStorePath string, fileService portainer.FileService) portainer.DataStore {
store, err := bolt.NewStore(dataStorePath, fileService)
if err != nil {
log.Fatalf("failed creating data store: %v", err)
log.Fatal(err)
}
err = store.Open()
if err != nil {
log.Fatalf("failed opening store: %v", err)
log.Fatal(err)
}
err = store.Init()
if err != nil {
log.Fatalf("failed initializing data store: %v", err)
log.Fatal(err)
}
err = store.MigrateData()
if err != nil {
log.Fatalf("failed migration: %v", err)
log.Fatal(err)
}
return store
}
@@ -211,7 +210,7 @@ func generateAndStoreKeyPair(fileService portainer.FileService, signatureService
func initKeyPair(fileService portainer.FileService, signatureService portainer.DigitalSignatureService) error {
existingKeyPair, err := fileService.KeyPairFilesExist()
if err != nil {
log.Fatalf("failed checking for existing key pair: %v", err)
log.Fatal(err)
}
if existingKeyPair {
@@ -359,7 +358,7 @@ func terminateIfNoAdminCreated(dataStore portainer.DataStore) {
users, err := dataStore.User().UsersByRole(portainer.AdministratorRole)
if err != nil {
log.Fatalf("failed getting admin user: %v", err)
log.Fatal(err)
}
if len(users) == 0 {
@@ -376,13 +375,9 @@ func main() {
dataStore := initDataStore(*flags.Data, fileService)
defer dataStore.Close()
if err := dataStore.CheckCurrentEdition(); err != nil {
log.Fatal(err)
}
jwtService, err := initJWTService(dataStore)
if err != nil {
log.Fatalf("failed initializing JWT service: %v", err)
log.Fatal(err)
}
ldapService := initLDAPService()
@@ -397,14 +392,14 @@ func main() {
err = initKeyPair(fileService, digitalSignatureService)
if err != nil {
log.Fatalf("failed initializing key pai: %v", err)
log.Fatal(err)
}
reverseTunnelService := chisel.NewService(dataStore)
instanceID, err := dataStore.Version().InstanceID()
if err != nil {
log.Fatalf("failed getting instance id: %v", err)
log.Fatal(err)
}
dockerClientFactory := initDockerClientFactory(digitalSignatureService, reverseTunnelService)
@@ -412,13 +407,13 @@ func main() {
snapshotService, err := initSnapshotService(*flags.SnapshotInterval, dataStore, dockerClientFactory, kubernetesClientFactory)
if err != nil {
log.Fatalf("failed initializing snapshot service: %v", err)
log.Fatal(err)
}
snapshotService.Start()
swarmStackManager, err := initSwarmStackManager(*flags.Assets, *flags.Data, digitalSignatureService, fileService, reverseTunnelService)
if err != nil {
log.Fatalf("failed initializing swarm stack manager: %v", err)
log.Fatal(err)
}
kubernetesTokenCacheManager := kubeproxy.NewTokenCacheManager()
proxyManager := proxy.NewManager(dataStore, digitalSignatureService, reverseTunnelService, dockerClientFactory, kubernetesClientFactory, kubernetesTokenCacheManager)
@@ -430,31 +425,31 @@ func main() {
if dataStore.IsNew() {
err = updateSettingsFromFlags(dataStore, flags)
if err != nil {
log.Fatalf("failed updating settings from flags: %v", err)
log.Fatal(err)
}
}
err = loadEdgeJobsFromDatabase(dataStore, reverseTunnelService)
if err != nil {
log.Fatalf("failed loading edge jobs from database: %v", err)
log.Fatal(err)
}
applicationStatus := initStatus(flags)
err = initEndpoint(flags, dataStore, snapshotService)
if err != nil {
log.Fatalf("failed initializing endpoint: %v", err)
log.Fatal(err)
}
adminPasswordHash := ""
if *flags.AdminPasswordFile != "" {
content, err := fileService.GetFileContent(*flags.AdminPasswordFile)
if err != nil {
log.Fatalf("failed getting admin password file: %v", err)
log.Fatal(err)
}
adminPasswordHash, err = cryptoService.Hash(strings.TrimSuffix(string(content), "\n"))
if err != nil {
log.Fatalf("failed hashing admin password: %v", err)
log.Fatal(err)
}
} else if *flags.AdminPassword != "" {
adminPasswordHash = *flags.AdminPassword
@@ -463,7 +458,7 @@ func main() {
if adminPasswordHash != "" {
users, err := dataStore.User().UsersByRole(portainer.AdministratorRole)
if err != nil {
log.Fatalf("failed getting admin user: %v", err)
log.Fatal(err)
}
if len(users) == 0 {
@@ -475,7 +470,7 @@ func main() {
}
err := dataStore.User().CreateUser(user)
if err != nil {
log.Fatalf("failed creating admin user: %v", err)
log.Fatal(err)
}
} else {
log.Println("Instance already has an administrator user defined. Skipping admin password related flags.")
@@ -486,7 +481,7 @@ func main() {
err = reverseTunnelService.StartTunnelServer(*flags.TunnelAddr, *flags.TunnelPort, snapshotService)
if err != nil {
log.Fatalf("failed starting tunnel server: %v", err)
log.Fatal(err)
}
var server portainer.Server = &http.Server{
@@ -518,6 +513,6 @@ func main() {
log.Printf("Starting Portainer %s on %s", portainer.APIVersion, *flags.Addr)
err = server.Start()
if err != nil {
log.Fatalf("failed starting server: %v", err)
log.Fatal(err)
}
}

View File

@@ -118,7 +118,6 @@ func snapshotNodes(snapshot *portainer.DockerSnapshot, cli *client.Client) error
}
snapshot.TotalCPU = int(nanoCpus / 1e9)
snapshot.TotalMemory = totalMem
snapshot.NodeCount = len(nodes)
return nil
}

View File

@@ -28,7 +28,7 @@ require (
github.com/portainer/libcompose v0.5.3
github.com/portainer/libcrypto v0.0.0-20190723020515-23ebe86ab2c2
github.com/portainer/libhttp v0.0.0-20190806161843-ba068f58be33
github.com/stretchr/testify v1.6.1
github.com/stretchr/testify v1.6.1 // indirect
golang.org/x/crypto v0.0.0-20191128160524-b544559bb6d1
golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933 // indirect
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45

View File

@@ -75,7 +75,6 @@ github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96 h1:cenwrSVm+Z7QLSV/BsnenAOcDXdX4cMv4wP0B/5QbPg=
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e h1:p1yVGRW3nmb85p1Sh1ZJSDm4A4iKLS5QNbvUHMgGu/M=
github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
@@ -142,6 +141,7 @@ github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
@@ -207,8 +207,10 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420 h1:Yu3681ykYHDfLoI6XVjL4JWmkE+3TX9yfIWwRCh1kFM=
github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
@@ -286,6 +288,7 @@ golang.org/x/crypto v0.0.0-20181015023909-0c41d7ab0a0e/go.mod h1:6SG95UA2DQfeDnf
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191128160524-b544559bb6d1 h1:anGSYQpPhQwXlwsu5wmfq0nWkCNaMEMUwAv13Y92hd8=
@@ -309,6 +312,8 @@ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc h1:gkKoSkUmnU6bpS/VhkuO27bzQeSA51uaEfbOW5dNb68=
golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933 h1:e6HwijUxhDe+hPNjZQQn9bA5PW3vNmnN64U2ZW759Lk=
golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -397,18 +402,29 @@ gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
k8s.io/api v0.0.0-20191114100352-16d7abae0d2a h1:86XISgFlG7lPOWj6wYLxd+xqhhVt/WQjS4Tf39rP09s=
k8s.io/api v0.0.0-20191114100352-16d7abae0d2a/go.mod h1:qetVJgs5i8jwdFIdoOZ70ks0ecgU+dYwqZ2uD1srwOU=
k8s.io/api v0.17.2 h1:NF1UFXcKN7/OOv1uxdRz3qfra8AHsPav5M93hlV9+Dc=
k8s.io/api v0.17.2/go.mod h1:BS9fjjLc4CMuqfSO8vgbHPKMt5+SF0ET6u/RVDihTo4=
k8s.io/apimachinery v0.0.0-20191028221656-72ed19daf4bb h1:ZUNsbuPdXWrj0rZziRfCWcFg9ZP31OKkziqCbiphznI=
k8s.io/apimachinery v0.0.0-20191028221656-72ed19daf4bb/go.mod h1:llRdnznGEAqC3DcNm6yEj472xaFVfLM7hnYofMb12tQ=
k8s.io/apimachinery v0.17.2 h1:hwDQQFbdRlpnnsR64Asdi55GyCaIP/3WQpMmbNBeWr4=
k8s.io/apimachinery v0.17.2/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg=
k8s.io/client-go v0.0.0-20191114101535-6c5935290e33 h1:07mhG/2oEoo3N+sHVOo0L9PJ/qvbk3N5n2dj8IWefnQ=
k8s.io/client-go v0.0.0-20191114101535-6c5935290e33/go.mod h1:4L/zQOBkEf4pArQJ+CMk1/5xjA30B5oyWv+Bzb44DOw=
k8s.io/client-go v0.17.2 h1:ndIfkfXEGrNhLIgkr0+qhRguSD3u6DCmonepn1O6NYc=
k8s.io/client-go v0.17.2/go.mod h1:QAzRgsa0C2xl4/eVpeVAZMvikCn8Nm81yqVx3Kk9XYI=
k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
k8s.io/klog v0.4.0 h1:lCJCxf/LIowc2IGS9TPjWDyXY4nOmdGdfcwwDQCOURQ=
k8s.io/klog v0.4.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8=
k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
k8s.io/kube-openapi v0.0.0-20190816220812-743ec37842bf/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
k8s.io/utils v0.0.0-20190801114015-581e00157fb1 h1:+ySTxfHnfzZb9ys375PXNlLhkJPLKgHajBU0N62BDvE=
k8s.io/utils v0.0.0-20190801114015-581e00157fb1/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
k8s.io/utils v0.0.0-20191114184206-e782cd3c129f h1:GiPwtSzdP43eI1hpPCbROQCCIgCuiMMNF8YUVLF3vJo=
k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=

View File

@@ -10,21 +10,18 @@ import (
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api"
bolterrors "github.com/portainer/portainer/api/bolt/errors"
httperrors "github.com/portainer/portainer/api/http/errors"
)
type authenticatePayload struct {
// Username
Username string `example:"admin" validate:"required"`
// Password
Password string `example:"mypassword" validate:"required"`
Username string
Password string
}
type authenticateResponse struct {
// JWT token used to authenticate against the API
JWT string `json:"jwt" example:"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJhZG1pbiIsInJvbGUiOjEsImV4cCI6MTQ5OTM3NjE1NH0.NJ6vE8FY1WG6jsRQzfMqeatJ4vh2TWAeeYfDhP71YEE"`
JWT string `json:"jwt"`
}
func (payload *authenticatePayload) Validate(r *http.Request) error {
@@ -37,18 +34,6 @@ func (payload *authenticatePayload) Validate(r *http.Request) error {
return nil
}
// @id AuthenticateUser
// @summary Authenticate
// @description Use this endpoint to authenticate against Portainer using a username and password.
// @tags auth
// @accept json
// @produce json
// @param body body authenticatePayload true "Credentials used for authentication"
// @success 200 {object} authenticateResponse "Success"
// @failure 400 "Invalid request"
// @failure 422 "Invalid Credentials"
// @failure 500 "Server error"
// @router /auth [post]
func (handler *Handler) authenticate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
var payload authenticatePayload
err := request.DecodeAndValidateJSONPayload(r, &payload)

View File

@@ -8,13 +8,12 @@ import (
"github.com/asaskevich/govalidator"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api"
bolterrors "github.com/portainer/portainer/api/bolt/errors"
httperrors "github.com/portainer/portainer/api/http/errors"
)
type oauthPayload struct {
// OAuth code returned from OAuth Provided
Code string
}
@@ -25,17 +24,6 @@ func (payload *oauthPayload) Validate(r *http.Request) error {
return nil
}
// @id AuthenticateOauth
// @summary Authenticate with OAuth
// @tags auth
// @accept json
// @produce json
// @param body body oauthPayload true "OAuth Credentials used for authentication"
// @success 200 {object} authenticateResponse "Success"
// @failure 400 "Invalid request"
// @failure 422 "Invalid Credentials"
// @failure 500 "Server error"
// @router /auth/oauth/validate [post]
func (handler *Handler) authenticateOAuth(code string, settings *portainer.OAuthSettings) (string, error) {
if code == "" {
return "", errors.New("Invalid OAuth authorization code")

View File

@@ -8,13 +8,7 @@ import (
"github.com/portainer/portainer/api/http/security"
)
// @id Logout
// @summary Logout
// @security jwt
// @tags auth
// @success 204 "Success"
// @failure 500 "Server error"
// @router /auth/logout [post]
// POST request on /logout
func (handler *Handler) logout(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
tokenData, err := security.RetrieveTokenData(r)
if err != nil {

View File

@@ -15,27 +15,6 @@ import (
"github.com/portainer/portainer/api/internal/authorization"
)
// @id CustomTemplateCreate
// @summary Create a custom template
// @description Create a custom template.
// @description **Access policy**: authenticated
// @tags custom_templates
// @security jwt
// @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"
// @param body_repository body customTemplateFromGitRepositoryPayload false "Required when using method=repository"
// @param Title formData string false "Title of the template. required when method is file"
// @param Description formData string false "Description of the template. required when method is file"
// @param Note formData string false "A note that will be displayed in the UI. Supports HTML content"
// @param Platform formData int false "Platform associated to the template (1 - 'linux', 2 - 'windows'). required when method is file" Enums(1,2)
// @param Type formData int false "Type of created stack (1 - swarm, 2 - compose), required when method is file" Enums(1,2)
// @param file formData file false "required when method is file"
// @success 200 {object} portainer.CustomTemplate
// @failure 400 "Invalid request"
// @failure 500 "Server error"
// @router /custom_templates [post]
func (handler *Handler) customTemplateCreate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
method, err := request.RetrieveQueryParameter(r, "method", false)
if err != nil {
@@ -95,21 +74,13 @@ func (handler *Handler) createCustomTemplate(method string, r *http.Request) (*p
}
type customTemplateFromFileContentPayload struct {
// URL of the template's logo
Logo string `example:"https://cloudinovasi.id/assets/img/logos/nginx.png"`
// Title of the template
Title string `example:"Nginx" validate:"required"`
// Description of the template
Description string `example:"High performance web server" validate:"required"`
// A note that will be displayed in the UI. Supports HTML content
Note string `example:"This is my <b>custom</b> template"`
// Platform associated to the template.
// Valid values are: 1 - 'linux', 2 - 'windows'
Platform portainer.CustomTemplatePlatform `example:"1" enums:"1,2" validate:"required"`
// Type of created stack (1 - swarm, 2 - compose)
Type portainer.StackType `example:"1" enums:"1,2" validate:"required"`
// Content of stack file
FileContent string `validate:"required"`
Logo string
Title string
FileContent string
Description string
Note string
Platform portainer.CustomTemplatePlatform
Type portainer.StackType
}
func (payload *customTemplateFromFileContentPayload) Validate(r *http.Request) error {
@@ -161,32 +132,18 @@ func (handler *Handler) createCustomTemplateFromFileContent(r *http.Request) (*p
}
type customTemplateFromGitRepositoryPayload struct {
// URL of the template's logo
Logo string `example:"https://cloudinovasi.id/assets/img/logos/nginx.png"`
// Title of the template
Title string `example:"Nginx" validate:"required"`
// Description of the template
Description string `example:"High performance web server" validate:"required"`
// A note that will be displayed in the UI. Supports HTML content
Note string `example:"This is my <b>custom</b> template"`
// Platform associated to the template.
// Valid values are: 1 - 'linux', 2 - 'windows'
Platform portainer.CustomTemplatePlatform `example:"1" enums:"1,2" validate:"required"`
// Type of created stack (1 - swarm, 2 - compose)
Type portainer.StackType `example:"1" enums:"1,2" validate:"required"`
// URL of a Git repository hosting the Stack file
RepositoryURL string `example:"https://github.com/openfaas/faas" validate:"required"`
// Reference name of a Git repository hosting the Stack file
RepositoryReferenceName string `example:"refs/heads/master"`
// Use basic authentication to clone the Git repository
RepositoryAuthentication bool `example:"true"`
// Username used in basic authentication. Required when RepositoryAuthentication is true.
RepositoryUsername string `example:"myGitUsername"`
// Password used in basic authentication. Required when RepositoryAuthentication is true.
RepositoryPassword string `example:"myGitPassword"`
// Path to the Stack file inside the Git repository
ComposeFilePathInRepository string `example:"docker-compose.yml" default:"docker-compose.yml"`
Logo string
Title string
Description string
Note string
Platform portainer.CustomTemplatePlatform
Type portainer.StackType
RepositoryURL string
RepositoryReferenceName string
RepositoryAuthentication bool
RepositoryUsername string
RepositoryPassword string
ComposeFilePathInRepository string
}
func (payload *customTemplateFromGitRepositoryPayload) Validate(r *http.Request) error {
@@ -294,7 +251,7 @@ func (payload *customTemplateFromFileUploadPayload) Validate(r *http.Request) er
}
payload.Type = templateType
composeFileContent, _, err := request.RetrieveMultiPartFormFile(r, "File")
composeFileContent, _, err := request.RetrieveMultiPartFormFile(r, "file")
if err != nil {
return errors.New("Invalid Compose file. Ensure that the Compose file is uploaded correctly")
}

View File

@@ -13,19 +13,6 @@ import (
"github.com/portainer/portainer/api/http/security"
)
// @id CustomTemplateDelete
// @summary Remove a template
// @description Remove a template.
// @description **Access policy**: authorized
// @tags custom_templates
// @security jwt
// @param id path int true "Template identifier"
// @success 204 "Success"
// @failure 400 "Invalid request"
// @failure 403 "Access denied to resource"
// @failure 404 "Template not found"
// @failure 500 "Server error"
// @router /custom_templates/{id} [delete]
func (handler *Handler) customTemplateDelete(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
customTemplateID, err := request.RetrieveNumericRouteVariableValue(r, "id")
if err != nil {

View File

@@ -7,7 +7,7 @@ import (
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api"
bolterrors "github.com/portainer/portainer/api/bolt/errors"
)
@@ -15,19 +15,7 @@ type fileResponse struct {
FileContent string
}
// @id CustomTemplateFile
// @summary Get Template stack file content.
// @description Retrieve the content of the Stack file for the specified custom template
// @description **Access policy**: authorized
// @tags custom_templates
// @security jwt
// @produce json
// @param id path int true "Template identifier"
// @success 200 {object} fileResponse "Success"
// @failure 400 "Invalid request"
// @failure 404 "Custom template not found"
// @failure 500 "Server error"
// @router /custom_templates/{id}/file [get]
// GET request on /api/custom_templates/:id/file
func (handler *Handler) customTemplateFile(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
customTemplateID, err := request.RetrieveNumericRouteVariableValue(r, "id")
if err != nil {

View File

@@ -7,26 +7,12 @@ import (
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api"
bolterrors "github.com/portainer/portainer/api/bolt/errors"
httperrors "github.com/portainer/portainer/api/http/errors"
"github.com/portainer/portainer/api/http/security"
)
// @id CustomTemplateInspect
// @summary Inspect a custom template
// @description Retrieve details about a template.
// @description **Access policy**: authenticated
// @tags custom_templates
// @security jwt
// @accept json
// @produce json
// @param id path int true "Template identifier"
// @success 200 {object} portainer.CustomTemplate "Success"
// @failure 400 "Invalid request"
// @failure 404 "Template not found"
// @failure 500 "Server error"
// @router /custom_templates/{id} [get]
func (handler *Handler) customTemplateInspect(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
customTemplateID, err := request.RetrieveNumericRouteVariableValue(r, "id")
if err != nil {

View File

@@ -4,28 +4,21 @@ import (
"net/http"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/http/security"
"github.com/portainer/portainer/api/internal/authorization"
)
// @id CustomTemplateList
// @summary List available custom templates
// @description List available custom templates.
// @description **Access policy**: authenticated
// @tags custom_templates
// @security jwt
// @produce json
// @success 200 {array} portainer.CustomTemplate "Success"
// @failure 500 "Server error"
// @router /custom_templates [get]
func (handler *Handler) customTemplateList(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
customTemplates, err := handler.DataStore.CustomTemplate().CustomTemplates()
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve custom templates from the database", err}
}
stackType, _ := request.RetrieveNumericQueryParameter(r, "type", true)
resourceControls, err := handler.DataStore.ResourceControl().ResourceControls()
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve resource controls from the database", err}
@@ -33,6 +26,8 @@ func (handler *Handler) customTemplateList(w http.ResponseWriter, r *http.Reques
customTemplates = authorization.DecorateCustomTemplates(customTemplates, resourceControls)
customTemplates = filterTemplatesByEngineType(customTemplates, portainer.StackType(stackType))
securityContext, err := security.RetrieveRestrictedRequestContext(r)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve info from request context", err}
@@ -54,3 +49,19 @@ func (handler *Handler) customTemplateList(w http.ResponseWriter, r *http.Reques
return response.JSON(w, customTemplates)
}
func filterTemplatesByEngineType(templates []portainer.CustomTemplate, stackType portainer.StackType) []portainer.CustomTemplate {
if stackType == 0 {
return templates
}
filteredTemplates := []portainer.CustomTemplate{}
for _, template := range templates {
if template.Type == stackType {
filteredTemplates = append(filteredTemplates, template)
}
}
return filteredTemplates
}

View File

@@ -17,21 +17,13 @@ import (
)
type customTemplateUpdatePayload struct {
// URL of the template's logo
Logo string `example:"https://cloudinovasi.id/assets/img/logos/nginx.png"`
// Title of the template
Title string `example:"Nginx" validate:"required"`
// Description of the template
Description string `example:"High performance web server" validate:"required"`
// A note that will be displayed in the UI. Supports HTML content
Note string `example:"This is my <b>custom</b> template"`
// Platform associated to the template.
// Valid values are: 1 - 'linux', 2 - 'windows'
Platform portainer.CustomTemplatePlatform `example:"1" enums:"1,2" validate:"required"`
// Type of created stack (1 - swarm, 2 - compose)
Type portainer.StackType `example:"1" enums:"1,2" validate:"required"`
// Content of stack file
FileContent string `validate:"required"`
Logo string
Title string
Description string
Note string
Platform portainer.CustomTemplatePlatform
Type portainer.StackType
FileContent string
}
func (payload *customTemplateUpdatePayload) Validate(r *http.Request) error {
@@ -53,22 +45,6 @@ func (payload *customTemplateUpdatePayload) Validate(r *http.Request) error {
return nil
}
// @id CustomTemplateUpdate
// @summary Update a template
// @description Update a template.
// @description **Access policy**: authenticated
// @tags custom_templates
// @security jwt
// @accept json
// @produce json
// @param id path int true "Template identifier"
// @param body body customTemplateUpdatePayload true "Template details"
// @success 200 {object} portainer.CustomTemplate "Success"
// @failure 400 "Invalid request"
// @failure 403 "Permission denied to access template"
// @failure 404 "Template not found"
// @failure 500 "Server error"
// @router /custom_templates/{id} [put]
func (handler *Handler) customTemplateUpdate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
customTemplateID, err := request.RetrieveNumericRouteVariableValue(r, "id")
if err != nil {

View File

@@ -7,16 +7,7 @@ import (
"github.com/portainer/libhttp/response"
)
// @id DockerHubInspect
// @summary Retrieve DockerHub information
// @description Use this endpoint to retrieve the information used to connect to the DockerHub
// @description **Access policy**: authenticated
// @tags dockerhub
// @security jwt
// @produce json
// @success 200 {object} portainer.DockerHub
// @failure 500 "Server error"
// @router /dockerhub [get]
// GET request on /api/dockerhub
func (handler *Handler) dockerhubInspect(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
dockerhub, err := handler.DataStore.DockerHub().DockerHub()
if err != nil {

View File

@@ -8,16 +8,13 @@ import (
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api"
)
type dockerhubUpdatePayload struct {
// Enable authentication against DockerHub
Authentication bool `validate:"required" example:"false"`
// Username used to authenticate against the DockerHub
Username string `validate:"required" example:"hub_user"`
// Password used to authenticate against the DockerHub
Password string `validate:"required" example:"hub_password"`
Authentication bool
Username string
Password string
}
func (payload *dockerhubUpdatePayload) Validate(r *http.Request) error {
@@ -27,19 +24,7 @@ func (payload *dockerhubUpdatePayload) Validate(r *http.Request) error {
return nil
}
// @id DockerHubUpdate
// @summary Update DockerHub information
// @description Use this endpoint to update the information used to connect to the DockerHub
// @description **Access policy**: administrator
// @tags dockerhub
// @security jwt
// @accept json
// @produce json
// @param body body dockerhubUpdatePayload true "DockerHub information"
// @success 204 "Success"
// @failure 400 "Invalid request"
// @failure 500 "Server error"
// @router /dockerhub [put]
// PUT request on /api/dockerhub
func (handler *Handler) dockerhubUpdate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
var payload dockerhubUpdatePayload
err := request.DecodeAndValidateJSONPayload(r, &payload)

View File

@@ -8,7 +8,7 @@ import (
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api"
)
type edgeGroupCreatePayload struct {
@@ -32,18 +32,6 @@ func (payload *edgeGroupCreatePayload) Validate(r *http.Request) error {
return nil
}
// @id EdgeGroupCreate
// @summary Create an EdgeGroup
// @description
// @tags edge_groups
// @security jwt
// @accept json
// @produce json
// @param body body edgeGroupCreatePayload true "EdgeGroup data"
// @success 200 {object} portainer.EdgeGroup
// @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 {
var payload edgeGroupCreatePayload
err := request.DecodeAndValidateJSONPayload(r, &payload)

View File

@@ -11,18 +11,6 @@ import (
bolterrors "github.com/portainer/portainer/api/bolt/errors"
)
// @id EdgeGroupDelete
// @summary Deletes an EdgeGroup
// @description
// @tags edge_groups
// @security jwt
// @accept json
// @produce json
// @param id path int true "EdgeGroup Id"
// @success 204
// @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 {
edgeGroupID, err := request.RetrieveNumericRouteVariableValue(r, "id")
if err != nil {

View File

@@ -6,22 +6,10 @@ import (
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/bolt/errors"
)
// @id EdgeGroupInspect
// @summary Inspects an EdgeGroup
// @description
// @tags edge_groups
// @security jwt
// @accept json
// @produce json
// @param id path int true "EdgeGroup Id"
// @success 200 {object} portainer.EdgeGroup
// @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 {
edgeGroupID, err := request.RetrieveNumericRouteVariableValue(r, "id")
if err != nil {

View File

@@ -5,7 +5,7 @@ import (
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api"
)
type decoratedEdgeGroup struct {
@@ -13,17 +13,6 @@ type decoratedEdgeGroup struct {
HasEdgeStack bool `json:"HasEdgeStack"`
}
// @id EdgeGroupList
// @summary list EdgeGroups
// @description
// @tags edge_groups
// @security jwt
// @accept json
// @produce json
// @success 200 {array} portainer.EdgeGroup{HasEdgeStack=bool} "EdgeGroups"
// @failure 500
// @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()
if err != nil {

View File

@@ -8,7 +8,7 @@ import (
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api"
bolterrors "github.com/portainer/portainer/api/bolt/errors"
"github.com/portainer/portainer/api/internal/edge"
)
@@ -34,19 +34,6 @@ func (payload *edgeGroupUpdatePayload) Validate(r *http.Request) error {
return nil
}
// @id EgeGroupUpdate
// @summary Updates an EdgeGroup
// @description
// @tags edge_groups
// @security jwt
// @accept json
// @produce json
// @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 500
// @router /edge_groups/{id} [put]
func (handler *Handler) edgeGroupUpdate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
edgeGroupID, err := request.RetrieveNumericRouteVariableValue(r, "id")
if err != nil {

View File

@@ -11,23 +11,10 @@ import (
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api"
)
// @id EdgeJobCreate
// @summary Create an EdgeJob
// @description
// @tags edge_jobs
// @security jwt
// @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"
// @success 200 {object} portainer.EdgeGroup
// @failure 503 Edge compute features are disabled
// @failure 500
// @router /edge_jobs [post]
// POST /api/edge_jobs?method=file|string
func (handler *Handler) edgeJobCreate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
method, err := request.RetrieveQueryParameter(r, "method", false)
if err != nil {

View File

@@ -7,23 +7,10 @@ import (
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api"
bolterrors "github.com/portainer/portainer/api/bolt/errors"
)
// @id EdgeJobDelete
// @summary Delete an EdgeJob
// @description
// @tags edge_jobs
// @security jwt
// @accept json
// @produce json
// @param id path string true "EdgeJob Id"
// @success 204
// @failure 500
// @failure 400
// @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")
if err != nil {

View File

@@ -6,7 +6,7 @@ import (
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api"
bolterrors "github.com/portainer/portainer/api/bolt/errors"
)
@@ -14,19 +14,7 @@ type edgeJobFileResponse struct {
FileContent string `json:"FileContent"`
}
// @id EdgeJobFile
// @summary Fetch a file of an EdgeJob
// @description
// @tags edge_jobs
// @security jwt
// @accept json
// @produce json
// @param id path string true "EdgeJob Id"
// @success 200 {object} edgeJobFileResponse
// @failure 500
// @failure 400
// @failure 503 Edge compute features are disabled
// @router /edge_jobs/{id}/file [get]
// GET request on /api/edge_jobs/:id/file
func (handler *Handler) edgeJobFile(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
edgeJobID, err := request.RetrieveNumericRouteVariableValue(r, "id")
if err != nil {

View File

@@ -6,7 +6,7 @@ import (
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api"
bolterrors "github.com/portainer/portainer/api/bolt/errors"
)
@@ -15,19 +15,6 @@ type edgeJobInspectResponse struct {
Endpoints []portainer.EndpointID
}
// @id EdgeJobInspect
// @summary Inspect an EdgeJob
// @description
// @tags edge_jobs
// @security jwt
// @accept json
// @produce json
// @param id path string true "EdgeJob Id"
// @success 200 {object} portainer.EdgeJob
// @failure 500
// @failure 400
// @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")
if err != nil {

View File

@@ -7,18 +7,6 @@ import (
"github.com/portainer/libhttp/response"
)
// @id EdgeJobList
// @summary Fetch EdgeJobs list
// @description
// @tags edge_jobs
// @security jwt
// @accept json
// @produce json
// @success 200 {array} portainer.EdgeJob
// @failure 500
// @failure 400
// @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 {
edgeJobs, err := handler.DataStore.EdgeJob().EdgeJobs()

View File

@@ -7,24 +7,11 @@ import (
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api"
bolterrors "github.com/portainer/portainer/api/bolt/errors"
)
// @id EdgeJobTasksClear
// @summary Clear the log for a specifc task on an EdgeJob
// @description
// @tags edge_jobs
// @security jwt
// @accept json
// @produce json
// @param id path string true "EdgeJob Id"
// @param taskID path string true "Task Id"
// @success 204
// @failure 500
// @failure 400
// @failure 503 Edge compute features are disabled
// @router /edge_jobs/{id}/tasks/{taskID}/logs [delete]
// DELETE request on /api/edge_jobs/:id/tasks/:taskID/logs
func (handler *Handler) edgeJobTasksClear(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
edgeJobID, err := request.RetrieveNumericRouteVariableValue(r, "id")
if err != nil {

View File

@@ -6,24 +6,11 @@ import (
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api"
bolterrors "github.com/portainer/portainer/api/bolt/errors"
)
// @id EdgeJobTasksCollect
// @summary Collect the log for a specifc task on an EdgeJob
// @description
// @tags edge_jobs
// @security jwt
// @accept json
// @produce json
// @param id path string true "EdgeJob Id"
// @param taskID path string true "Task Id"
// @success 204
// @failure 500
// @failure 400
// @failure 503 Edge compute features are disabled
// @router /edge_jobs/{id}/tasks/{taskID}/logs [post]
// POST request on /api/edge_jobs/:id/tasks/:taskID/logs
func (handler *Handler) edgeJobTasksCollect(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
edgeJobID, err := request.RetrieveNumericRouteVariableValue(r, "id")
if err != nil {

View File

@@ -13,20 +13,7 @@ type fileResponse struct {
FileContent string `json:"FileContent"`
}
// @id EdgeJobTaskLogsInspect
// @summary Fetch the log for a specifc task on an EdgeJob
// @description
// @tags edge_jobs
// @security jwt
// @accept json
// @produce json
// @param id path string true "EdgeJob Id"
// @param taskID path string true "Task Id"
// @success 200 {object} fileResponse
// @failure 500
// @failure 400
// @failure 503 Edge compute features are disabled
// @router /edge_jobs/{id}/tasks/{taskID}/logs [get]
// GET request on /api/edge_jobs/:id/tasks/:taskID/logs
func (handler *Handler) edgeJobTaskLogsInspect(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
edgeJobID, err := request.RetrieveNumericRouteVariableValue(r, "id")
if err != nil {

View File

@@ -7,7 +7,7 @@ import (
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api"
bolterrors "github.com/portainer/portainer/api/bolt/errors"
)
@@ -17,19 +17,7 @@ type taskContainer struct {
LogsStatus portainer.EdgeJobLogsStatus `json:"LogsStatus"`
}
// @id EdgeJobTasksList
// @summary Fetch the list of tasks on an EdgeJob
// @description
// @tags edge_jobs
// @security jwt
// @accept json
// @produce json
// @param id path string true "EdgeJob Id"
// @success 200 {array} taskContainer
// @failure 500
// @failure 400
// @failure 503 Edge compute features are disabled
// @router /edge_jobs/{id}/tasks [get]
// GET request on /api/edge_jobs/:id/tasks
func (handler *Handler) edgeJobTasksList(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
edgeJobID, err := request.RetrieveNumericRouteVariableValue(r, "id")
if err != nil {

View File

@@ -9,7 +9,7 @@ import (
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api"
bolterrors "github.com/portainer/portainer/api/bolt/errors"
)
@@ -28,20 +28,6 @@ func (payload *edgeJobUpdatePayload) Validate(r *http.Request) error {
return nil
}
// @id EdgeJobUpdate
// @summary Update an EdgeJob
// @description
// @tags edge_jobs
// @security jwt
// @accept json
// @produce json
// @param id path string true "EdgeJob Id"
// @param body body edgeJobUpdatePayload true "EdgeGroup data"
// @success 200 {object} portainer.EdgeJob
// @failure 500
// @failure 400
// @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")
if err != nil {

View File

@@ -11,26 +11,12 @@ import (
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/filesystem"
"github.com/portainer/portainer/api/internal/edge"
)
// @id EdgeStackCreate
// @summary Create an EdgeStack
// @description
// @tags edge_stacks
// @security jwt
// @accept json
// @produce json
// @param method query string true "Creation Method" Enums(file,string,repository)
// @param body_string body swarmStackFromFileContentPayload true "Required when using method=string"
// @param body_file body swarmStackFromFileUploadPayload true "Required when using method=file"
// @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
// @router /edge_stacks [post]
// POST request on /api/endpoint_groups
func (handler *Handler) edgeStackCreate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
method, err := request.RetrieveQueryParameter(r, "method", false)
if err != nil {
@@ -89,12 +75,9 @@ func (handler *Handler) createSwarmStack(method string, r *http.Request) (*porta
}
type swarmStackFromFileContentPayload struct {
// Name of the stack
Name string `example:"myStack" validate:"required"`
// Content of the Stack file
StackFileContent string `example:"version: 3\n services:\n web:\n image:nginx" validate:"required"`
// List of identifiers of EdgeGroups
EdgeGroups []portainer.EdgeGroupID `example:"1"`
Name string
StackFileContent string
EdgeGroups []portainer.EdgeGroupID
}
func (payload *swarmStackFromFileContentPayload) Validate(r *http.Request) error {
@@ -149,22 +132,14 @@ func (handler *Handler) createSwarmStackFromFileContent(r *http.Request) (*porta
}
type swarmStackFromGitRepositoryPayload struct {
// Name of the stack
Name string `example:"myStack" validate:"required"`
// URL of a Git repository hosting the Stack file
RepositoryURL string `example:"https://github.com/openfaas/faas" validate:"required"`
// Reference name of a Git repository hosting the Stack file
RepositoryReferenceName string `example:"refs/heads/master"`
// Use basic authentication to clone the Git repository
RepositoryAuthentication bool `example:"true"`
// Username used in basic authentication. Required when RepositoryAuthentication is true.
RepositoryUsername string `example:"myGitUsername"`
// Password used in basic authentication. Required when RepositoryAuthentication is true.
RepositoryPassword string `example:"myGitPassword"`
// Path to the Stack file inside the Git repository
ComposeFilePathInRepository string `example:"docker-compose.yml" default:"docker-compose.yml"`
// List of identifiers of EdgeGroups
EdgeGroups []portainer.EdgeGroupID `example:"1"`
Name string
RepositoryURL string
RepositoryReferenceName string
RepositoryAuthentication bool
RepositoryUsername string
RepositoryPassword string
ComposeFilePathInRepository string
EdgeGroups []portainer.EdgeGroupID
}
func (payload *swarmStackFromGitRepositoryPayload) Validate(r *http.Request) error {

View File

@@ -6,24 +6,11 @@ import (
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/bolt/errors"
"github.com/portainer/portainer/api/internal/edge"
)
// @id EdgeStackDelete
// @summary Delete an EdgeStack
// @description
// @tags edge_stacks
// @security jwt
// @accept json
// @produce json
// @param id path string true "EdgeStack Id"
// @success 204
// @failure 500
// @failure 400
// @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")
if err != nil {

View File

@@ -7,7 +7,7 @@ import (
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/bolt/errors"
)
@@ -15,19 +15,7 @@ type stackFileResponse struct {
StackFileContent string `json:"StackFileContent"`
}
// @id EdgeStackFile
// @summary Fetches the stack file for an EdgeStack
// @description
// @tags edge_stacks
// @security jwt
// @accept json
// @produce json
// @param id path string true "EdgeStack Id"
// @success 200 {object} stackFileResponse
// @failure 500
// @failure 400
// @failure 503 Edge compute features are disabled
// @router /edge_stacks/{id}/file [get]
// GET request on /api/edge_stacks/:id/file
func (handler *Handler) edgeStackFile(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
stackID, err := request.RetrieveNumericRouteVariableValue(r, "id")
if err != nil {

View File

@@ -6,23 +6,10 @@ import (
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/bolt/errors"
)
// @id EdgeStackInspect
// @summary Inspect an EdgeStack
// @description
// @tags edge_stacks
// @security jwt
// @accept json
// @produce json
// @param id path string true "EdgeStack Id"
// @success 200 {object} portainer.EdgeStack
// @failure 500
// @failure 400
// @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")
if err != nil {

View File

@@ -7,18 +7,6 @@ import (
"github.com/portainer/libhttp/response"
)
// @id EdgeStackList
// @summary Fetches the list of EdgeStacks
// @description
// @tags edge_stacks
// @security jwt
// @accept json
// @produce json
// @success 200 {array} portainer.EdgeStack
// @failure 500
// @failure 400
// @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()
if err != nil {

View File

@@ -8,7 +8,7 @@ import (
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api"
bolterrors "github.com/portainer/portainer/api/bolt/errors"
)
@@ -31,19 +31,6 @@ func (payload *updateStatusPayload) Validate(r *http.Request) error {
return nil
}
// @id EdgeStackStatusUpdate
// @summary Update an EdgeStack status
// @description Authorized only if the request is done by an Edge Endpoint
// @tags edge_stacks
// @accept json
// @produce json
// @param id path string true "EdgeStack Id"
// @success 200 {object} portainer.EdgeStack
// @failure 500
// @failure 400
// @failure 404
// @failure 403
// @router /edge_stacks/{id}/status [put]
func (handler *Handler) edgeStackStatusUpdate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
stackID, err := request.RetrieveNumericRouteVariableValue(r, "id")
if err != nil {

View File

@@ -9,7 +9,7 @@ import (
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api"
bolterrors "github.com/portainer/portainer/api/bolt/errors"
"github.com/portainer/portainer/api/internal/edge"
)
@@ -31,20 +31,6 @@ func (payload *updateEdgeStackPayload) Validate(r *http.Request) error {
return nil
}
// @id EdgeStackUpdate
// @summary Update an EdgeStack
// @description
// @tags edge_stacks
// @security jwt
// @accept json
// @produce json
// @param id path string true "EdgeStack Id"
// @param body body updateEdgeStackPayload true "EdgeStack data"
// @success 200 {object} portainer.EdgeStack
// @failure 500
// @failure 400
// @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")
if err != nil {

View File

@@ -6,7 +6,7 @@ import (
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/http/client"
)
@@ -15,16 +15,7 @@ type templateFileFormat struct {
Templates []portainer.Template `json:"templates"`
}
// @id EdgeTemplateList
// @summary Fetches the list of Edge Templates
// @description
// @tags edge_templates
// @security jwt
// @accept json
// @produce json
// @success 200 {array} portainer.Template
// @failure 500
// @router /edge_templates [get]
// GET request on /api/edgetemplates
func (handler *Handler) edgeTemplateList(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
settings, err := handler.DataStore.Settings().Settings()
if err != nil {

View File

@@ -7,7 +7,7 @@ import (
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api"
bolterrors "github.com/portainer/portainer/api/bolt/errors"
)
@@ -19,18 +19,7 @@ func (payload *logsPayload) Validate(r *http.Request) error {
return nil
}
// endpointEdgeJobsLogs
// @summary Inspect an EdgeJob Log
// @description
// @tags edge, endpoints
// @accept json
// @produce json
// @param id path string true "Endpoint Id"
// @param jobID path string true "Job Id"
// @success 200
// @failure 500
// @failure 400
// @router /endpoints/{id}/edge/jobs/{jobID}/logs [post]
// POST request on api/endpoints/:id/edge/jobs/:jobID/logs
func (handler *Handler) endpointEdgeJobsLogs(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
endpointID, err := request.RetrieveNumericRouteVariableValue(r, "id")
if err != nil {

View File

@@ -17,18 +17,7 @@ type configResponse struct {
Name string
}
// @summary Inspect an Edge Stack for an Endpoint
// @description
// @tags edge, endpoints, edge_stacks
// @accept json
// @produce json
// @param id path string true "Endpoint Id"
// @param stackID path string true "EdgeStack Id"
// @success 200 {object} configResponse
// @failure 500
// @failure 400
// @failure 404
// @router /endpoints/{id}/edge/stacks/{stackId} [get]
// GET request on api/endpoints/:id/edge/stacks/:stackId
func (handler *Handler) endpointEdgeStackInspect(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
endpointID, err := request.RetrieveNumericRouteVariableValue(r, "id")
if err != nil {

View File

@@ -8,18 +8,14 @@ import (
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api"
)
type endpointGroupCreatePayload struct {
// Endpoint group name
Name string `validate:"required" example:"my-endpoint-group"`
// Endpoint group description
Description string `example:"description"`
// List of endpoint identifiers that will be part of this group
AssociatedEndpoints []portainer.EndpointID `example:"1,3"`
// List of tag identifiers to which this endpoint group is associated
TagIDs []portainer.TagID `example:"1,2"`
Name string
Description string
AssociatedEndpoints []portainer.EndpointID
TagIDs []portainer.TagID
}
func (payload *endpointGroupCreatePayload) Validate(r *http.Request) error {
@@ -32,18 +28,7 @@ func (payload *endpointGroupCreatePayload) Validate(r *http.Request) error {
return nil
}
// @summary Create an Endpoint Group
// @description Create a new endpoint group.
// @description **Access policy**: administrator
// @tags endpoint_groups
// @security jwt
// @accept json
// @produce json
// @param body body endpointGroupCreatePayload true "Endpoint Group details"
// @success 200 {object} portainer.EndpointGroup "Success"
// @failure 400 "Invalid request"
// @failure 500 "Server error"
// @router /endpoint_groups [post]
// POST request on /api/endpoint_groups
func (handler *Handler) endpointGroupCreate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
var payload endpointGroupCreatePayload
err := request.DecodeAndValidateJSONPayload(r, &payload)

View File

@@ -7,24 +7,11 @@ import (
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api"
bolterrors "github.com/portainer/portainer/api/bolt/errors"
)
// @id EndpointGroupDelete
// @summary Remove an endpoint group
// @description Remove an endpoint group.
// @description **Access policy**: administrator
// @tags endpoint_groups
// @security jwt
// @accept json
// @produce json
// @param id path int true "EndpointGroup identifier"
// @success 204 "Success"
// @failure 400 "Invalid request"
// @failure 404 "EndpointGroup not found"
// @failure 500 "Server error"
// @router /endpoint_groups/{id} [delete]
// DELETE request on /api/endpoint_groups/:id
func (handler *Handler) endpointGroupDelete(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
endpointGroupID, err := request.RetrieveNumericRouteVariableValue(r, "id")
if err != nil {

View File

@@ -6,23 +6,11 @@ import (
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/bolt/errors"
)
// @id EndpointGroupAddEndpoint
// @summary Add an endpoint to an endpoint group
// @description Add an endpoint to an endpoint group
// @description **Access policy**: administrator
// @tags endpoint_groups
// @security jwt
// @param id path int true "EndpointGroup identifier"
// @param endpointId path int true "Endpoint identifier"
// @success 204 "Success"
// @failure 400 "Invalid request"
// @failure 404 "EndpointGroup not found"
// @failure 500 "Server error"
// @router /endpoint_groups/{id}/endpoints/{endpointId} [put]
// PUT request on /api/endpoint_groups/:id/endpoints/:endpointId
func (handler *Handler) endpointGroupAddEndpoint(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
endpointGroupID, err := request.RetrieveNumericRouteVariableValue(r, "id")
if err != nil {

View File

@@ -6,22 +6,11 @@ import (
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/bolt/errors"
)
// @id EndpointGroupDeleteEndpoint
// @summary Removes endpoint from an endpoint group
// @description **Access policy**: administrator
// @tags endpoint_groups
// @security jwt
// @param id path int true "EndpointGroup identifier"
// @param endpointId path int true "Endpoint identifier"
// @success 204 "Success"
// @failure 400 "Invalid request"
// @failure 404 "EndpointGroup not found"
// @failure 500 "Server error"
// @router /endpoint_groups/{id}/endpoints/{endpointId} [delete]
// DELETE request on /api/endpoint_groups/:id/endpoints/:endpointId
func (handler *Handler) endpointGroupDeleteEndpoint(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
endpointGroupID, err := request.RetrieveNumericRouteVariableValue(r, "id")
if err != nil {

View File

@@ -6,23 +6,11 @@ import (
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/bolt/errors"
)
// @summary Inspect an Endpoint group
// @description Retrieve details abont an endpoint group.
// @description **Access policy**: administrator
// @tags endpoint_groups
// @security jwt
// @accept json
// @produce json
// @param id path int true "Endpoint group identifier"
// @success 200 {object} portainer.EndpointGroup "Success"
// @failure 400 "Invalid request"
// @failure 404 "EndpointGroup not found"
// @failure 500 "Server error"
// @router /endpoint_groups/:id [get]
// GET request on /api/endpoint_groups/:id
func (handler *Handler) endpointGroupInspect(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
endpointGroupID, err := request.RetrieveNumericRouteVariableValue(r, "id")
if err != nil {

View File

@@ -8,18 +8,7 @@ import (
"github.com/portainer/portainer/api/http/security"
)
// @id EndpointGroupList
// @summary List Endpoint groups
// @description List all endpoint groups based on the current user authorizations. Will
// @description return all endpoint groups if using an administrator account otherwise it will
// @description only return authorized endpoint groups.
// @description **Access policy**: restricted
// @tags endpoint_groups
// @security jwt
// @produce json
// @success 200 {array} portainer.EndpointGroup "Endpoint group"
// @failure 500 "Server error"
// @router /endpoint_groups [get]
// GET request on /api/endpoint_groups
func (handler *Handler) endpointGroupList(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
endpointGroups, err := handler.DataStore.EndpointGroup().EndpointGroups()
if err != nil {

View File

@@ -7,18 +7,15 @@ import (
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/bolt/errors"
"github.com/portainer/portainer/api/internal/tag"
)
type endpointGroupUpdatePayload struct {
// Endpoint group name
Name string `example:"my-endpoint-group"`
// Endpoint group description
Description string `example:"description"`
// List of tag identifiers associated to the endpoint group
TagIDs []portainer.TagID `example:"3,4"`
Name string
Description string
TagIDs []portainer.TagID
UserAccessPolicies portainer.UserAccessPolicies
TeamAccessPolicies portainer.TeamAccessPolicies
}
@@ -27,21 +24,7 @@ func (payload *endpointGroupUpdatePayload) Validate(r *http.Request) error {
return nil
}
// @id EndpointGroupUpdate
// @summary Update an endpoint group
// @description Update an endpoint group.
// @description **Access policy**: administrator
// @tags endpoint_groups
// @security jwt
// @accept json
// @produce json
// @param id path int true "EndpointGroup identifier"
// @param body body endpointGroupUpdatePayload true "EndpointGroup details"
// @success 200 {object} portainer.EndpointGroup "Success"
// @failure 400 "Invalid request"
// @failure 404 "EndpointGroup not found"
// @failure 500 "Server error"
// @router /endpoint_groups/:id [put]
// PUT request on /api/endpoint_groups/:id
func (handler *Handler) endpointGroupUpdate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
endpointGroupID, err := request.RetrieveNumericRouteVariableValue(r, "id")
if err != nil {

View File

@@ -147,34 +147,7 @@ func (payload *endpointCreatePayload) Validate(r *http.Request) error {
return nil
}
// @id EndpointCreate
// @summary Create a new endpoint
// @description Create a new endpoint that will be used to manage an environment.
// @description **Access policy**: administrator
// @tags endpoints
// @security jwt
// @accept multipart/form-data
// @produce json
// @param Name formData string true "Name that will be used to identify this endpoint (example: my-endpoint)"
// @param EndpointType formData integer true "Environment type. Value must be one of: 1 (Local Docker environment), 2 (Agent environment), 3 (Azure environment), 4 (Edge agent environment) or 5 (Local Kubernetes Environment" Enum(1,2,3,4,5)
// @param URL formData string false "URL or IP address of a Docker host (example: docker.mydomain.tld:2375). Defaults to local if not specified (Linux: /var/run/docker.sock, Windows: //./pipe/docker_engine)"
// @param PublicURL formData string false "URL or IP address where exposed containers will be reachable. Defaults to URL if not specified (example: docker.mydomain.tld:2375)"
// @param GroupID formData int false "Endpoint group identifier. If not specified will default to 1 (unassigned)."
// @param TLS formData bool false "Require TLS to connect against this endpoint"
// @param TLSSkipVerify formData bool false "Skip server verification when using TLS"
// @param TLSSkipClientVerify formData bool false "Skip client verification when using TLS"
// @param TLSCACertFile formData file false "TLS CA certificate file"
// @param TLSCertFile formData file false "TLS client certificate file"
// @param TLSKeyFile formData file false "TLS client key file"
// @param AzureApplicationID formData string false "Azure application ID. Required if endpoint type is set to 3"
// @param AzureTenantID formData string false "Azure tenant ID. Required if endpoint type is set to 3"
// @param AzureAuthenticationKey formData string false "Azure authentication key. Required if endpoint type is set to 3"
// @param TagIDs formData []int false "List of tag identifiers to which this endpoint is associated"
// @param EdgeCheckinInterval formData int false "The check in interval for edge agent (in seconds)"
// @success 200 {object} portainer.Endpoint "Success"
// @failure 400 "Invalid request"
// @failure 500 "Server error"
// @router /endpoints [post]
// POST request on /api/endpoints
func (handler *Handler) endpointCreate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
payload := &endpointCreatePayload{}
err := payload.Validate(r)

View File

@@ -7,22 +7,11 @@ import (
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/bolt/errors"
)
// @id EndpointDelete
// @summary Remove an endpoint
// @description Remove an endpoint.
// @description **Access policy**: administrator
// @tags endpoints
// @security jwt
// @param id path int true "Endpoint identifier"
// @success 204 "Success"
// @failure 400 "Invalid request"
// @failure 404 "Endpoint not found"
// @failure 500 "Server error"
// @router /endpoints/{id} [delete]
// DELETE request on /api/endpoints/:id
func (handler *Handler) endpointDelete(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
endpointID, err := request.RetrieveNumericRouteVariableValue(r, "id")
if err != nil {

View File

@@ -10,7 +10,7 @@ import (
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api"
bolterrors "github.com/portainer/portainer/api/bolt/errors"
)
@@ -29,6 +29,7 @@ func (payload *endpointExtensionAddPayload) Validate(r *http.Request) error {
return nil
}
// POST request on /api/endpoints/:id/extensions
func (handler *Handler) endpointExtensionAdd(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
endpointID, err := request.RetrieveNumericRouteVariableValue(r, "id")
if err != nil {

View File

@@ -8,10 +8,11 @@ import (
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/bolt/errors"
)
// DELETE request on /api/endpoints/:id/extensions/:extensionType
func (handler *Handler) endpointExtensionRemove(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
endpointID, err := request.RetrieveNumericRouteVariableValue(r, "id")
if err != nil {

View File

@@ -10,19 +10,7 @@ import (
"github.com/portainer/portainer/api/bolt/errors"
)
// @id EndpointInspect
// @summary Inspect an endpoint
// @description Retrieve details about an endpoint.
// @description **Access policy**: restricted
// @tags endpoints
// @security jwt
// @produce json
// @param id path int true "Endpoint identifier"
// @success 200 {object} portainer.Endpoint "Success"
// @failure 400 "Invalid request"
// @failure 404 "Endpoint not found"
// @failure 500 "Server error"
// @router /endpoints/{id} [get]
// GET request on /api/endpoints/:id
func (handler *Handler) endpointInspect(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
endpointID, err := request.RetrieveNumericRouteVariableValue(r, "id")
if err != nil {

View File

@@ -13,26 +13,7 @@ import (
"github.com/portainer/portainer/api/http/security"
)
// @id EndpointList
// @summary List endpoints
// @description List all endpoints based on the current user authorizations. Will
// @description return all endpoints if using an administrator account otherwise it will
// @description only return authorized endpoints.
// @description **Access policy**: restricted
// @tags endpoints
// @security jwt
// @produce json
// @param start query int false "Start searching from"
// @param search query string false "Search query"
// @param groupId query int false "List endpoints of this group"
// @param limit query int false "Limit results to this value"
// @param type query int false "List endpoints of this type"
// @param tagIds query []int false "search endpoints with these tags (depends on tagsPartialMatch)"
// @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
// @router /endpoints [get]
// GET request on /api/endpoints?(start=<start>)&(limit=<limit>)&(search=<search>)&(groupId=<groupId)
func (handler *Handler) endpointList(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
start, _ := request.RetrieveNumericQueryParameter(r, "start", true)
if start != 0 {

View File

@@ -11,43 +11,21 @@ import (
)
type endpointSettingsUpdatePayload struct {
// Whether non-administrator should be able to use bind mounts when creating containers
AllowBindMountsForRegularUsers *bool `json:"allowBindMountsForRegularUsers" example:"false"`
// Whether non-administrator should be able to use privileged mode when creating containers
AllowPrivilegedModeForRegularUsers *bool `json:"allowPrivilegedModeForRegularUsers" example:"false"`
// Whether non-administrator should be able to browse volumes
AllowVolumeBrowserForRegularUsers *bool `json:"allowVolumeBrowserForRegularUsers" example:"true"`
// Whether non-administrator should be able to use the host pid
AllowHostNamespaceForRegularUsers *bool `json:"allowHostNamespaceForRegularUsers" example:"true"`
// Whether non-administrator should be able to use device mapping
AllowDeviceMappingForRegularUsers *bool `json:"allowDeviceMappingForRegularUsers" example:"true"`
// Whether non-administrator should be able to manage stacks
AllowStackManagementForRegularUsers *bool `json:"allowStackManagementForRegularUsers" example:"true"`
// Whether non-administrator should be able to use container capabilities
AllowContainerCapabilitiesForRegularUsers *bool `json:"allowContainerCapabilitiesForRegularUsers" example:"true"`
// Whether host management features are enabled
EnableHostManagementFeatures *bool `json:"enableHostManagementFeatures" example:"true"`
AllowBindMountsForRegularUsers *bool `json:"allowBindMountsForRegularUsers"`
AllowPrivilegedModeForRegularUsers *bool `json:"allowPrivilegedModeForRegularUsers"`
AllowVolumeBrowserForRegularUsers *bool `json:"allowVolumeBrowserForRegularUsers"`
AllowHostNamespaceForRegularUsers *bool `json:"allowHostNamespaceForRegularUsers"`
AllowDeviceMappingForRegularUsers *bool `json:"allowDeviceMappingForRegularUsers"`
AllowStackManagementForRegularUsers *bool `json:"allowStackManagementForRegularUsers"`
AllowContainerCapabilitiesForRegularUsers *bool `json:"allowContainerCapabilitiesForRegularUsers"`
EnableHostManagementFeatures *bool `json:"enableHostManagementFeatures"`
}
func (payload *endpointSettingsUpdatePayload) Validate(r *http.Request) error {
return nil
}
// @id EndpointSettingsUpdate
// @summary Update settings for an endpoint
// @description Update settings for an endpoint.
// @description **Access policy**: administrator
// @security jwt
// @tags endpoints
// @accept json
// @produce json
// @param id path int true "Endpoint identifier"
// @param body body endpointSettingsUpdatePayload true "Endpoint details"
// @success 200 {object} portainer.Endpoint "Success"
// @failure 400 "Invalid request"
// @failure 404 "Endpoint not found"
// @failure 500 "Server error"
// @router /api/endpoints/:id/settings [put]
// PUT request on /api/endpoints/:id/settings
func (handler *Handler) endpointSettingsUpdate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
endpointID, err := request.RetrieveNumericRouteVariableValue(r, "id")
if err != nil {

View File

@@ -6,23 +6,12 @@ import (
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/bolt/errors"
"github.com/portainer/portainer/api/internal/snapshot"
)
// @id EndpointSnapshot
// @summary Snapshots an endpoint
// @description Snapshots an endpoint
// @description **Access policy**: restricted
// @tags endpoints
// @security jwt
// @param id path int true "Endpoint identifier"
// @success 204 "Success"
// @failure 400 "Invalid request"
// @failure 404 "Endpoint not found"
// @failure 500 "Server error"
// @router /endpoints/{id}/snapshot [post]
// POST request on /api/endpoints/:id/snapshot
func (handler *Handler) endpointSnapshot(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
endpointID, err := request.RetrieveNumericRouteVariableValue(r, "id")
if err != nil {

View File

@@ -6,19 +6,11 @@ import (
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/internal/snapshot"
)
// @id EndpointSnapshots
// @summary Snapshot all endpoints
// @description Snapshot all endpoints
// @description **Access policy**: administrator
// @tags endpoints
// @security jwt
// @success 204 "Success"
// @failure 500 "Server Error"
// @router /endpoints/snapshot [post]
// POST request on /api/endpoints/snapshot
func (handler *Handler) endpointSnapshots(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
endpoints, err := handler.DataStore.Endpoint().Endpoints()
if err != nil {

View File

@@ -5,63 +5,37 @@ import (
"errors"
"net/http"
"strconv"
"time"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api"
bolterrors "github.com/portainer/portainer/api/bolt/errors"
)
type stackStatusResponse struct {
// EdgeStack Identifier
ID portainer.EdgeStackID `example:"1"`
// Version of this stack
Version int `example:"3"`
ID portainer.EdgeStackID
Version int
}
type edgeJobResponse struct {
// EdgeJob Identifier
ID portainer.EdgeJobID `json:"Id" example:"2"`
// Whether to collect logs
CollectLogs bool `json:"CollectLogs" example:"true"`
// A cron expression to schedule this job
CronExpression string `json:"CronExpression" example:"* * * * *"`
// Script to run
Script string `json:"Script" example:"echo hello"`
// Version of this EdgeJob
Version int `json:"Version" example:"2"`
ID portainer.EdgeJobID `json:"Id"`
CollectLogs bool `json:"CollectLogs"`
CronExpression string `json:"CronExpression"`
Script string `json:"Script"`
Version int `json:"Version"`
}
type endpointStatusInspectResponse struct {
// Status represents the endpoint status
Status string `json:"status" example:"REQUIRED"`
// The tunnel port
Port int `json:"port" example:"8732"`
// List of requests for jobs to run on the endpoint
Schedules []edgeJobResponse `json:"schedules"`
// The current value of CheckinInterval
CheckinInterval int `json:"checkin" example:"5"`
//
Credentials string `json:"credentials" example:""`
// List of stacks to be deployed on the endpoints
Stacks []stackStatusResponse `json:"stacks"`
Status string `json:"status"`
Port int `json:"port"`
Schedules []edgeJobResponse `json:"schedules"`
CheckinInterval int `json:"checkin"`
Credentials string `json:"credentials"`
Stacks []stackStatusResponse `json:"stacks"`
}
// @id EndpointStatusInspect
// @summary Get endpoint status
// @description Endpoint for edge agent to check status of environment
// @description **Access policy**: restricted only to Edge endpoints
// @tags endpoints
// @security jwt
// @param id path int true "Endpoint identifier"
// @success 200 {object} endpointStatusInspectResponse "Success"
// @failure 400 "Invalid request"
// @failure 403 "Permission denied to access endpoint"
// @failure 404 "Endpoint not found"
// @failure 500 "Server error"
// @router /endpoints/{id}/status [get]
// GET request on /api/endpoints/:id/status
func (handler *Handler) endpointStatusInspect(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
endpointID, err := request.RetrieveNumericRouteVariableValue(r, "id")
if err != nil {
@@ -101,13 +75,11 @@ func (handler *Handler) endpointStatusInspect(w http.ResponseWriter, r *http.Req
} else if agentPlatform == portainer.AgentPlatformKubernetes {
endpoint.Type = portainer.EdgeAgentOnKubernetesEnvironment
}
}
endpoint.LastCheckInDate = time.Now().Unix()
err = handler.DataStore.Endpoint().UpdateEndpoint(endpoint.ID, endpoint)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to Unable to persist endpoint changes inside the database", err}
err = handler.DataStore.Endpoint().UpdateEndpoint(endpoint.ID, endpoint)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to Unable to persist endpoint changes inside the database", err}
}
}
settings, err := handler.DataStore.Settings().Settings()

View File

@@ -8,7 +8,7 @@ import (
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/bolt/errors"
"github.com/portainer/portainer/api/http/client"
"github.com/portainer/portainer/api/internal/edge"
@@ -16,58 +16,29 @@ import (
)
type endpointUpdatePayload struct {
// Name that will be used to identify this endpoint
Name *string `example:"my-endpoint"`
// URL or IP address of a Docker host
URL *string `example:"docker.mydomain.tld:2375"`
// URL or IP address where exposed containers will be reachable.\
// Defaults to URL if not specified
PublicURL *string `example:"docker.mydomain.tld:2375"`
// Group identifier
GroupID *int `example:"1"`
// Require TLS to connect against this endpoint
TLS *bool `example:"true"`
// Skip server verification when using TLS
TLSSkipVerify *bool `example:"false"`
// Skip client verification when using TLS
TLSSkipClientVerify *bool `example:"false"`
// The status of the endpoint (1 - up, 2 - down)
Status *int `example:"1"`
// Azure application ID
AzureApplicationID *string `example:"eag7cdo9-o09l-9i83-9dO9-f0b23oe78db4"`
// Azure tenant ID
AzureTenantID *string `example:"34ddc78d-4fel-2358-8cc1-df84c8o839f5"`
// Azure authentication key
AzureAuthenticationKey *string `example:"cOrXoK/1D35w8YQ8nH1/8ZGwzz45JIYD5jxHKXEQknk="`
// List of tag identifiers to which this endpoint is associated
TagIDs []portainer.TagID `example:"1,2"`
UserAccessPolicies portainer.UserAccessPolicies
TeamAccessPolicies portainer.TeamAccessPolicies
// The check in interval for edge agent (in seconds)
EdgeCheckinInterval *int `example:"5"`
// Associated Kubernetes data
Kubernetes *portainer.KubernetesData
Name *string
URL *string
PublicURL *string
GroupID *int
TLS *bool
TLSSkipVerify *bool
TLSSkipClientVerify *bool
Status *int
AzureApplicationID *string
AzureTenantID *string
AzureAuthenticationKey *string
TagIDs []portainer.TagID
UserAccessPolicies portainer.UserAccessPolicies
TeamAccessPolicies portainer.TeamAccessPolicies
EdgeCheckinInterval *int
Kubernetes *portainer.KubernetesData
}
func (payload *endpointUpdatePayload) Validate(r *http.Request) error {
return nil
}
// @id EndpointUpdate
// @summary Update an endpoint
// @description Update an endpoint.
// @description **Access policy**: administrator
// @security jwt
// @tags endpoints
// @accept json
// @produce json
// @param id path int true "Endpoint identifier"
// @param body body endpointUpdatePayload true "Endpoint details"
// @success 200 {object} portainer.Endpoint "Success"
// @failure 400 "Invalid request"
// @failure 404 "Endpoint not found"
// @failure 500 "Server error"
// @router /endpoints/{id} [put]
// PUT request on /api/endpoints/:id
func (handler *Handler) endpointUpdate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
endpointID, err := request.RetrieveNumericRouteVariableValue(r, "id")
if err != nil {
@@ -238,11 +209,6 @@ func (handler *Handler) endpointUpdate(w http.ResponseWriter, r *http.Request) *
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to remove TLS files from disk", err}
}
}
if endpoint.Type == portainer.AgentOnKubernetesEnvironment || endpoint.Type == portainer.EdgeAgentOnKubernetesEnvironment {
endpoint.TLSConfig.TLS = true
endpoint.TLSConfig.TLSSkipVerify = true
}
}
if payload.URL != nil || payload.TLS != nil || endpoint.Type == portainer.AzureEnvironment {

View File

@@ -64,77 +64,6 @@ type Handler struct {
WebhookHandler *webhooks.Handler
}
// @title PortainerCE API
// @version 2.1.1
// @description.markdown api-description.md
// @termsOfService
// @contact.email info@portainer.io
// @license.name
// @license.url
// @host
// @BasePath /api
// @schemes http https
// @securitydefinitions.apikey jwt
// @in header
// @name Authorization
// @tag.name auth
// @tag.description Authenticate against Portainer HTTP API
// @tag.name custom_templates
// @tag.description Manage Custom Templates
// @tag.name dockerhub
// @tag.description Manage how Portainer connects to the DockerHub
// @tag.name edge_groups
// @tag.description Manage Edge Groups
// @tag.name edge_jobs
// @tag.description Manage Edge Jobs
// @tag.name edge_stacks
// @tag.description Manage Edge Stacks
// @tag.name edge_templates
// @tag.description Manage Edge Templates
// @tag.name edge
// @tag.description Manage Edge related endpoint settings
// @tag.name endpoints
// @tag.description Manage Docker environments
// @tag.name endpoint_groups
// @tag.description Manage endpoint groups
// @tag.name motd
// @tag.description Fetch the message of the day
// @tag.name registries
// @tag.description Manage Docker registries
// @tag.name resource_controls
// @tag.description Manage access control on Docker resources
// @tag.name roles
// @tag.description Manage roles
// @tag.name settings
// @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
// @tag.description Manage tags
// @tag.name teams
// @tag.description Manage teams
// @tag.name team_memberships
// @tag.description Manage team memberships
// @tag.name templates
// @tag.description Manage App Templates
// @tag.name stacks
// @tag.description Manage stacks
// @tag.name upload
// @tag.description Upload files
// @tag.name webhooks
// @tag.description Manage webhooks
// @tag.name websocket
// @tag.description Create exec sessions using websockets
// ServeHTTP delegates a request to the appropriate subhandler.
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
switch {

View File

@@ -7,7 +7,7 @@ import (
"github.com/portainer/libcrypto"
"github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/http/client"
)
@@ -26,12 +26,6 @@ type motdData struct {
Style string `json:"style"`
}
// @summary fetches the message of the day
// @tags motd
// @security jwt
// @produce json
// @success 200 {object} motdResponse
// @router /motd [get]
func (handler *Handler) motd(w http.ResponseWriter, r *http.Request) {
motd, err := client.Get(portainer.MessageOfTheDayURL, 0)
if err != nil {

View File

@@ -8,28 +8,19 @@ import (
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api"
bolterrors "github.com/portainer/portainer/api/bolt/errors"
)
type registryConfigurePayload struct {
// Is authentication against this registry enabled
Authentication bool `example:"false" validate:"required"`
// Username used to authenticate against this registry. Required when Authentication is true
Username string `example:"registry_user"`
// Password used to authenticate against this registry. required when Authentication is true
Password string `example:"registry_password"`
// Use TLS
TLS bool `example:"true"`
// Skip the verification of the server TLS certificate
TLSSkipVerify bool `example:"false"`
// The TLS CA certificate file
TLSCACertFile []byte
// The TLS client certificate file
TLSCertFile []byte
// The TLS client key file
TLSKeyFile []byte
Authentication bool
Username string
Password string
TLS bool
TLSSkipVerify bool
TLSCertFile []byte
TLSKeyFile []byte
TLSCACertFile []byte
}
func (payload *registryConfigurePayload) Validate(r *http.Request) error {
@@ -76,22 +67,7 @@ func (payload *registryConfigurePayload) Validate(r *http.Request) error {
return nil
}
// @id RegistryConfigure
// @summary Configures a registry
// @description Configures a registry.
// @description **Access policy**: admin
// @tags registries
// @security jwt
// @accept json
// @produce json
// @param id path int true "Registry identifier"
// @param body body registryConfigurePayload true "Registry configuration"
// @success 204 "Success"
// @failure 400 "Invalid request"
// @failure 403 "Permission denied"
// @failure 404 "Registry not found"
// @failure 500 "Server error"
// @router /registries/{id}/configure [post]
// POST request on /api/registries/:id/configure
func (handler *Handler) registryConfigure(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
registryID, err := request.RetrieveNumericRouteVariableValue(r, "id")
if err != nil {

View File

@@ -8,24 +8,17 @@ import (
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api"
)
type registryCreatePayload struct {
// Name that will be used to identify this registry
Name string `example:"my-registry" validate:"required"`
// Registry Type. Valid values are: 1 (Quay.io), 2 (Azure container registry), 3 (custom registry) or 4 (Gitlab registry)
Type portainer.RegistryType `example:"1" validate:"required" enums:"1,2,3,4"`
// URL or IP address of the Docker registry
URL string `example:"registry.mydomain.tld:2375" validate:"required"`
// Is authentication against this registry enabled
Authentication bool `example:"false" validate:"required"`
// Username used to authenticate against this registry. Required when Authentication is true
Username string `example:"registry_user"`
// Password used to authenticate against this registry. required when Authentication is true
Password string `example:"registry_password"`
// Gitlab specific details, required when type = 4
Gitlab portainer.GitlabRegistryData
Name string
Type portainer.RegistryType
URL string
Authentication bool
Username string
Password string
Gitlab portainer.GitlabRegistryData
}
func (payload *registryCreatePayload) Validate(r *http.Request) error {
@@ -44,19 +37,6 @@ func (payload *registryCreatePayload) Validate(r *http.Request) error {
return nil
}
// @id RegistryCreate
// @summary Create a new registry
// @description Create a new registry.
// @description **Access policy**: administrator
// @tags registries
// @security jwt
// @accept json
// @produce json
// @param body body registryCreatePayload true "Registry details"
// @success 200 {object} portainer.Registry "Success"
// @failure 400 "Invalid request"
// @failure 500 "Server error"
// @router /registries [post]
func (handler *Handler) registryCreate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
var payload registryCreatePayload
err := request.DecodeAndValidateJSONPayload(r, &payload)

View File

@@ -6,22 +6,11 @@ import (
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/bolt/errors"
)
// @id RegistryDelete
// @summary Remove a registry
// @description Remove a registry
// @description **Access policy**: administrator
// @tags registries
// @security jwt
// @param id path int true "Registry identifier"
// @success 204 "Success"
// @failure 400 "Invalid request"
// @failure 404 "Registry not found"
// @failure 500 "Server error"
// @router /registries/{id} [delete]
// DELETE request on /api/registries/:id
func (handler *Handler) registryDelete(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
registryID, err := request.RetrieveNumericRouteVariableValue(r, "id")
if err != nil {

View File

@@ -3,29 +3,16 @@ package registries
import (
"net/http"
portainer "github.com/portainer/portainer/api"
bolterrors "github.com/portainer/portainer/api/bolt/errors"
"github.com/portainer/portainer/api/http/errors"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer/api"
)
// @id RegistryInspect
// @summary Inspect a registry
// @description Retrieve details about a registry.
// @description **Access policy**: administrator
// @tags registries
// @security jwt
// @produce json
// @param id path int true "Registry identifier"
// @success 200 {object} portainer.Registry "Success"
// @failure 400 "Invalid request"
// @failure 403 "Permission denied to access registry"
// @failure 404 "Registry not found"
// @failure 500 "Server error"
// @router /registries/{id} [get]
// GET request on /api/registries/:id
func (handler *Handler) registryInspect(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
registryID, err := request.RetrieveNumericRouteVariableValue(r, "id")
if err != nil {

View File

@@ -8,18 +8,7 @@ import (
"github.com/portainer/portainer/api/http/security"
)
// @id RegistryList
// @summary List Registries
// @description List all registries based on the current user authorizations.
// @description Will return all registries if using an administrator account otherwise it
// @description will only return authorized registries.
// @description **Access policy**: restricted
// @tags registries
// @security jwt
// @produce json
// @success 200 {array} portainer.Registry "Success"
// @failure 500 "Server error"
// @router /registries [get]
// GET request on /api/registries
func (handler *Handler) registryList(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
registries, err := handler.DataStore.Registry().Registries()
if err != nil {

View File

@@ -12,16 +12,11 @@ import (
)
type registryUpdatePayload struct {
// Name that will be used to identify this registry
Name *string `validate:"required" example:"my-registry"`
// URL or IP address of the Docker registry
URL *string `validate:"required" example:"registry.mydomain.tld:2375"`
// Is authentication against this registry enabled
Authentication *bool `example:"false" validate:"required"`
// Username used to authenticate against this registry. Required when Authentication is true
Username *string `example:"registry_user"`
// Password used to authenticate against this registry. required when Authentication is true
Password *string `example:"registry_password"`
Name *string
URL *string
Authentication *bool
Username *string
Password *string
UserAccessPolicies portainer.UserAccessPolicies
TeamAccessPolicies portainer.TeamAccessPolicies
}
@@ -30,22 +25,7 @@ func (payload *registryUpdatePayload) Validate(r *http.Request) error {
return nil
}
// @id RegistryUpdate
// @summary Update a registry
// @description Update a registry
// @description **Access policy**: administrator
// @tags registries
// @security jwt
// @accept json
// @produce json
// @param id path int true "Registry identifier"
// @param body body registryUpdatePayload true "Registry details"
// @success 200 {object} portainer.Registry "Success"
// @failure 400 "Invalid request"
// @failure 404 "Registry not found"
// @failure 409 "Another registry with the same URL already exists"
// @failure 500 "Server error"
// @router /registries/{id} [put]
// PUT request on /api/registries/:id
func (handler *Handler) registryUpdate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
registryID, err := request.RetrieveNumericRouteVariableValue(r, "id")
if err != nil {

View File

@@ -8,25 +8,17 @@ import (
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api"
)
type resourceControlCreatePayload struct {
//
ResourceID string `example:"617c5f22bb9b023d6daab7cba43a57576f83492867bc767d1c59416b065e5f08" validate:"required"`
// Type of Docker resource. Valid values are: container, volume\
// service, secret, config or stack
Type string `example:"container" validate:"required"`
// Permit access to the associated resource to any user
Public bool `example:"true"`
// Permit access to resource only to admins
AdministratorsOnly bool `example:"true"`
// List of user identifiers with access to the associated resource
Users []int `example:"1,4"`
// List of team identifiers with access to the associated resource
Teams []int `example:"56,7"`
// List of Docker resources that will inherit this access control
SubResourceIDs []string `example:"617c5f22bb9b023d6daab7cba43a57576f83492867bc767d1c59416b065e5f08"`
ResourceID string
Type string
Public bool
AdministratorsOnly bool
Users []int
Teams []int
SubResourceIDs []string
}
var (
@@ -53,20 +45,7 @@ func (payload *resourceControlCreatePayload) Validate(r *http.Request) error {
return nil
}
// @id ResourceControlCreate
// @summary Create a new resource control
// @description Create a new resource control to restrict access to a Docker resource.
// @description **Access policy**: administrator
// @tags resource_controls
// @security jwt
// @accept json
// @produce json
// @param body body resourceControlCreatePayload true "Resource control details"
// @success 200 {object} portainer.ResourceControl "Success"
// @failure 400 "Invalid request"
// @failure 409 "Resource control already exists"
// @failure 500 "Server error"
// @router /resource_controls [post]
// POST request on /api/resource_controls
func (handler *Handler) resourceControlCreate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
var payload resourceControlCreatePayload
err := request.DecodeAndValidateJSONPayload(r, &payload)

View File

@@ -6,22 +6,11 @@ import (
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/bolt/errors"
)
// @id ResourceControlDelete
// @summary Remove a resource control
// @description Remove a resource control.
// @description **Access policy**: administrator
// @tags resource_controls
// @security jwt
// @param id path int true "Resource control identifier"
// @success 204 "Success"
// @failure 400 "Invalid request"
// @failure 404 "Resource control not found"
// @failure 500 "Server error"
// @router /resource_controls/{id} [delete]
// DELETE request on /api/resource_controls/:id
func (handler *Handler) resourceControlDelete(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
resourceControlID, err := request.RetrieveNumericRouteVariableValue(r, "id")
if err != nil {

View File

@@ -7,21 +7,17 @@ import (
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api"
bolterrors "github.com/portainer/portainer/api/bolt/errors"
httperrors "github.com/portainer/portainer/api/http/errors"
"github.com/portainer/portainer/api/http/security"
)
type resourceControlUpdatePayload struct {
// Permit access to the associated resource to any user
Public bool `example:"true"`
// List of user identifiers with access to the associated resource
Users []int `example:"4"`
// List of team identifiers with access to the associated resource
Teams []int `example:"7"`
// Permit access to resource only to admins
AdministratorsOnly bool `example:"true"`
Public bool
Users []int
Teams []int
AdministratorsOnly bool
}
func (payload *resourceControlUpdatePayload) Validate(r *http.Request) error {
@@ -35,22 +31,7 @@ func (payload *resourceControlUpdatePayload) Validate(r *http.Request) error {
return nil
}
// @id ResourceControlUpdate
// @summary Update a resource control
// @description Update a resource control
// @description **Access policy**: restricted
// @tags resource_controls
// @security jwt
// @accept json
// @produce json
// @param id path int true "Resource control identifier"
// @param body body resourceControlUpdatePayload true "Resource control details"
// @success 200 {object} portainer.ResourceControl "Success"
// @failure 400 "Invalid request"
// @failure 403 "Unauthorized"
// @failure 404 "Resource control not found"
// @failure 500 "Server error"
// @router /resource_controls/{id} [put]
// PUT request on /api/resource_controls/:id
func (handler *Handler) resourceControlUpdate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
resourceControlID, err := request.RetrieveNumericRouteVariableValue(r, "id")
if err != nil {

View File

@@ -7,16 +7,7 @@ import (
"github.com/portainer/libhttp/response"
)
// @id RoleList
// @summary List roles
// @description List all roles available for use
// @description **Access policy**: administrator
// @tags roles
// @security jwt
// @produce json
// @success 200 {array} portainer.Role "Success"
// @failure 500 "Server error"
// @router /roles [get]
// GET request on /api/Role
func (handler *Handler) roleList(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
roles, err := handler.DataStore.Role().Roles()
if err != nil {

View File

@@ -7,16 +7,7 @@ import (
"github.com/portainer/libhttp/response"
)
// @id SettingsInspect
// @summary Retrieve Portainer settings
// @description Retrieve Portainer settings.
// @description **Access policy**: administrator
// @tags settings
// @security jwt
// @produce json
// @success 200 {object} portainer.Settings "Success"
// @failure 500 "Server error"
// @router /settings [get]
// GET request on /api/settings
func (handler *Handler) settingsInspect(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
settings, err := handler.DataStore.Settings().Settings()
if err != nil {

View File

@@ -6,7 +6,7 @@ import (
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/filesystem"
)
@@ -18,18 +18,7 @@ func (payload *settingsLDAPCheckPayload) Validate(r *http.Request) error {
return nil
}
// @id SettingsLDAPCheck
// @summary Test LDAP connectivity
// @description Test LDAP connectivity using LDAP details
// @description **Access policy**: administrator
// @tags settings
// @security jwt
// @accept json
// @param body body settingsLDAPCheckPayload true "details"
// @success 204 "Success"
// @failure 400 "Invalid request"
// @failure 500 "Server error"
// @router /settings/ldap/check [put]
// PUT request on /settings/ldap/check
func (handler *Handler) settingsLDAPCheck(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
var payload settingsLDAPCheckPayload
err := request.DecodeAndValidateJSONPayload(r, &payload)

View File

@@ -10,27 +10,14 @@ import (
)
type publicSettingsResponse struct {
// URL to a logo that will be displayed on the login page as well as on top of the sidebar. Will use default Portainer logo when value is empty string
LogoURL string `json:"LogoURL" example:"https://mycompany.mydomain.tld/logo.png"`
// Active authentication method for the Portainer instance. Valid values are: 1 for internal, 2 for LDAP, or 3 for oauth
AuthenticationMethod portainer.AuthenticationMethod `json:"AuthenticationMethod" example:"1"`
// Whether edge compute features are enabled
EnableEdgeComputeFeatures bool `json:"EnableEdgeComputeFeatures" example:"true"`
// The URL used for oauth login
OAuthLoginURI string `json:"OAuthLoginURI" example:"https://gitlab.com/oauth"`
// Whether telemetry is enabled
EnableTelemetry bool `json:"EnableTelemetry" example:"true"`
LogoURL string `json:"LogoURL"`
AuthenticationMethod portainer.AuthenticationMethod `json:"AuthenticationMethod"`
EnableEdgeComputeFeatures bool `json:"EnableEdgeComputeFeatures"`
OAuthLoginURI string `json:"OAuthLoginURI"`
EnableTelemetry bool `json:"EnableTelemetry"`
}
// @id SettingsPublic
// @summary Retrieve Portainer public settings
// @description Retrieve public settings. Returns a small set of settings that are not reserved to administrators only.
// @description **Access policy**: public
// @tags settings
// @produce json
// @success 200 {object} publicSettingsResponse "Success"
// @failure 500 "Server error"
// @router /settings/public [get]
// GET request on /api/settings/public
func (handler *Handler) settingsPublic(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
settings, err := handler.DataStore.Settings().Settings()
if err != nil {

View File

@@ -14,26 +14,17 @@ import (
)
type settingsUpdatePayload struct {
// URL to a logo that will be displayed on the login page as well as on top of the sidebar. Will use default Portainer logo when value is empty string
LogoURL *string `example:"https://mycompany.mydomain.tld/logo.png"`
// A list of label name & value that will be used to hide containers when querying containers
BlackListedLabels []portainer.Pair
// Active authentication method for the Portainer instance. Valid values are: 1 for internal, 2 for LDAP, or 3 for oauth
AuthenticationMethod *int `example:"1"`
LDAPSettings *portainer.LDAPSettings `example:""`
OAuthSettings *portainer.OAuthSettings `example:""`
// The interval in which endpoint snapshots are created
SnapshotInterval *string `example:"5m"`
// URL to the templates that will be displayed in the UI when navigating to App Templates
TemplatesURL *string `example:"https://raw.githubusercontent.com/portainer/templates/master/templates.json"`
// The default check in interval for edge agent (in seconds)
EdgeAgentCheckinInterval *int `example:"5"`
// Whether edge compute features are enabled
EnableEdgeComputeFeatures *bool `example:"true"`
// The duration of a user session
UserSessionTimeout *string `example:"5m"`
// Whether telemetry is enabled
EnableTelemetry *bool `example:"false"`
LogoURL *string
BlackListedLabels []portainer.Pair
AuthenticationMethod *int
LDAPSettings *portainer.LDAPSettings
OAuthSettings *portainer.OAuthSettings
SnapshotInterval *string
TemplatesURL *string
EdgeAgentCheckinInterval *int
EnableEdgeComputeFeatures *bool
UserSessionTimeout *string
EnableTelemetry *bool
}
func (payload *settingsUpdatePayload) Validate(r *http.Request) error {
@@ -56,19 +47,7 @@ func (payload *settingsUpdatePayload) Validate(r *http.Request) error {
return nil
}
// @id SettingsUpdate
// @summary Update Portainer settings
// @description Update Portainer settings.
// @description **Access policy**: administrator
// @tags settings
// @security jwt
// @accept json
// @produce json
// @param body body settingsUpdatePayload true "New settings"
// @success 200 {object} portainer.Settings "Success"
// @failure 400 "Invalid request"
// @failure 500 "Server error"
// @router /settings [put]
// PUT request on /api/settings
func (handler *Handler) settingsUpdate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
var payload settingsUpdatePayload
err := request.DecodeAndValidateJSONPayload(r, &payload)

View File

@@ -2,7 +2,6 @@ package stacks
import (
"errors"
"fmt"
"net/http"
"path"
"regexp"
@@ -26,12 +25,9 @@ func normalizeStackName(name string) string {
}
type composeStackFromFileContentPayload struct {
// Name of the stack
Name string `example:"myStack" validate:"required"`
// Content of the Stack file
StackFileContent string `example:"version: 3\n services:\n web:\n image:nginx" validate:"required"`
// A list of environment variables used during stack deployment
Env []portainer.Pair `example:""`
Name string
StackFileContent string
Env []portainer.Pair
}
func (payload *composeStackFromFileContentPayload) Validate(r *http.Request) error {
@@ -52,13 +48,15 @@ func (handler *Handler) createComposeStackFromFileContent(w http.ResponseWriter,
return &httperror.HandlerError{http.StatusBadRequest, "Invalid request payload", err}
}
isUnique, err := handler.checkUniqueName(endpoint, payload.Name, 0, false)
stacks, err := handler.DataStore.Stack().Stacks()
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to check for name collision", err}
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve stacks from the database", err}
}
if !isUnique {
errorMessage := fmt.Sprintf("A stack with the name '%s' is already running", payload.Name)
return &httperror.HandlerError{http.StatusConflict, errorMessage, errors.New(errorMessage)}
for _, stack := range stacks {
if strings.EqualFold(stack.Name, payload.Name) {
return &httperror.HandlerError{http.StatusConflict, "A stack with this name already exists", errStackAlreadyExists}
}
}
stackID := handler.DataStore.Stack().GetNextIdentifier()
@@ -105,24 +103,14 @@ func (handler *Handler) createComposeStackFromFileContent(w http.ResponseWriter,
}
type composeStackFromGitRepositoryPayload struct {
// Name of the stack
Name string `example:"myStack" validate:"required"`
// URL of a Git repository hosting the Stack file
RepositoryURL string `example:"https://github.com/openfaas/faas" validate:"required"`
// Reference name of a Git repository hosting the Stack file
RepositoryReferenceName string `example:"refs/heads/master"`
// Use basic authentication to clone the Git repository
RepositoryAuthentication bool `example:"true"`
// Username used in basic authentication. Required when RepositoryAuthentication is true.
RepositoryUsername string `example:"myGitUsername"`
// Password used in basic authentication. Required when RepositoryAuthentication is true.
RepositoryPassword string `example:"myGitPassword"`
// Path to the Stack file inside the Git repository
ComposeFilePathInRepository string `example:"docker-compose.yml" default:"docker-compose.yml"`
// A list of environment variables used during stack deployment
Env []portainer.Pair
Name string
RepositoryURL string
RepositoryReferenceName string
RepositoryAuthentication bool
RepositoryUsername string
RepositoryPassword string
ComposeFilePathInRepository string
Env []portainer.Pair
}
func (payload *composeStackFromGitRepositoryPayload) Validate(r *http.Request) error {
@@ -149,13 +137,15 @@ func (handler *Handler) createComposeStackFromGitRepository(w http.ResponseWrite
return &httperror.HandlerError{http.StatusBadRequest, "Invalid request payload", err}
}
isUnique, err := handler.checkUniqueName(endpoint, payload.Name, 0, false)
stacks, err := handler.DataStore.Stack().Stacks()
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to check for name collision", err}
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve stacks from the database", err}
}
if !isUnique {
errorMessage := fmt.Sprintf("A stack with the name '%s' is already running", payload.Name)
return &httperror.HandlerError{http.StatusConflict, errorMessage, errors.New(errorMessage)}
for _, stack := range stacks {
if strings.EqualFold(stack.Name, payload.Name) {
return &httperror.HandlerError{http.StatusConflict, "A stack with this name already exists", errStackAlreadyExists}
}
}
stackID := handler.DataStore.Stack().GetNextIdentifier()
@@ -246,13 +236,15 @@ func (handler *Handler) createComposeStackFromFileUpload(w http.ResponseWriter,
return &httperror.HandlerError{http.StatusBadRequest, "Invalid request payload", err}
}
isUnique, err := handler.checkUniqueName(endpoint, payload.Name, 0, false)
stacks, err := handler.DataStore.Stack().Stacks()
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to check for name collision", err}
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve stacks from the database", err}
}
if !isUnique {
errorMessage := fmt.Sprintf("A stack with the name '%s' is already running", payload.Name)
return &httperror.HandlerError{http.StatusConflict, errorMessage, errors.New(errorMessage)}
for _, stack := range stacks {
if strings.EqualFold(stack.Name, payload.Name) {
return &httperror.HandlerError{http.StatusConflict, "A stack with this name already exists", errStackAlreadyExists}
}
}
stackID := handler.DataStore.Stack().GetNextIdentifier()

View File

@@ -2,10 +2,10 @@ package stacks
import (
"errors"
"fmt"
"net/http"
"path"
"strconv"
"strings"
"time"
"github.com/asaskevich/govalidator"
@@ -17,14 +17,10 @@ import (
)
type swarmStackFromFileContentPayload struct {
// Name of the stack
Name string `example:"myStack" validate:"required"`
// Swarm cluster identifier
SwarmID string `example:"jpofkc0i9uo9wtx1zesuk649w" validate:"required"`
// Content of the Stack file
StackFileContent string `example:"version: 3\n services:\n web:\n image:nginx" validate:"required"`
// A list of environment variables used during stack deployment
Env []portainer.Pair
Name string
SwarmID string
StackFileContent string
Env []portainer.Pair
}
func (payload *swarmStackFromFileContentPayload) Validate(r *http.Request) error {
@@ -47,13 +43,15 @@ func (handler *Handler) createSwarmStackFromFileContent(w http.ResponseWriter, r
return &httperror.HandlerError{http.StatusBadRequest, "Invalid request payload", err}
}
isUnique, err := handler.checkUniqueName(endpoint, payload.Name, 0, true)
stacks, err := handler.DataStore.Stack().Stacks()
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to check for name collision", err}
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve stacks from the database", err}
}
if !isUnique {
errorMessage := fmt.Sprintf("A stack with the name '%s' is already running", payload.Name)
return &httperror.HandlerError{http.StatusConflict, errorMessage, errors.New(errorMessage)}
for _, stack := range stacks {
if strings.EqualFold(stack.Name, payload.Name) {
return &httperror.HandlerError{http.StatusConflict, "A stack with this name already exists", errStackAlreadyExists}
}
}
stackID := handler.DataStore.Stack().GetNextIdentifier()
@@ -101,25 +99,15 @@ func (handler *Handler) createSwarmStackFromFileContent(w http.ResponseWriter, r
}
type swarmStackFromGitRepositoryPayload struct {
// Name of the stack
Name string `example:"myStack" validate:"required"`
// Swarm cluster identifier
SwarmID string `example:"jpofkc0i9uo9wtx1zesuk649w" validate:"required"`
// A list of environment variables used during stack deployment
Env []portainer.Pair
// URL of a Git repository hosting the Stack file
RepositoryURL string `example:"https://github.com/openfaas/faas" validate:"required"`
// Reference name of a Git repository hosting the Stack file
RepositoryReferenceName string `example:"refs/heads/master"`
// Use basic authentication to clone the Git repository
RepositoryAuthentication bool `example:"true"`
// Username used in basic authentication. Required when RepositoryAuthentication is true.
RepositoryUsername string `example:"myGitUsername"`
// Password used in basic authentication. Required when RepositoryAuthentication is true.
RepositoryPassword string `example:"myGitPassword"`
// Path to the Stack file inside the Git repository
ComposeFilePathInRepository string `example:"docker-compose.yml" default:"docker-compose.yml"`
Name string
SwarmID string
Env []portainer.Pair
RepositoryURL string
RepositoryReferenceName string
RepositoryAuthentication bool
RepositoryUsername string
RepositoryPassword string
ComposeFilePathInRepository string
}
func (payload *swarmStackFromGitRepositoryPayload) Validate(r *http.Request) error {
@@ -148,13 +136,15 @@ func (handler *Handler) createSwarmStackFromGitRepository(w http.ResponseWriter,
return &httperror.HandlerError{http.StatusBadRequest, "Invalid request payload", err}
}
isUnique, err := handler.checkUniqueName(endpoint, payload.Name, 0, true)
stacks, err := handler.DataStore.Stack().Stacks()
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to check for name collision", err}
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve stacks from the database", err}
}
if !isUnique {
errorMessage := fmt.Sprintf("A stack with the name '%s' is already running", payload.Name)
return &httperror.HandlerError{http.StatusConflict, errorMessage, errors.New(errorMessage)}
for _, stack := range stacks {
if strings.EqualFold(stack.Name, payload.Name) {
return &httperror.HandlerError{http.StatusConflict, "A stack with this name already exists", errStackAlreadyExists}
}
}
stackID := handler.DataStore.Stack().GetNextIdentifier()
@@ -253,13 +243,15 @@ func (handler *Handler) createSwarmStackFromFileUpload(w http.ResponseWriter, r
return &httperror.HandlerError{http.StatusBadRequest, "Invalid request payload", err}
}
isUnique, err := handler.checkUniqueName(endpoint, payload.Name, 0, true)
stacks, err := handler.DataStore.Stack().Stacks()
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to check for name collision", err}
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve stacks from the database", err}
}
if !isUnique {
errorMessage := fmt.Sprintf("A stack with the name '%s' is already running", payload.Name)
return &httperror.HandlerError{http.StatusConflict, errorMessage, errors.New(errorMessage)}
for _, stack := range stacks {
if strings.EqualFold(stack.Name, payload.Name) {
return &httperror.HandlerError{http.StatusConflict, "A stack with this name already exists", errStackAlreadyExists}
}
}
stackID := handler.DataStore.Stack().GetNextIdentifier()

View File

@@ -1,17 +1,13 @@
package stacks
import (
"context"
"errors"
"net/http"
"strings"
"sync"
"github.com/docker/docker/api/types"
"github.com/gorilla/mux"
httperror "github.com/portainer/libhttp/error"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/docker"
"github.com/portainer/portainer/api/http/security"
"github.com/portainer/portainer/api/internal/authorization"
)
@@ -28,7 +24,6 @@ type Handler struct {
requestBouncer *security.RequestBouncer
*mux.Router
DataStore portainer.DataStore
DockerClientFactory *docker.ClientFactory
FileService portainer.FileService
GitService portainer.GitService
SwarmStackManager portainer.SwarmStackManager
@@ -108,50 +103,3 @@ func (handler *Handler) userCanCreateStack(securityContext *security.RestrictedR
return handler.userIsAdminOrEndpointAdmin(user, endpointID)
}
func (handler *Handler) checkUniqueName(endpoint *portainer.Endpoint, name string, stackID portainer.StackID, swarmMode bool) (bool, error) {
stacks, err := handler.DataStore.Stack().Stacks()
if err != nil {
return false, err
}
for _, stack := range stacks {
if strings.EqualFold(stack.Name, name) && (stackID == 0 || stackID != stack.ID) && stack.EndpointID == endpoint.ID {
return false, nil
}
}
dockerClient, err := handler.DockerClientFactory.CreateClient(endpoint, "")
if err != nil {
return false, err
}
defer dockerClient.Close()
if swarmMode {
services, err := dockerClient.ServiceList(context.Background(), types.ServiceListOptions{})
if err != nil {
return false, err
}
for _, service := range services {
serviceNS, ok := service.Spec.Labels["com.docker.stack.namespace"]
if ok && serviceNS == name {
return false, nil
}
}
}
containers, err := dockerClient.ContainerList(context.Background(), types.ContainerListOptions{All: true})
if err != nil {
return false, err
}
for _, container := range containers {
containerNS, ok := container.Labels["com.docker.compose.project"]
if ok && containerNS == name {
return false, nil
}
}
return true, nil
}

View File

@@ -15,7 +15,6 @@ import (
httperrors "github.com/portainer/portainer/api/http/errors"
"github.com/portainer/portainer/api/http/security"
"github.com/portainer/portainer/api/internal/authorization"
"github.com/portainer/portainer/api/internal/stackutils"
)
func (handler *Handler) cleanUp(stack *portainer.Stack, doCleanUp *bool) error {
@@ -30,29 +29,7 @@ func (handler *Handler) cleanUp(stack *portainer.Stack, doCleanUp *bool) error {
return nil
}
// @id StackCreate
// @summary Deploy a new stack
// @description Deploy a new stack into a Docker environment specified via the endpoint identifier.
// @description **Access policy**: restricted
// @tags stacks
// @security jwt
// @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)
// @param endpointId query int true "Identifier of the endpoint that will be used to deploy the stack"
// @param body_swarm_string body swarmStackFromFileContentPayload false "Required when using method=string and type=1"
// @param body_swarm_repository body swarmStackFromGitRepositoryPayload false "Required when using method=repository and type=1"
// @param body_compose_string body composeStackFromFileContentPayload false "Required when using method=string and type=2"
// @param body_compose_repository body composeStackFromGitRepositoryPayload false "Required when using method=repository and type=2"
// @param Name formData string false "Name of the stack. required when method is file"
// @param SwarmID formData string false "Swarm cluster identifier. Required when method equals file and type equals 1. required when method is file"
// @param Env formData string false "Environment variables passed during deployment, represented as a JSON array [{'name': 'name', 'value': 'value'}]. Optional, used when method equals file and type equals 1."
// @param file formData file false "Stack file. required when method is file"
// @success 200 {object} portainer.CustomTemplate
// @failure 400 "Invalid request"
// @failure 500 "Server error"
// @router /stacks [post]
// POST request on /api/stacks?type=<type>&method=<method>&endpointId=<endpointId>
func (handler *Handler) stackCreate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
stackType, err := request.RetrieveNumericQueryParameter(r, "type", false)
if err != nil {
@@ -209,9 +186,9 @@ func (handler *Handler) decorateStackResponse(w http.ResponseWriter, stack *port
}
if isAdmin {
resourceControl = authorization.NewAdministratorsOnlyResourceControl(stackutils.ResourceControlID(stack.EndpointID, stack.Name), portainer.StackResourceControl)
resourceControl = authorization.NewAdministratorsOnlyResourceControl(stack.Name, portainer.StackResourceControl)
} else {
resourceControl = authorization.NewPrivateResourceControl(stackutils.ResourceControlID(stack.EndpointID, stack.Name), portainer.StackResourceControl, userID)
resourceControl = authorization.NewPrivateResourceControl(stack.Name, portainer.StackResourceControl, userID)
}
err = handler.DataStore.ResourceControl().CreateResourceControl(resourceControl)

View File

@@ -12,24 +12,11 @@ import (
bolterrors "github.com/portainer/portainer/api/bolt/errors"
httperrors "github.com/portainer/portainer/api/http/errors"
"github.com/portainer/portainer/api/http/security"
"github.com/portainer/portainer/api/internal/stackutils"
)
// @id StackDelete
// @summary Remove a stack
// @description Remove a stack.
// @description **Access policy**: restricted
// @tags stacks
// @security jwt
// @param id path int true "Stack identifier"
// @param external query boolean false "Set to true to delete an external stack. Only external Swarm stacks are supported"
// @param endpointId query int false "Endpoint identifier used to remove an external stack (required when external is set to true)"
// @success 204 "Success"
// @failure 400 "Invalid request"
// @failure 403 "Permission denied"
// @failure 404 " not found"
// @failure 500 "Server error"
// @router /stacks/{id} [delete]
// DELETE request on /api/stacks/:id?external=<external>&endpointId=<endpointId>
// If the external query parameter is set to true, the id route variable is expected to be
// the name of an external stack as a string.
func (handler *Handler) stackDelete(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
stackID, err := request.RetrieveRouteVariableValue(r, "id")
if err != nil {
@@ -83,7 +70,7 @@ func (handler *Handler) stackDelete(w http.ResponseWriter, r *http.Request) *htt
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err}
}
resourceControl, err := handler.DataStore.ResourceControl().ResourceControlByResourceIDAndType(stackutils.ResourceControlID(stack.EndpointID, stack.Name), portainer.StackResourceControl)
resourceControl, err := handler.DataStore.ResourceControl().ResourceControlByResourceIDAndType(stack.Name, portainer.StackResourceControl)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve a resource control associated to the stack", err}
}

View File

@@ -7,32 +7,17 @@ import (
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api"
bolterrors "github.com/portainer/portainer/api/bolt/errors"
"github.com/portainer/portainer/api/http/errors"
"github.com/portainer/portainer/api/http/security"
"github.com/portainer/portainer/api/internal/stackutils"
)
type stackFileResponse struct {
// Content of the Stack file
StackFileContent string `json:"StackFileContent" example:"version: 3\n services:\n web:\n image:nginx"`
StackFileContent string `json:"StackFileContent"`
}
// @id StackFileInspect
// @summary Retrieve the content of the Stack file for the specified stack
// @description Get Stack file content.
// @description **Access policy**: restricted
// @tags stacks
// @security jwt
// @produce json
// @param id path int true "Stack identifier"
// @success 200 {object} stackFileResponse "Success"
// @failure 400 "Invalid request"
// @failure 403 "Permission denied"
// @failure 404 "Stack not found"
// @failure 500 "Server error"
// @router /stacks/{id}/file [get]
// GET request on /api/stacks/:id/file
func (handler *Handler) stackFile(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
stackID, err := request.RetrieveNumericRouteVariableValue(r, "id")
if err != nil {
@@ -58,7 +43,7 @@ func (handler *Handler) stackFile(w http.ResponseWriter, r *http.Request) *httpe
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err}
}
resourceControl, err := handler.DataStore.ResourceControl().ResourceControlByResourceIDAndType(stackutils.ResourceControlID(stack.EndpointID, stack.Name), portainer.StackResourceControl)
resourceControl, err := handler.DataStore.ResourceControl().ResourceControlByResourceIDAndType(stack.Name, portainer.StackResourceControl)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve a resource control associated to the stack", err}
}

View File

@@ -6,27 +6,13 @@ import (
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api"
bolterrors "github.com/portainer/portainer/api/bolt/errors"
"github.com/portainer/portainer/api/http/errors"
"github.com/portainer/portainer/api/http/security"
"github.com/portainer/portainer/api/internal/stackutils"
)
// @id StackInspect
// @summary Inspect a stack
// @description Retrieve details about a stack.
// @description **Access policy**: restricted
// @tags stacks
// @security jwt
// @produce json
// @param id path int true "Stack identifier"
// @success 200 {object} portainer.Stack "Success"
// @failure 400 "Invalid request"
// @failure 403 "Permission denied"
// @failure 404 "Stack not found"
// @failure 500 "Server error"
// @router /stacks/{id} [get]
// GET request on /api/stacks/:id
func (handler *Handler) stackInspect(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
stackID, err := request.RetrieveNumericRouteVariableValue(r, "id")
if err != nil {
@@ -57,7 +43,7 @@ func (handler *Handler) stackInspect(w http.ResponseWriter, r *http.Request) *ht
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve user info from request context", err}
}
resourceControl, err := handler.DataStore.ResourceControl().ResourceControlByResourceIDAndType(stackutils.ResourceControlID(stack.EndpointID, stack.Name), portainer.StackResourceControl)
resourceControl, err := handler.DataStore.ResourceControl().ResourceControlByResourceIDAndType(stack.Name, portainer.StackResourceControl)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve a resource control associated to the stack", err}
}

View File

@@ -6,7 +6,7 @@ import (
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/http/security"
"github.com/portainer/portainer/api/internal/authorization"
)
@@ -16,20 +16,7 @@ type stackListOperationFilters struct {
EndpointID int `json:"EndpointID"`
}
// @id StackList
// @summary List stacks
// @description List all stacks based on the current user authorizations.
// @description Will return all stacks if using an administrator account otherwise it
// @description will only return the list of stacks the user have access to.
// @description **Access policy**: restricted
// @tags stacks
// @security jwt
// @param filters query string false "Filters to process on the stack list. Encoded as JSON (a map[string]string). For example, {"SwarmID": "jpofkc0i9uo9wtx1zesuk649w"} will only return stacks that are part of the specified Swarm cluster. Available filters: EndpointID, SwarmID."
// @success 200 {array} portainer.Stack "Success"
// @success 204 "Success"
// @failure 400 "Invalid request"
// @failure 500 "Server error"
// @router /stacks [get]
// GET request on /api/stacks?(filters=<filters>)
func (handler *Handler) stackList(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
var filters stackListOperationFilters
err := request.RetrieveJSONQueryParameter(r, "filters", &filters, true)

View File

@@ -2,26 +2,21 @@ package stacks
import (
"errors"
"fmt"
"net/http"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api"
bolterrors "github.com/portainer/portainer/api/bolt/errors"
httperrors "github.com/portainer/portainer/api/http/errors"
"github.com/portainer/portainer/api/http/security"
"github.com/portainer/portainer/api/internal/stackutils"
)
type stackMigratePayload struct {
// Endpoint identifier of the target endpoint where the stack will be relocated
EndpointID int `example:"2" validate:"required"`
// Swarm cluster identifier, must match the identifier of the cluster where the stack will be relocated
SwarmID string `example:"jpofkc0i9uo9wtx1zesuk649w"`
// If provided will rename the migrated stack
Name string `example:"new-stack"`
EndpointID int
SwarmID string
Name string
}
func (payload *stackMigratePayload) Validate(r *http.Request) error {
@@ -31,22 +26,7 @@ func (payload *stackMigratePayload) Validate(r *http.Request) error {
return nil
}
// @id StackMigrate
// @summary Migrate a stack to another endpoint
// @description Migrate a stack from an endpoint to another endpoint. It will re-create the stack inside the target endpoint before removing the original stack.
// @description **Access policy**: restricted
// @tags stacks
// @security jwt
// @produce json
// @param id path int true "Stack identifier"
// @param endpointId query int false "Stacks created before version 1.18.0 might not have an associated endpoint identifier. Use this optional parameter to set the endpoint identifier used by the stack."
// @param body body stackMigratePayload true "Stack migration details"
// @success 200 {object} portainer.Stack "Success"
// @failure 400 "Invalid request"
// @failure 403 "Permission denied"
// @failure 404 "Stack not found"
// @failure 500 "Server error"
// @router /stacks/{id}/migrate [post]
// POST request on /api/stacks/:id/migrate?endpointId=<endpointId>
func (handler *Handler) stackMigrate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
stackID, err := request.RetrieveNumericRouteVariableValue(r, "id")
if err != nil {
@@ -78,7 +58,7 @@ func (handler *Handler) stackMigrate(w http.ResponseWriter, r *http.Request) *ht
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err}
}
resourceControl, err := handler.DataStore.ResourceControl().ResourceControlByResourceIDAndType(stackutils.ResourceControlID(stack.EndpointID, stack.Name), portainer.StackResourceControl)
resourceControl, err := handler.DataStore.ResourceControl().ResourceControlByResourceIDAndType(stack.Name, portainer.StackResourceControl)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve a resource control associated to the stack", err}
}
@@ -124,16 +104,6 @@ func (handler *Handler) stackMigrate(w http.ResponseWriter, r *http.Request) *ht
stack.Name = payload.Name
}
isUnique, err := handler.checkUniqueName(targetEndpoint, stack.Name, stack.ID, stack.SwarmID != "")
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to check for name collision", err}
}
if !isUnique {
errorMessage := fmt.Sprintf("A stack with the name '%s' is already running on endpoint '%s'", stack.Name, targetEndpoint.Name)
return &httperror.HandlerError{http.StatusConflict, errorMessage, errors.New(errorMessage)}
}
migrationError := handler.migrateStack(r, stack, targetEndpoint)
if migrationError != nil {
return migrationError

View File

@@ -2,13 +2,11 @@ package stacks
import (
"errors"
"fmt"
"net/http"
portainer "github.com/portainer/portainer/api"
httperrors "github.com/portainer/portainer/api/http/errors"
"github.com/portainer/portainer/api/http/security"
"github.com/portainer/portainer/api/internal/stackutils"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
@@ -16,19 +14,7 @@ import (
bolterrors "github.com/portainer/portainer/api/bolt/errors"
)
// @id StackStart
// @summary Starts a stopped Stack
// @description Starts a stopped Stack.
// @description **Access policy**: restricted
// @tags stacks
// @security jwt
// @param id path int true "Stack identifier"
// @success 200 {object} portainer.Stack "Success"
// @failure 400 "Invalid request"
// @failure 403 "Permission denied"
// @failure 404 " not found"
// @failure 500 "Server error"
// @router /stacks/{id}/start [post]
// POST request on /api/stacks/:id/start
func (handler *Handler) stackStart(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
stackID, err := request.RetrieveNumericRouteVariableValue(r, "id")
if err != nil {
@@ -59,16 +45,7 @@ func (handler *Handler) stackStart(w http.ResponseWriter, r *http.Request) *http
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err}
}
isUnique, err := handler.checkUniqueName(endpoint, stack.Name, stack.ID, stack.SwarmID != "")
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to check for name collision", err}
}
if !isUnique {
errorMessage := fmt.Sprintf("A stack with the name '%s' is already running", stack.Name)
return &httperror.HandlerError{http.StatusConflict, errorMessage, errors.New(errorMessage)}
}
resourceControl, err := handler.DataStore.ResourceControl().ResourceControlByResourceIDAndType(stackutils.ResourceControlID(stack.EndpointID, stack.Name), portainer.StackResourceControl)
resourceControl, err := handler.DataStore.ResourceControl().ResourceControlByResourceIDAndType(stack.Name, portainer.StackResourceControl)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve a resource control associated to the stack", err}
}
@@ -87,7 +64,7 @@ func (handler *Handler) stackStart(w http.ResponseWriter, r *http.Request) *http
err = handler.startStack(stack, endpoint)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to start stack", err}
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to stop stack", err}
}
stack.Status = portainer.StackStatusActive

Some files were not shown because too many files have changed in this diff Show More