Compare commits

...

66 Commits

Author SHA1 Message Date
Anthony Lapenna 02362defde Merge branch 'release/1.19.2' 2018-09-15 16:40:38 +08:00
Anthony Lapenna 57bd82ba85 chore(version): bump version number 2018-09-15 16:40:26 +08:00
Anthony Lapenna e2258f98cc fix(services): only display logs action when container has ID in agent proxy mode 2018-09-15 10:33:33 +08:00
Anthony Lapenna bab02f2b91 fix(container-details): update container restart policy init 2018-09-15 10:19:51 +08:00
Anthony Lapenna 77913543b1 feat(container-details): update container-restart-policy component (#2273) 2018-09-15 09:53:35 +08:00
Anthony Lapenna b24891a6bc refactor(api): introduce libhttp usage (#2263) 2018-09-10 12:01:38 +02:00
Anthony Lapenna 42f5aec6a5 feat(container-console): increase hijacked tcp connection reader size (#2259) 2018-09-07 11:24:18 +02:00
Anthony Lapenna 7ba19ee1f9 fix(api): change user password update flow (#2247)
* fix(api): change password update flow

* feat(update-password): add current password confirmation
2018-09-05 08:49:43 +02:00
Anthony Lapenna 736f61dc2f fix(snapshots): close Docker client after snapshot (#2235) 2018-09-05 08:44:04 +02:00
Anthony Lapenna 0b8f7f6cea refactor(api): update error message for /users/admin/init 2018-09-03 20:18:04 +02:00
Kendrick 0efeeaf185 feat(webhooks): add support for service update webhooks (#2161)
* Initial pass at adding webhook controller and routes

* Moving some objects around

* Cleaning up comments

* Fixing syntax, switching to using the docker sdk over building an http client

* Adding delete and list functionality

* Updating the handler to use the correct permissions. Updating some comments

* Fixing some comments

* Code cleanup per pull request comments

* Cleanup per PR feedback. Syntax error fix

* Initial creation of webhook app code

* Moving ClientFactory creation out of handler code and instead using the one created by the main process. Removing webhookInspect method and updating the list function to use json filters

* Delete now works on the webhook ID vs service ID

* WIP - Service creates a webhook. Display will show an existing webhook URL.

* Adding the webhook field to the service view. There is now the ability to add or remove a webhook from a service

* Moving all api calls to be webhooks vs webhook

* Code cleanup. Moving all api calls to be webhooks vs webhook

* More conversion of webhook to webhooks?

* Moving UI elements around. Starting function for copying to clipboard

* Finalizing function for copying to clipboard. Adding button that calls function and copies webhook to clipboard.

* Fixing UI issues. Hiding field entirely when there is no webhook

* Moving URL crafting to a helper method. The edit pane for service now creates/deletes webhooks immidiately.

* style(service-details): update webhook line

* feat(api): strip sha when updating an image via the update webhook

* Fixing up some copy. Only displying the port if it is not http or https

* Fixing tooltip copy. Setting the forceupdate to be true to require an update to occur

* Fixing code climate errors

* Adding WebhookType field and setting to ServiceWebhook for new webhooks. Renaming ServiceID to resourceID so future work can add new types of webhooks in other resource areas.

* Adding the webhook type to the payload to support more types of webhooks in the future. Setting the type correctly when creating one for a service

* feat(webhooks): changes related to webhook management

* API code cleanup, removing unneeded functions, and updating validation logic

* Incorrectly ignoring the error that the webhook did not exist

* Re-adding missing error handling. Changing error response to be a 404 vs 500 when token can't find an object

* fix(webhooks): close Docker client after service webhook execution
2018-09-03 12:08:03 +02:00
Anthony Lapenna d5facde9d4 fix(api): fix invalid error message in endpoint creation handler (#2233) 2018-09-02 10:35:05 +02:00
classmember e17c873e73 refactor(build-system): update build_in_container.sh (#2230)
wrapped `$(pwd)/api:/src` in `"` quotes to prevent word splitting on the `-tv` option
2018-09-01 10:09:24 +02:00
Anthony Lapenna 84fc3119a0 docs(swagger): update StackCreate operation parameter 2018-08-30 13:11:15 +02:00
Anthony Lapenna 887c16c580 feat(api): display details in error response (#2228) 2018-08-30 12:21:53 +02:00
Anthony Lapenna a5d6ab0410 refactor(app): remove unused params in templates state declaration 2018-08-28 10:50:15 +02:00
Anthony Lapenna 812f3e3e85 feat(auth): remove sanitization calls and ask for password update if needed (#2222)
* wip

* feat(auth): remove sanitization calls and ask for password update if needed
2018-08-28 10:13:01 +02:00
aksappy bfccf55729 fix(images): Fix upload modal to allow both tar and tar.gz images (#2218) 2018-08-27 21:43:58 +02:00
Anthony Lapenna 538a2b5ee2 fix(service-details): disable auto-focus on task datatable (#2214)
* fix(service-details): disable auto-focus on task datatable

* refactor(api): gofmt main.go
2018-08-24 14:30:41 +02:00
William Easton c941fac2cc fix(api): set templatesURL in settings when using the --templates flag
Re-add the CLI for external template management
2018-08-24 13:08:46 +02:00
Anthony Lapenna 4b05699e66 chore(codeclimate): update .codeclimate.yml (#2212)
* chore(codeclimate): update .codeclimate.yml

* chore(codeclimate): update .codeclimate.yml

* chore(codeclimate): update .codeclimate.yml
2018-08-24 10:40:05 +02:00
Anthony Lapenna 8cd3964d75 feat(security): update secured headers and sanitize team name (#2167) 2018-08-23 17:10:18 +02:00
Chaim Lev-Ari e58acd7dd6 * chore(eslint): update esllint and remove unused variables
* chore(eslint-config): change no-unused-vars to warn

* chore(eslint): remove unused variables

* chore(eslint): allow unused globals

* fixup! chore(eslint): allow unused globals

* chore(eslint): remove commented unused vars

* fixup! chore(eslint): remove commented unused vars
2018-08-22 17:33:06 +02:00
Anthony Lapenna 46da95ecfb feat(motd): ignore loading for motd 2018-08-22 13:18:02 +02:00
Luca 68d77e5e0e feat(networks): add details about the attachable/internal properties (#2200) 2018-08-22 08:45:14 +02:00
Luca e8ab89ae79 feat(config-details): add the ability to clone a config (#2189) 2018-08-22 08:41:02 +02:00
Anthony Lapenna 6ab6cfafb7 feat(motd): add the ability to display motd and dimiss information panels (#2191)
* feat(api): add motd handler

* feat(app): add the motd api layer

* feat(motd): display motd and add the ability to dismiss information messages

* style(home): relocate important message before info01

* feat(api): silently fail when an error occurs during motd retrieval
2018-08-21 20:40:42 +02:00
Chaim Lev-Ari 74ca908759 fix(stack-details): pass agentProxy as an argument (#2196) 2018-08-21 12:11:39 +02:00
Anthony Lapenna e60d809154 fix(container-creation): fix an issue with container-edition and UAC 2018-08-20 21:06:30 +02:00
Anthony Lapenna 64beaaa279 feat(container-details): update re-creation flow (#2193) 2018-08-20 20:55:12 +02:00
baron_l 1b51daf9c4 fix(services): fix invalid replica count (#1990) (#2127)
* fix(services): replicas numbers display is now correct with constraints and down nodes

* refactor(helpers): constraint helper has less complexity

* feat(services): constraints on node/engine labels are now supported

* refactor(helpers): ConstraintsHelper - remove regex patterns and improve code lisibility

* refactor(helpers): rework matchesConstraint() for better code lisibility and lodash find() instead for IE compatibility
2018-08-19 08:05:16 +02:00
Ricardo Cardona Ramirez e1e263d8c8 feat(UAC): change default ownership to admininstrators (#2137)
* #960 feat(UAC): change ownership to admins for externally created ressources

* feat(UAC): change ownership to admins for externally created resources

Deprecated AdministratorsOnly js and go backend

* #960 feat(UAC): remove AdministratorsOnly property and minor GUI  fixes

Update swagger definition changing AdministratorsOnly to Public

* #960 feat(UAC): fix create resource with access control data

* #960 feat(UAC): authorization of non-admin users for restricted operations

On stacks, containers networks, services , tasks and volumes.

* #960 feat(UAC): database migration to version 14

 The administrator resources are deleted and Public resources are now managed by admins

* #960 feat(UAC):  small fixes from PR #2137

* #960 feat(UAC): improve the readability of the source code

* feat(UAC) fix displayed ownership for Swarm related  resources  (#960)
2018-08-19 07:57:28 +02:00
Hasnat 31c2a6d9e7 feat(container-console): Adds custom commands based on container labels (#2159)
* feat(console): Adds custom commands based on container labels

* feat(console): Update custom commands label prefix
2018-08-18 10:31:01 +02:00
Chaim Lev-Ari 102e63e1e5 refactor(container-creation): change order of container re-creation/duplication steps
* refactor(container-creation): change order of container creation steps

* refactor(container-creation):  remove nested methods

* fix(container-creation): skip actions if old container missing

* fix(container-creation): reject if user is not authorized

* fix(container-creation): remove rejection on invalid form

* refactor(container-creation): start container after duplicate

* fix(container-creation): add form validation error message

* fix(container-creation): pass correct id to create resource control

* fix(container-creation): set action in progress after confirmation
2018-08-18 10:27:24 +02:00
Chaim Lev-Ari 7e08227ddb feat(build-system): add build-offline script (#2169) 2018-08-17 08:37:31 +02:00
baron_l bda5eac0c1 feat(network-creation): enhance UX with macvlan driver for swarm mode (#2082) (#2122)
* feat(network-creation): macvlan driver for swarm

* refactor(network-creation): layout rework to make it simpler with MACVLAN and keep it consistent with other drivers

* fix(network-creation): MACVLAN - parent network card is now properly saved, names are not prefixed anymore and the --attachable option is now supported

* refactor(network-creation): PR macvlan review - rework of macvlan view + code optimisation

* fix(network-creation): disable attachable and internal options on macvlan config creation
2018-08-16 12:29:15 +02:00
Chaim Lev-Ari 8769fadd5c feat(container-details): add the ability to update restart policy 2018-08-16 11:31:00 +02:00
Ru Fan de9f99d030 feat(container-creation): add runtime option in (#2162) (#2163) 2018-08-16 11:28:06 +02:00
Anthony Lapenna 55f719128b docs(README): update build badge 2018-08-15 21:02:01 +02:00
Chaim Lev-Ari 594daf0de8 fix(home): Show correct number of cpus and total memory for swarm (#2147)
* fix(home): show cpu/mem for swarm

* fix(home): add nodes data to snapshot

* fix(dashboard): get cpus/mem from snapshot

* refactor(home): remove temp variable
2018-08-13 21:20:56 +02:00
Chaim Lev-Ari f3dc67a852 fix(container-details): change order of container recreation 2018-08-13 21:13:42 +02:00
Anthony Lapenna 1233cb7f08 chore(project): update lodash version to 4.17.10 (#2156) 2018-08-13 19:10:09 +02:00
Chaim Lev-Ari d4e4d34ea4 chore(build-system): add dev, clean, build scripts (#2146) 2018-08-13 17:28:59 +02:00
Anthony Lapenna df1592a3d2 feat(templates): add datadog agent templates 2018-08-13 14:06:54 +02:00
salcedo cbe4cc92db feat(templates): update file browser image (#2152) 2018-08-13 09:11:54 +02:00
Anthony Lapenna 80c2adfc53 chore(ci): remove codefresh workflows (#2144) 2018-08-09 17:53:25 +02:00
baron_l 9c0b568773 feat(container-creation): container add/drop capabilities on creation (#468) (#2078)
* feat(container-creation): container add/drop capabilities on creation

* feat(container-creation): capabilities are now loaded on edit/duplicate/update
2018-08-09 10:40:06 +02:00
baron_l 5222413532 feat(volume-creation) : NFS volume creation (#2083) (#2108)
* feat(volume-creation): NFS support for volume creation - layout

* feat(volume-creation): NFS support for volume creation

* fix(volume-creation): NFS style, display and check on submit

* refactor(volume-creation): remove useless controller + refactor var naming

* refactor(volume-creation): NFS wording, help and style
2018-08-09 10:33:16 +02:00
Anthony Lapenna ee9c8d7d1a feat(templates): re-introduce external template management (#2119)
* feat(templates): re-introduce external template management

* refactor(api): review error handling
2018-08-07 17:43:36 +02:00
Chaim Lev-Ari 09cb8e7350 chore(gitignore): add .vscode to .gitignore (#2130) 2018-08-06 15:32:27 +02:00
Chaim Lev-Ari 8dfa129129 fix(dashboard): update stopped/running container filters 2018-08-06 15:09:23 +02:00
Kendrick 0ae10c6f82 feat(container-details): add the image name to the container details in addition to the sha (#1369) (#2121) 2018-08-02 21:00:58 +02:00
Olli Janatuinen 892276b105 feat(build-system): add Dockerfile for Windows server 2016 (#2117) 2018-08-02 16:52:36 +02:00
Anthony Lapenna aa36adc5fd chore(project): update CONTRIBUTING.md 2018-08-02 09:39:43 +02:00
Anthony Lapenna 2216bd6e80 style(home): only display CPU/MEM for standalone endpoints 2018-07-31 11:58:08 +02:00
Anthony Lapenna 5f79547138 fix(api): filter sensitive information from API response (#2103) 2018-07-31 11:50:04 +02:00
Anthony Lapenna b8ed6d3d4a chore(version): bump version number 2018-07-28 20:42:17 +02:00
Anthony Lapenna 252af86cea fix(build-system): fix an invalid condition in shell_downloadDockerBinary task 2018-07-28 20:35:01 +02:00
Anthony Lapenna 8c5b80cefd Merge tag '1.19.1' into develop
Release 1.19.1
2018-07-28 19:46:20 +02:00
Anthony Lapenna e94a725a8a Merge branch 'release/1.19.1' 2018-07-28 19:46:14 +02:00
Anthony Lapenna b15af67552 chore(version): bump version number 2018-07-28 19:44:01 +02:00
Anthony Lapenna 29cd952a0b feat(home): display refresh button if --no-auth enabled 2018-07-28 18:12:03 +02:00
Anthony Lapenna 6e072dbcdf fix(build-system): fix the downloadDockerBinary task 2018-07-28 16:45:44 +02:00
Anthony Lapenna 024739f9f1 fix(authentication): fix an issue with the --no-auth flag (#2090) 2018-07-28 16:38:26 +02:00
Anthony Lapenna 2e0d1f289c fix(build-system): fix invalid template copy step (#2089) 2018-07-28 16:12:24 +02:00
Anthony Lapenna 8cca3de70b Merge tag '1.19.0' into develop
Release 1.19.0
2018-07-27 16:06:07 +02:00
283 changed files with 3528 additions and 1591 deletions
+40 -8
View File
@@ -1,5 +1,42 @@
---
engines:
version: "2"
checks:
argument-count:
enabled: true
config:
threshold: 4
complex-logic:
enabled: true
config:
threshold: 4
file-lines:
enabled: true
config:
threshold: 300
method-complexity:
enabled: false
method-count:
enabled: true
config:
threshold: 20
method-lines:
enabled: true
config:
threshold: 50
nested-control-flow:
enabled: true
config:
threshold: 4
return-statements:
enabled: false
similar-code:
enabled: true
config:
threshold: #language-specific defaults. overrides affect all languages.
identical-code:
enabled: true
config:
threshold: #language-specific defaults. overrides affect all languages.
plugins:
gofmt:
enabled: true
golint:
@@ -20,10 +57,5 @@ engines:
config: .eslintrc.yml
fixme:
enabled: true
ratings:
paths:
- "**.css"
- "**.js"
- "**.go"
exclude_paths:
exclude_patterns:
- test/
-46
View File
@@ -1,46 +0,0 @@
version: '1.0'
steps:
build_backend:
image: portainer/golang-builder:ci
working_directory: ${{main_clone}}
commands:
- mkdir -p /go/src/github.com/${{CF_REPO_OWNER}}
- ln -s /codefresh/volume/${{CF_REPO_NAME}}/api /go/src/github.com/${{CF_REPO_OWNER}}/${{CF_REPO_NAME}}
- /build.sh api/cmd/portainer
build_frontend:
image: portainer/angular-builder:latest
working_directory: ${{build_backend}}
commands:
- yarn
- yarn grunt build-webapp
- mv api/cmd/portainer/portainer dist/
get_docker_version:
image: alpine:3.7
working_directory: ${{build_frontend}}
commands:
- cf_export DOCKER_VERSION=`cat gruntfile.js | grep -m 1 'shippedDockerVersion' | cut -d\' -f2`
download_docker_binary:
image: busybox
working_directory: ${{build_frontend}}
commands:
- echo ${{DOCKER_VERSION}}
- wget -O /tmp/docker-binaries.tgz https://download.docker.com/linux/static/stable/x86_64/docker-${{DOCKER_VERSION}}.tgz
- tar -xf /tmp/docker-binaries.tgz -C /tmp
- mv /tmp/docker/docker dist/
build_image:
type: build
working_directory: ${{download_docker_binary}}
dockerfile: ./build/linux/Dockerfile
image_name: portainer/portainer
tag: ${{CF_BRANCH}}
push_image:
type: push
candidate: '${{build_image}}'
tag: '${{CF_BRANCH}}'
registry: dockerhub
-46
View File
@@ -1,46 +0,0 @@
version: '1.0'
steps:
build_backend:
image: portainer/golang-builder:ci
working_directory: ${{main_clone}}
commands:
- mkdir -p /go/src/github.com/${{CF_REPO_OWNER}}
- ln -s /codefresh/volume/${{CF_REPO_NAME}}/api /go/src/github.com/${{CF_REPO_OWNER}}/${{CF_REPO_NAME}}
- /build.sh api/cmd/portainer
build_frontend:
image: portainer/angular-builder:latest
working_directory: ${{build_backend}}
commands:
- yarn
- yarn grunt build-webapp
- mv api/cmd/portainer/portainer dist/
get_docker_version:
image: alpine:3.7
working_directory: ${{build_frontend}}
commands:
- cf_export DOCKER_VERSION=`cat gruntfile.js | grep -m 1 'shippedDockerVersion' | cut -d\' -f2`
download_docker_binary:
image: busybox
working_directory: ${{build_frontend}}
commands:
- echo ${{DOCKER_VERSION}}
- wget -O /tmp/docker-binaries.tgz https://download.docker.com/linux/static/stable/x86_64/docker-${{DOCKER_VERSION}}.tgz
- tar -xf /tmp/docker-binaries.tgz -C /tmp
- mv /tmp/docker/docker dist/
build_image:
type: build
working_directory: ${{download_docker_binary}}
dockerfile: ./build/linux/Dockerfile
image_name: portainer/portainer
tag: ${{CF_BRANCH}}
push_image:
type: push
candidate: '${{build_image}}'
tag: 'pr${{CF_PULL_REQUEST_NUMBER}}'
registry: dockerhub
+4 -1
View File
@@ -141,7 +141,10 @@ rules:
no-undef-init: error
no-undef: off
no-undefined: off
no-unused-vars: off
no-unused-vars:
- warn
-
vars: local
no-use-before-define: off
# Node.js and CommonJS
+1
View File
@@ -4,3 +4,4 @@ dist
portainer-checksum.txt
api/cmd/portainer/portainer*
.tmp
.vscode
+21 -7
View File
@@ -2,7 +2,7 @@
Some basic conventions for contributing to this project.
### General
## General
Please make sure that there aren't existing pull requests attempting to address the issue mentioned. Likewise, please check for issues related to update, as someone else may be working on the issue in a branch or fork.
@@ -13,7 +13,7 @@ When creating a new branch, prefix it with the *type* of the change (see section
For example, if you work on a bugfix for the issue #361, you could name the branch `fix361-template-selection`.
### Issues open to contribution
## Issues open to contribution
Want to contribute but don't know where to start?
@@ -24,14 +24,14 @@ Some of the open issues are labeled with prefix `exp/`, this is used to mark the
either AngularJS or Golang
* **advanced**: a task that require a deep understanding of the project codebase
You can have a use Github filters to list these issues:
You can use Github filters to list these issues:
* beginner labeled issues: https://github.com/portainer/portainer/labels/exp%2Fbeginner
* intermediate labeled issues: https://github.com/portainer/portainer/labels/exp%2Fintermediate
* advanced labeled issues: https://github.com/portainer/portainer/labels/exp%2Fadvanced
### Commit Message Format
## Commit Message Format
Each commit message should include a **type**, a **scope** and a **subject**:
@@ -47,7 +47,7 @@ Lines should not exceed 100 characters. This allows the message to be easier to
#269 style(dashboard): update dashboard with new layout
```
#### Type
### Type
Must be one of the following:
@@ -61,16 +61,30 @@ Must be one of the following:
* **chore**: Changes to the build process or auxiliary tools and libraries such as documentation
generation
#### Scope
### Scope
The scope could be anything specifying place of the commit change. For example `networks`,
`containers`, `images` etc...
You can use the **area** label tag associated on the issue here (for `area/containers` use `containers` as a scope...)
#### Subject
### Subject
The subject contains succinct description of the change:
* use the imperative, present tense: "change" not "changed" nor "changes"
* don't capitalize first letter
* no dot (.) at the end
## Contribution process
Our contribution process is described below. Some of the steps can be visualized inside Github via specific `contrib/` labels, such as `contrib/func-review-in-progress` or `contrib/tech-review-approved`.
### Bug report
![portainer_bugreport_workflow](https://user-images.githubusercontent.com/5485061/43569306-5571b3a0-9637-11e8-8559-786cfc82a14f.png)
### Feature request
The feature request process is similar to the bug report process but has an extra functional validation before the technical validation.
![portainer_featurerequest_workflow](https://user-images.githubusercontent.com/5485061/43569315-5d30a308-9637-11e8-8292-3c62b5612925.png)
+1 -1
View File
@@ -6,7 +6,7 @@
[![Docker Pulls](https://img.shields.io/docker/pulls/portainer/portainer.svg)](https://hub.docker.com/r/portainer/portainer/)
[![Microbadger](https://images.microbadger.com/badges/image/portainer/portainer.svg)](http://microbadger.com/images/portainer/portainer "Image size")
[![Documentation Status](https://readthedocs.org/projects/portainer/badge/?version=stable)](http://portainer.readthedocs.io/en/stable/?badge=stable)
[![Codefresh build status]( https://g.codefresh.io/api/badges/build?repoOwner=portainer&repoName=portainer&branch=develop&pipelineName=portainer-ci&accountName=deviantony&type=cf-1)]( https://g.codefresh.io/repositories/portainer/portainer/builds?filter=trigger:build;branch:develop;service:5922a08a3a1aab000116fcc6~portainer-ci)
[![Build Status](https://semaphoreci.com/api/v1/portainer/portainer/branches/develop/badge.svg)](https://semaphoreci.com/portainer/portainer)
[![Code Climate](https://codeclimate.com/github/portainer/portainer/badges/gpa.svg)](https://codeclimate.com/github/portainer/portainer)
[![Slack](https://portainer.io/slack/badge.svg)](https://portainer.io/slack/)
[![Gitter](https://badges.gitter.im/portainer/Lobby.svg)](https://gitter.im/portainer/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
+8
View File
@@ -21,6 +21,7 @@ import (
"github.com/portainer/portainer/bolt/template"
"github.com/portainer/portainer/bolt/user"
"github.com/portainer/portainer/bolt/version"
"github.com/portainer/portainer/bolt/webhook"
)
const (
@@ -47,6 +48,7 @@ type Store struct {
TemplateService *template.Service
UserService *user.Service
VersionService *version.Service
WebhookService *webhook.Service
}
// NewStore initializes a new Store and the associated services
@@ -232,5 +234,11 @@ func (store *Store) initServices() error {
}
store.VersionService = versionService
webhookService, err := webhook.NewService(store.db)
if err != nil {
return err
}
store.WebhookService = webhookService
return nil
}
+19
View File
@@ -0,0 +1,19 @@
package migrator
func (m *Migrator) updateResourceControlsToDBVersion14() error {
resourceControls, err := m.resourceControlService.ResourceControls()
if err != nil {
return err
}
for _, resourceControl := range resourceControls {
if resourceControl.AdministratorsOnly == true {
err = m.resourceControlService.DeleteResourceControl(resourceControl.ID)
if err != nil {
return err
}
}
}
return nil
}
+8
View File
@@ -178,5 +178,13 @@ func (m *Migrator) Migrate() error {
}
}
// Portainer 1.19.2
if m.currentDBVersion < 14 {
err := m.updateResourceControlsToDBVersion14()
if err != nil {
return err
}
}
return m.versionService.StoreDBVersion(portainer.DBVersion)
}
+151
View File
@@ -0,0 +1,151 @@
package webhook
import (
"github.com/portainer/portainer"
"github.com/portainer/portainer/bolt/internal"
"github.com/boltdb/bolt"
)
const (
// BucketName represents the name of the bucket where this service stores data.
BucketName = "webhooks"
)
// Service represents a service for managing webhook data.
type Service struct {
db *bolt.DB
}
// NewService creates a new instance of a service.
func NewService(db *bolt.DB) (*Service, error) {
err := internal.CreateBucket(db, BucketName)
if err != nil {
return nil, err
}
return &Service{
db: db,
}, nil
}
//Webhooks returns an array of all webhooks
func (service *Service) Webhooks() ([]portainer.Webhook, error) {
var webhooks = make([]portainer.Webhook, 0)
err := service.db.View(func(tx *bolt.Tx) error {
bucket := tx.Bucket([]byte(BucketName))
cursor := bucket.Cursor()
for k, v := cursor.First(); k != nil; k, v = cursor.Next() {
var webhook portainer.Webhook
err := internal.UnmarshalObject(v, &webhook)
if err != nil {
return err
}
webhooks = append(webhooks, webhook)
}
return nil
})
return webhooks, err
}
// Webhook returns a webhook by ID.
func (service *Service) Webhook(ID portainer.WebhookID) (*portainer.Webhook, error) {
var webhook portainer.Webhook
identifier := internal.Itob(int(ID))
err := internal.GetObject(service.db, BucketName, identifier, &webhook)
if err != nil {
return nil, err
}
return &webhook, nil
}
// WebhookByResourceID returns a webhook by the ResourceID it is associated with.
func (service *Service) WebhookByResourceID(ID string) (*portainer.Webhook, error) {
var webhook *portainer.Webhook
err := service.db.View(func(tx *bolt.Tx) error {
bucket := tx.Bucket([]byte(BucketName))
cursor := bucket.Cursor()
for k, v := cursor.First(); k != nil; k, v = cursor.Next() {
var w portainer.Webhook
err := internal.UnmarshalObject(v, &w)
if err != nil {
return err
}
if w.ResourceID == ID {
webhook = &w
break
}
}
if webhook == nil {
return portainer.ErrObjectNotFound
}
return nil
})
return webhook, err
}
// WebhookByToken returns a webhook by the random token it is associated with.
func (service *Service) WebhookByToken(token string) (*portainer.Webhook, error) {
var webhook *portainer.Webhook
err := service.db.View(func(tx *bolt.Tx) error {
bucket := tx.Bucket([]byte(BucketName))
cursor := bucket.Cursor()
for k, v := cursor.First(); k != nil; k, v = cursor.Next() {
var w portainer.Webhook
err := internal.UnmarshalObject(v, &w)
if err != nil {
return err
}
if w.Token == token {
webhook = &w
break
}
}
if webhook == nil {
return portainer.ErrObjectNotFound
}
return nil
})
return webhook, err
}
// DeleteWebhook deletes a webhook.
func (service *Service) DeleteWebhook(ID portainer.WebhookID) error {
identifier := internal.Itob(int(ID))
return internal.DeleteObject(service.db, BucketName, identifier)
}
// CreateWebhook assign an ID to a new webhook and saves it.
func (service *Service) CreateWebhook(webhook *portainer.Webhook) error {
return service.db.Update(func(tx *bolt.Tx) error {
bucket := tx.Bucket([]byte(BucketName))
id, _ := bucket.NextSequence()
webhook.ID = portainer.WebhookID(id)
data, err := internal.MarshalObject(webhook)
if err != nil {
return err
}
return bucket.Put(internal.Itob(int(webhook.ID)), data)
})
}
+13 -20
View File
@@ -178,6 +178,10 @@ func initSettings(settingsService portainer.SettingsService, flags *portainer.CL
SnapshotInterval: *flags.SnapshotInterval,
}
if *flags.Templates != "" {
settings.TemplatesURL = *flags.Templates
}
if *flags.Labels != nil {
settings.BlackListedLabels = *flags.Labels
} else {
@@ -193,6 +197,10 @@ func initSettings(settingsService portainer.SettingsService, flags *portainer.CL
}
func initTemplates(templateService portainer.TemplateService, fileService portainer.FileService, templateURL, templateFile string) error {
if templateURL != "" {
log.Printf("Portainer started with the --templates flag. Using external templates, template management will be disabled.")
return nil
}
existingTemplates, err := templateService.Templates()
if err != nil {
@@ -204,32 +212,14 @@ func initTemplates(templateService portainer.TemplateService, fileService portai
return nil
}
var templatesJSON []byte
if templateURL == "" {
return loadTemplatesFromFile(fileService, templateService, templateFile)
}
templatesJSON, err = client.Get(templateURL)
if err != nil {
log.Println("Unable to retrieve templates via HTTP")
return err
}
return unmarshalAndPersistTemplates(templateService, templatesJSON)
}
func loadTemplatesFromFile(fileService portainer.FileService, templateService portainer.TemplateService, templateFile string) error {
templatesJSON, err := fileService.GetFileContent(templateFile)
if err != nil {
log.Println("Unable to retrieve template via filesystem")
log.Println("Unable to retrieve template definitions via filesystem")
return err
}
return unmarshalAndPersistTemplates(templateService, templatesJSON)
}
func unmarshalAndPersistTemplates(templateService portainer.TemplateService, templateData []byte) error {
var templates []portainer.Template
err := json.Unmarshal(templateData, &templates)
err = json.Unmarshal(templatesJSON, &templates)
if err != nil {
log.Println("Unable to parse templates file. Please review your template definition file.")
return err
@@ -241,6 +231,7 @@ func unmarshalAndPersistTemplates(templateService portainer.TemplateService, tem
return err
}
}
return nil
}
@@ -514,6 +505,7 @@ func main() {
StackService: store.StackService,
TagService: store.TagService,
TemplateService: store.TemplateService,
WebhookService: store.WebhookService,
SwarmStackManager: swarmStackManager,
ComposeStackManager: composeStackManager,
CryptoService: cryptoService,
@@ -527,6 +519,7 @@ func main() {
SSL: *flags.SSL,
SSLCert: *flags.SSLCert,
SSLKey: *flags.SSLKey,
DockerClientFactory: clientFactory,
}
log.Printf("Starting Portainer %s on %s", portainer.APIVersion, *flags.Addr)
+1 -4
View File
@@ -3,7 +3,6 @@ package crypto
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/md5"
"crypto/rand"
"crypto/x509"
"encoding/base64"
@@ -97,9 +96,7 @@ func (service *ECDSAService) GenerateKeyPair() ([]byte, []byte, error) {
// that hash.
// It then encodes the generated signature in base64.
func (service *ECDSAService) Sign(message string) (string, error) {
digest := md5.New()
digest.Write([]byte(message))
hash := digest.Sum(nil)
hash := HashFromBytes([]byte(message))
r := big.NewInt(0)
s := big.NewInt(0)
+10
View File
@@ -0,0 +1,10 @@
package crypto
import "crypto/md5"
// HashFromBytes returns the hash of the specified data
func HashFromBytes(data []byte) []byte {
digest := md5.New()
digest.Write(data)
return digest.Sum(nil)
}
+21
View File
@@ -30,6 +30,11 @@ func snapshot(cli *client.Client) (*portainer.Snapshot, error) {
if err != nil {
return nil, err
}
err = snapshotNodes(snapshot, cli)
if err != nil {
return nil, err
}
}
err = snapshotContainers(snapshot, cli)
@@ -64,6 +69,22 @@ func snapshotInfo(snapshot *portainer.Snapshot, cli *client.Client) error {
return nil
}
func snapshotNodes(snapshot *portainer.Snapshot, cli *client.Client) error {
nodes, err := cli.NodeList(context.Background(), types.NodeListOptions{})
if err != nil {
return err
}
var nanoCpus int64
var totalMem int64
for _, node := range nodes {
nanoCpus += node.Description.Resources.NanoCPUs
totalMem += node.Description.Resources.MemoryBytes
}
snapshot.TotalCPU = int(nanoCpus / 1e9)
snapshot.TotalMemory = totalMem
return nil
}
func snapshotSwarmServices(snapshot *portainer.Snapshot, cli *client.Client) error {
stacks := make(map[string]struct{})
+1
View File
@@ -22,6 +22,7 @@ func (snapshotter *Snapshotter) CreateSnapshot(endpoint *portainer.Endpoint) (*p
if err != nil {
return nil, err
}
defer cli.Close()
return snapshot(cli)
}
+6
View File
@@ -93,3 +93,9 @@ type Error string
// Error returns the error message.
func (e Error) Error() string { return string(e) }
// Webhook errors
const (
ErrWebhookAlreadyExists = Error("A webhook for this resource already exists")
ErrUnsupportedWebhookType = Error("Webhooks for this resource are not currently supported")
)
+8
View File
@@ -13,6 +13,10 @@ import (
"github.com/portainer/portainer"
)
const (
errInvalidResponseStatus = portainer.Error("Invalid response status (expecting 200)")
)
// HTTPClient represents a client to send HTTP requests.
type HTTPClient struct {
*http.Client
@@ -75,6 +79,10 @@ func Get(url string) ([]byte, error) {
}
defer response.Body.Close()
if response.StatusCode != http.StatusOK {
return nil, errInvalidResponseStatus
}
body, err := ioutil.ReadAll(response.Body)
if err != nil {
return nil, err
-41
View File
@@ -1,41 +0,0 @@
package error
import (
"encoding/json"
"log"
"net/http"
)
type (
// LoggerHandler defines a HTTP handler that includes a HandlerError return pointer
LoggerHandler func(http.ResponseWriter, *http.Request) *HandlerError
// HandlerError represents an error raised inside a HTTP handler
HandlerError struct {
StatusCode int
Message string
Err error
}
errorResponse struct {
Err string `json:"err,omitempty"`
}
)
func (handler LoggerHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
err := handler(rw, r)
if err != nil {
writeErrorResponse(rw, err)
}
}
func writeErrorResponse(rw http.ResponseWriter, err *HandlerError) {
log.Printf("http error: %s (err=%s) (code=%d)\n", err.Message, err.Err, err.StatusCode)
rw.Header().Set("Content-Type", "application/json")
rw.WriteHeader(err.StatusCode)
json.NewEncoder(rw).Encode(&errorResponse{Err: err.Message})
}
// WriteError is a convenience function that creates a new HandlerError before calling writeErrorResponse.
// For use outside of the standard http handlers.
func WriteError(rw http.ResponseWriter, code int, message string, err error) {
writeErrorResponse(rw, &HandlerError{code, message, err})
}
+3 -3
View File
@@ -6,10 +6,10 @@ import (
"strings"
"github.com/asaskevich/govalidator"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
)
type authenticatePayload struct {
+1 -1
View File
@@ -4,8 +4,8 @@ import (
"net/http"
"github.com/gorilla/mux"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/security"
)
@@ -3,8 +3,8 @@ package dockerhub
import (
"net/http"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/response"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/response"
)
// GET request on /api/dockerhub
@@ -4,10 +4,10 @@ import (
"net/http"
"github.com/asaskevich/govalidator"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
)
type dockerhubUpdatePayload struct {
+1 -1
View File
@@ -4,8 +4,8 @@ import (
"net/http"
"github.com/gorilla/mux"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/security"
)
@@ -4,10 +4,10 @@ import (
"net/http"
"github.com/asaskevich/govalidator"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
)
type endpointGroupCreatePayload struct {
@@ -3,10 +3,10 @@ package endpointgroups
import (
"net/http"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
)
// DELETE request on /api/endpoint_groups/:id
@@ -3,10 +3,10 @@ package endpointgroups
import (
"net/http"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
)
// GET request on /api/endpoint_groups/:id
@@ -3,8 +3,8 @@ package endpointgroups
import (
"net/http"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/response"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer/http/security"
)
@@ -3,10 +3,10 @@ package endpointgroups
import (
"net/http"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
)
type endpointGroupUpdatePayload struct {
@@ -3,10 +3,10 @@ package endpointgroups
import (
"net/http"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
)
type endpointGroupUpdateAccessPayload struct {
+1 -1
View File
@@ -4,8 +4,8 @@ import (
"net/http"
"github.com/gorilla/mux"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/security"
)
+1 -1
View File
@@ -2,8 +2,8 @@ package endpointproxy
import (
"github.com/gorilla/mux"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/proxy"
"github.com/portainer/portainer/http/security"
)
@@ -3,9 +3,9 @@ package endpointproxy
import (
"strconv"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/request"
"net/http"
)
@@ -3,9 +3,9 @@ package endpointproxy
import (
"strconv"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/request"
"net/http"
)
@@ -3,9 +3,9 @@ package endpointproxy
import (
"strconv"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/request"
"net/http"
)
@@ -6,12 +6,12 @@ import (
"runtime"
"strconv"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer"
"github.com/portainer/portainer/crypto"
"github.com/portainer/portainer/http/client"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
)
type endpointCreatePayload struct {
@@ -35,7 +35,7 @@ type endpointCreatePayload struct {
func (payload *endpointCreatePayload) Validate(r *http.Request) error {
name, err := request.RetrieveMultiPartFormValue(r, "Name", false)
if err != nil {
return portainer.Error("Invalid stack name")
return portainer.Error("Invalid endpoint name")
}
payload.Name = name
@@ -71,7 +71,7 @@ func (payload *endpointCreatePayload) Validate(r *http.Request) error {
payload.TLSSkipClientVerify = skipTLSClientVerification
if !payload.TLSSkipVerify {
caCert, err := request.RetrieveMultiPartFormFile(r, "TLSCACertFile")
caCert, _, err := request.RetrieveMultiPartFormFile(r, "TLSCACertFile")
if err != nil {
return portainer.Error("Invalid CA certificate file. Ensure that the file is uploaded correctly")
}
@@ -79,13 +79,13 @@ func (payload *endpointCreatePayload) Validate(r *http.Request) error {
}
if !payload.TLSSkipClientVerify {
cert, err := request.RetrieveMultiPartFormFile(r, "TLSCertFile")
cert, _, err := request.RetrieveMultiPartFormFile(r, "TLSCertFile")
if err != nil {
return portainer.Error("Invalid certificate file. Ensure that the file is uploaded correctly")
}
payload.TLSCertFile = cert
key, err := request.RetrieveMultiPartFormFile(r, "TLSKeyFile")
key, _, err := request.RetrieveMultiPartFormFile(r, "TLSKeyFile")
if err != nil {
return portainer.Error("Invalid key file. Ensure that the file is uploaded correctly")
}
@@ -174,7 +174,7 @@ func (handler *Handler) createAzureEndpoint(payload *endpointCreatePayload) (*po
endpoint := &portainer.Endpoint{
ID: portainer.EndpointID(endpointID),
Name: payload.Name,
URL: payload.URL,
URL: "https://management.azure.com",
Type: portainer.AzureEnvironment,
GroupID: portainer.EndpointGroupID(payload.GroupID),
PublicURL: payload.PublicURL,
@@ -4,10 +4,10 @@ import (
"net/http"
"strconv"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
)
// DELETE request on /api/endpoints/:id
@@ -4,10 +4,10 @@ import (
"net/http"
"github.com/asaskevich/govalidator"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
)
type endpointExtensionAddPayload struct {
@@ -3,10 +3,10 @@ package endpoints
import (
"net/http"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
)
// DELETE request on /api/endpoints/:id/extensions/:extensionType
@@ -3,10 +3,10 @@ package endpoints
import (
"net/http"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
)
// GET request on /api/endpoints/:id
+5 -4
View File
@@ -3,8 +3,8 @@ package endpoints
import (
"net/http"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/response"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer/http/security"
)
@@ -27,8 +27,9 @@ func (handler *Handler) endpointList(w http.ResponseWriter, r *http.Request) *ht
filteredEndpoints := security.FilterEndpoints(endpoints, endpointGroups, securityContext)
for _, endpoint := range filteredEndpoints {
hideFields(&endpoint)
for idx := range filteredEndpoints {
hideFields(&filteredEndpoints[idx])
}
return response.JSON(w, filteredEndpoints)
}
@@ -4,9 +4,9 @@ import (
"log"
"net/http"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/response"
)
// POST request on /api/endpoints/snapshot
@@ -4,11 +4,11 @@ import (
"net/http"
"strconv"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer"
"github.com/portainer/portainer/http/client"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
)
type endpointUpdatePayload struct {
@@ -3,10 +3,10 @@ package endpoints
import (
"net/http"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
)
type endpointUpdateAccessPayload struct {
+1 -1
View File
@@ -1,8 +1,8 @@
package endpoints
import (
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/proxy"
"github.com/portainer/portainer/http/security"
+4
View File
@@ -33,5 +33,9 @@ func (handler *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
} else {
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
}
w.Header().Add("X-Frame-Options", "DENY")
w.Header().Add("X-XSS-Protection", "1; mode=block")
w.Header().Add("X-Content-Type-Options", "nosniff")
handler.Handler.ServeHTTP(w, r)
}
+8
View File
@@ -10,6 +10,7 @@ import (
"github.com/portainer/portainer/http/handler/endpointproxy"
"github.com/portainer/portainer/http/handler/endpoints"
"github.com/portainer/portainer/http/handler/file"
"github.com/portainer/portainer/http/handler/motd"
"github.com/portainer/portainer/http/handler/registries"
"github.com/portainer/portainer/http/handler/resourcecontrols"
"github.com/portainer/portainer/http/handler/settings"
@@ -21,6 +22,7 @@ import (
"github.com/portainer/portainer/http/handler/templates"
"github.com/portainer/portainer/http/handler/upload"
"github.com/portainer/portainer/http/handler/users"
"github.com/portainer/portainer/http/handler/webhooks"
"github.com/portainer/portainer/http/handler/websocket"
)
@@ -33,6 +35,7 @@ type Handler struct {
EndpointHandler *endpoints.Handler
EndpointProxyHandler *endpointproxy.Handler
FileHandler *file.Handler
MOTDHandler *motd.Handler
RegistryHandler *registries.Handler
ResourceControlHandler *resourcecontrols.Handler
SettingsHandler *settings.Handler
@@ -45,6 +48,7 @@ type Handler struct {
UploadHandler *upload.Handler
UserHandler *users.Handler
WebSocketHandler *websocket.Handler
WebhookHandler *webhooks.Handler
}
// ServeHTTP delegates a request to the appropriate subhandler.
@@ -67,6 +71,8 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
default:
http.StripPrefix("/api", h.EndpointHandler).ServeHTTP(w, r)
}
case strings.HasPrefix(r.URL.Path, "/api/motd"):
http.StripPrefix("/api", h.MOTDHandler).ServeHTTP(w, r)
case strings.HasPrefix(r.URL.Path, "/api/registries"):
http.StripPrefix("/api", h.RegistryHandler).ServeHTTP(w, r)
case strings.HasPrefix(r.URL.Path, "/api/resource_controls"):
@@ -91,6 +97,8 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
http.StripPrefix("/api", h.TeamMembershipHandler).ServeHTTP(w, r)
case strings.HasPrefix(r.URL.Path, "/api/websocket"):
http.StripPrefix("/api", h.WebSocketHandler).ServeHTTP(w, r)
case strings.HasPrefix(r.URL.Path, "/api/webhooks"):
http.StripPrefix("/api", h.WebhookHandler).ServeHTTP(w, r)
case strings.HasPrefix(r.URL.Path, "/"):
h.FileHandler.ServeHTTP(w, r)
}
+24
View File
@@ -0,0 +1,24 @@
package motd
import (
"net/http"
"github.com/gorilla/mux"
"github.com/portainer/portainer/http/security"
)
// Handler is the HTTP handler used to handle MOTD operations.
type Handler struct {
*mux.Router
}
// NewHandler returns a new Handler
func NewHandler(bouncer *security.RequestBouncer) *Handler {
h := &Handler{
Router: mux.NewRouter(),
}
h.Handle("/motd",
bouncer.AuthenticatedAccess(http.HandlerFunc(h.motd))).Methods(http.MethodGet)
return h
}
+27
View File
@@ -0,0 +1,27 @@
package motd
import (
"net/http"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer"
"github.com/portainer/portainer/crypto"
"github.com/portainer/portainer/http/client"
)
type motdResponse struct {
Message string `json:"Message"`
Hash []byte `json:"Hash"`
}
func (handler *Handler) motd(w http.ResponseWriter, r *http.Request) {
motd, err := client.Get(portainer.MessageOfTheDayURL)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
hash := crypto.HashFromBytes(motd)
response.JSON(w, &motdResponse{Message: string(motd), Hash: hash})
}
+1 -1
View File
@@ -1,8 +1,8 @@
package registries
import (
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/security"
"net/http"
@@ -4,10 +4,10 @@ import (
"net/http"
"github.com/asaskevich/govalidator"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
)
type registryCreatePayload struct {
@@ -3,10 +3,10 @@ package registries
import (
"net/http"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
)
// DELETE request on /api/registries/:id
@@ -3,10 +3,10 @@ package registries
import (
"net/http"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
)
// GET request on /api/registries/:id
+6 -5
View File
@@ -3,8 +3,8 @@ package registries
import (
"net/http"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/response"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer/http/security"
)
@@ -22,8 +22,9 @@ func (handler *Handler) registryList(w http.ResponseWriter, r *http.Request) *ht
filteredRegistries := security.FilterRegistries(registries, securityContext)
for _, registry := range filteredRegistries {
hideFields(&registry)
for idx := range filteredRegistries {
hideFields(&filteredRegistries[idx])
}
return response.JSON(w, registries)
return response.JSON(w, filteredRegistries)
}
@@ -4,10 +4,10 @@ import (
"net/http"
"github.com/asaskevich/govalidator"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
)
type registryUpdatePayload struct {
@@ -3,10 +3,10 @@ package registries
import (
"net/http"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
)
type registryUpdateAccessPayload struct {
+1 -1
View File
@@ -4,8 +4,8 @@ import (
"net/http"
"github.com/gorilla/mux"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/security"
)
@@ -4,20 +4,20 @@ import (
"net/http"
"github.com/asaskevich/govalidator"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
"github.com/portainer/portainer/http/security"
)
type resourceControlCreatePayload struct {
ResourceID string
Type string
AdministratorsOnly bool
Users []int
Teams []int
SubResourceIDs []string
ResourceID string
Type string
Public bool
Users []int
Teams []int
SubResourceIDs []string
}
func (payload *resourceControlCreatePayload) Validate(r *http.Request) error {
@@ -29,8 +29,8 @@ func (payload *resourceControlCreatePayload) Validate(r *http.Request) error {
return portainer.Error("Invalid type")
}
if len(payload.Users) == 0 && len(payload.Teams) == 0 && !payload.AdministratorsOnly {
return portainer.Error("Invalid resource control declaration. Must specify Users, Teams or AdministratorOnly")
if len(payload.Users) == 0 && len(payload.Teams) == 0 && !payload.Public {
return portainer.Error("Invalid resource control declaration. Must specify Users, Teams or Public")
}
return nil
}
@@ -90,12 +90,12 @@ func (handler *Handler) resourceControlCreate(w http.ResponseWriter, r *http.Req
}
resourceControl := portainer.ResourceControl{
ResourceID: payload.ResourceID,
SubResourceIDs: payload.SubResourceIDs,
Type: resourceControlType,
AdministratorsOnly: payload.AdministratorsOnly,
UserAccesses: userAccesses,
TeamAccesses: teamAccesses,
ResourceID: payload.ResourceID,
SubResourceIDs: payload.SubResourceIDs,
Type: resourceControlType,
Public: payload.Public,
UserAccesses: userAccesses,
TeamAccesses: teamAccesses,
}
securityContext, err := security.RetrieveRestrictedRequestContext(r)
@@ -3,10 +3,10 @@ package resourcecontrols
import (
"net/http"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
"github.com/portainer/portainer/http/security"
)
@@ -3,22 +3,22 @@ package resourcecontrols
import (
"net/http"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
"github.com/portainer/portainer/http/security"
)
type resourceControlUpdatePayload struct {
AdministratorsOnly bool
Users []int
Teams []int
Public bool
Users []int
Teams []int
}
func (payload *resourceControlUpdatePayload) Validate(r *http.Request) error {
if len(payload.Users) == 0 && len(payload.Teams) == 0 && !payload.AdministratorsOnly {
return portainer.Error("Invalid resource control declaration. Must specify Users, Teams or AdministratorOnly")
if len(payload.Users) == 0 && len(payload.Teams) == 0 && !payload.Public {
return portainer.Error("Invalid resource control declaration. Must specify Users, Teams or Public")
}
return nil
}
@@ -52,7 +52,7 @@ func (handler *Handler) resourceControlUpdate(w http.ResponseWriter, r *http.Req
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to update the resource control", portainer.ErrResourceAccessDenied}
}
resourceControl.AdministratorsOnly = payload.AdministratorsOnly
resourceControl.Public = payload.Public
var userAccesses = make([]portainer.UserResourceAccess, 0)
for _, v := range payload.Users {
+1 -1
View File
@@ -4,8 +4,8 @@ import (
"net/http"
"github.com/gorilla/mux"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/security"
)
@@ -3,8 +3,8 @@ package settings
import (
"net/http"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/response"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/response"
)
// GET request on /api/settings
@@ -3,11 +3,11 @@ package settings
import (
"net/http"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer"
"github.com/portainer/portainer/filesystem"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
)
type settingsLDAPCheckPayload struct {
+8 -2
View File
@@ -3,9 +3,9 @@ package settings
import (
"net/http"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/response"
)
type publicSettingsResponse struct {
@@ -13,6 +13,7 @@ type publicSettingsResponse struct {
AuthenticationMethod portainer.AuthenticationMethod `json:"AuthenticationMethod"`
AllowBindMountsForRegularUsers bool `json:"AllowBindMountsForRegularUsers"`
AllowPrivilegedModeForRegularUsers bool `json:"AllowPrivilegedModeForRegularUsers"`
ExternalTemplates bool `json:"ExternalTemplates"`
}
// GET request on /api/settings/public
@@ -27,6 +28,11 @@ func (handler *Handler) settingsPublic(w http.ResponseWriter, r *http.Request) *
AuthenticationMethod: settings.AuthenticationMethod,
AllowBindMountsForRegularUsers: settings.AllowBindMountsForRegularUsers,
AllowPrivilegedModeForRegularUsers: settings.AllowPrivilegedModeForRegularUsers,
ExternalTemplates: false,
}
if settings.TemplatesURL != "" {
publicSettings.ExternalTemplates = true
}
return response.JSON(w, publicSettings)
+11 -3
View File
@@ -4,11 +4,11 @@ import (
"net/http"
"github.com/asaskevich/govalidator"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer"
"github.com/portainer/portainer/filesystem"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
)
type settingsUpdatePayload struct {
@@ -19,6 +19,7 @@ type settingsUpdatePayload struct {
AllowBindMountsForRegularUsers *bool
AllowPrivilegedModeForRegularUsers *bool
SnapshotInterval *string
TemplatesURL *string
}
func (payload *settingsUpdatePayload) Validate(r *http.Request) error {
@@ -28,6 +29,9 @@ func (payload *settingsUpdatePayload) Validate(r *http.Request) error {
if payload.LogoURL != nil && *payload.LogoURL != "" && !govalidator.IsURL(*payload.LogoURL) {
return portainer.Error("Invalid logo URL. Must correspond to a valid URL format")
}
if payload.TemplatesURL != nil && *payload.TemplatesURL != "" && !govalidator.IsURL(*payload.TemplatesURL) {
return portainer.Error("Invalid external templates URL. Must correspond to a valid URL format")
}
return nil
}
@@ -52,6 +56,10 @@ func (handler *Handler) settingsUpdate(w http.ResponseWriter, r *http.Request) *
settings.LogoURL = *payload.LogoURL
}
if payload.TemplatesURL != nil {
settings.TemplatesURL = *payload.TemplatesURL
}
if payload.BlackListedLabels != nil {
settings.BlackListedLabels = payload.BlackListedLabels
}
@@ -6,11 +6,11 @@ import (
"strings"
"github.com/asaskevich/govalidator"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer"
"github.com/portainer/portainer/filesystem"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
"github.com/portainer/portainer/http/security"
)
@@ -194,7 +194,7 @@ func (payload *composeStackFromFileUploadPayload) Validate(r *http.Request) erro
}
payload.Name = name
composeFileContent, err := request.RetrieveMultiPartFormFile(r, "file")
composeFileContent, _, err := request.RetrieveMultiPartFormFile(r, "file")
if err != nil {
return portainer.Error("Invalid Compose file. Ensure that the Compose file is uploaded correctly")
}
@@ -6,11 +6,11 @@ import (
"strings"
"github.com/asaskevich/govalidator"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer"
"github.com/portainer/portainer/filesystem"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
"github.com/portainer/portainer/http/security"
)
@@ -211,7 +211,7 @@ func (payload *swarmStackFromFileUploadPayload) Validate(r *http.Request) error
}
payload.SwarmID = swarmID
composeFileContent, err := request.RetrieveMultiPartFormFile(r, "file")
composeFileContent, _, err := request.RetrieveMultiPartFormFile(r, "file")
if err != nil {
return portainer.Error("Invalid Compose file. Ensure that the Compose file is uploaded correctly")
}
+1 -1
View File
@@ -5,8 +5,8 @@ import (
"sync"
"github.com/gorilla/mux"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/security"
)
+6 -5
View File
@@ -1,12 +1,13 @@
package stacks
import (
"errors"
"log"
"net/http"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/request"
)
func (handler *Handler) cleanUp(stack *portainer.Stack, doCleanUp *bool) error {
@@ -57,7 +58,7 @@ func (handler *Handler) stackCreate(w http.ResponseWriter, r *http.Request) *htt
return handler.createComposeStack(w, r, method, endpoint)
}
return &httperror.HandlerError{http.StatusBadRequest, "Invalid value for query parameter: type. Value must be one of: 1 (Swarm stack) or 2 (Compose stack)", request.ErrInvalidQueryParameter}
return &httperror.HandlerError{http.StatusBadRequest, "Invalid value for query parameter: type. Value must be one of: 1 (Swarm stack) or 2 (Compose stack)", errors.New(request.ErrInvalidQueryParameter)}
}
func (handler *Handler) createComposeStack(w http.ResponseWriter, r *http.Request, method string, endpoint *portainer.Endpoint) *httperror.HandlerError {
@@ -71,7 +72,7 @@ func (handler *Handler) createComposeStack(w http.ResponseWriter, r *http.Reques
return handler.createComposeStackFromFileUpload(w, r, endpoint)
}
return &httperror.HandlerError{http.StatusBadRequest, "Invalid value for query parameter: method. Value must be one of: string, repository or file", request.ErrInvalidQueryParameter}
return &httperror.HandlerError{http.StatusBadRequest, "Invalid value for query parameter: method. Value must be one of: string, repository or file", errors.New(request.ErrInvalidQueryParameter)}
}
func (handler *Handler) createSwarmStack(w http.ResponseWriter, r *http.Request, method string, endpoint *portainer.Endpoint) *httperror.HandlerError {
@@ -84,5 +85,5 @@ func (handler *Handler) createSwarmStack(w http.ResponseWriter, r *http.Request,
return handler.createSwarmStackFromFileUpload(w, r, endpoint)
}
return &httperror.HandlerError{http.StatusBadRequest, "Invalid value for query parameter: method. Value must be one of: string, repository or file", request.ErrInvalidQueryParameter}
return &httperror.HandlerError{http.StatusBadRequest, "Invalid value for query parameter: method. Value must be one of: string, repository or file", errors.New(request.ErrInvalidQueryParameter)}
}
+5 -5
View File
@@ -4,11 +4,11 @@ import (
"net/http"
"strconv"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/proxy"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
"github.com/portainer/portainer/http/security"
)
@@ -48,8 +48,8 @@ func (handler *Handler) stackDelete(w http.ResponseWriter, r *http.Request) *htt
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve info from request context", err}
}
if resourceControl != nil {
if !securityContext.IsAdmin && !proxy.CanAccessStack(stack, resourceControl, securityContext.UserID, securityContext.UserMemberships) {
if !securityContext.IsAdmin {
if !proxy.CanAccessStack(stack, resourceControl, securityContext.UserID, securityContext.UserMemberships) {
return &httperror.HandlerError{http.StatusForbidden, "Access denied to resource", portainer.ErrResourceAccessDenied}
}
}
+7 -3
View File
@@ -4,11 +4,11 @@ import (
"net/http"
"path"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/proxy"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
"github.com/portainer/portainer/http/security"
)
@@ -41,6 +41,10 @@ func (handler *Handler) stackFile(w http.ResponseWriter, r *http.Request) *httpe
}
extendedStack := proxy.ExtendedStack{*stack, portainer.ResourceControl{}}
if !securityContext.IsAdmin && resourceControl == nil {
return &httperror.HandlerError{http.StatusForbidden, "Access denied to resource", portainer.ErrResourceAccessDenied}
}
if resourceControl != nil {
if securityContext.IsAdmin || proxy.CanAccessStack(stack, resourceControl, securityContext.UserID, securityContext.UserMemberships) {
extendedStack.ResourceControl = *resourceControl
+7 -3
View File
@@ -3,11 +3,11 @@ package stacks
import (
"net/http"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/proxy"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
"github.com/portainer/portainer/http/security"
)
@@ -36,6 +36,10 @@ func (handler *Handler) stackInspect(w http.ResponseWriter, r *http.Request) *ht
}
extendedStack := proxy.ExtendedStack{*stack, portainer.ResourceControl{}}
if !securityContext.IsAdmin && resourceControl == nil {
return &httperror.HandlerError{http.StatusForbidden, "Access denied to resource", portainer.ErrResourceAccessDenied}
}
if resourceControl != nil {
if securityContext.IsAdmin || proxy.CanAccessStack(stack, resourceControl, securityContext.UserID, securityContext.UserMemberships) {
extendedStack.ResourceControl = *resourceControl
+3 -3
View File
@@ -3,11 +3,11 @@ package stacks
import (
"net/http"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/proxy"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
"github.com/portainer/portainer/http/security"
)
+5 -5
View File
@@ -3,11 +3,11 @@ package stacks
import (
"net/http"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/proxy"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
"github.com/portainer/portainer/http/security"
)
@@ -53,8 +53,8 @@ func (handler *Handler) stackMigrate(w http.ResponseWriter, r *http.Request) *ht
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve info from request context", err}
}
if resourceControl != nil {
if !securityContext.IsAdmin && !proxy.CanAccessStack(stack, resourceControl, securityContext.UserID, securityContext.UserMemberships) {
if !securityContext.IsAdmin {
if !proxy.CanAccessStack(stack, resourceControl, securityContext.UserID, securityContext.UserMemberships) {
return &httperror.HandlerError{http.StatusForbidden, "Access denied to resource", portainer.ErrResourceAccessDenied}
}
}
+5 -5
View File
@@ -5,11 +5,11 @@ import (
"strconv"
"github.com/asaskevich/govalidator"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/proxy"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
"github.com/portainer/portainer/http/security"
)
@@ -62,8 +62,8 @@ func (handler *Handler) stackUpdate(w http.ResponseWriter, r *http.Request) *htt
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve info from request context", err}
}
if resourceControl != nil {
if !securityContext.IsAdmin && !proxy.CanAccessStack(stack, resourceControl, securityContext.UserID, securityContext.UserMemberships) {
if !securityContext.IsAdmin {
if !proxy.CanAccessStack(stack, resourceControl, securityContext.UserID, securityContext.UserMemberships) {
return &httperror.HandlerError{http.StatusForbidden, "Access denied to resource", portainer.ErrResourceAccessDenied}
}
}
+1 -1
View File
@@ -4,8 +4,8 @@ import (
"net/http"
"github.com/gorilla/mux"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/security"
)
+2 -2
View File
@@ -3,8 +3,8 @@ package status
import (
"net/http"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/response"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/response"
)
// GET request on /api/status
+1 -1
View File
@@ -4,8 +4,8 @@ import (
"net/http"
"github.com/gorilla/mux"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/security"
)
+3 -3
View File
@@ -4,10 +4,10 @@ import (
"net/http"
"github.com/asaskevich/govalidator"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
)
type tagCreatePayload struct {
+3 -3
View File
@@ -3,10 +3,10 @@ package tags
import (
"net/http"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
)
// DELETE request on /api/tags/:id
+2 -2
View File
@@ -3,8 +3,8 @@ package tags
import (
"net/http"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/response"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/response"
)
// GET request on /api/tags
+1 -1
View File
@@ -1,8 +1,8 @@
package teammemberships
import (
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/security"
"net/http"
@@ -3,10 +3,10 @@ package teammemberships
import (
"net/http"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
"github.com/portainer/portainer/http/security"
)
@@ -3,10 +3,10 @@ package teammemberships
import (
"net/http"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
"github.com/portainer/portainer/http/security"
)
@@ -3,9 +3,9 @@ package teammemberships
import (
"net/http"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/response"
"github.com/portainer/portainer/http/security"
)
@@ -3,10 +3,10 @@ package teammemberships
import (
"net/http"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
"github.com/portainer/portainer/http/security"
)
+1 -1
View File
@@ -4,8 +4,8 @@ import (
"net/http"
"github.com/gorilla/mux"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/security"
)
+3 -3
View File
@@ -4,10 +4,10 @@ import (
"net/http"
"github.com/asaskevich/govalidator"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
)
type teamCreatePayload struct {
+3 -3
View File
@@ -3,10 +3,10 @@ package teams
import (
"net/http"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
)
// DELETE request on /api/teams/:id
+3 -3
View File
@@ -3,10 +3,10 @@ package teams
import (
"net/http"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
"github.com/portainer/portainer/http/security"
)
+2 -2
View File
@@ -3,8 +3,8 @@ package teams
import (
"net/http"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/response"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer/http/security"
)
+3 -3
View File
@@ -3,10 +3,10 @@ package teams
import (
"net/http"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
"github.com/portainer/portainer/http/security"
)
+3 -3
View File
@@ -3,10 +3,10 @@ package teams
import (
"net/http"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
)
type teamUpdatePayload struct {
+27 -5
View File
@@ -4,15 +4,20 @@ import (
"net/http"
"github.com/gorilla/mux"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/security"
)
const (
errTemplateManagementDisabled = portainer.Error("Template management is disabled")
)
// Handler represents an HTTP API handler for managing templates.
type Handler struct {
*mux.Router
TemplateService portainer.TemplateService
SettingsService portainer.SettingsService
}
// NewHandler returns a new instance of Handler.
@@ -20,15 +25,32 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler {
h := &Handler{
Router: mux.NewRouter(),
}
h.Handle("/templates",
bouncer.RestrictedAccess(httperror.LoggerHandler(h.templateList))).Methods(http.MethodGet)
h.Handle("/templates",
bouncer.AdministratorAccess(httperror.LoggerHandler(h.templateCreate))).Methods(http.MethodPost)
bouncer.AdministratorAccess(h.templateManagementCheck(httperror.LoggerHandler(h.templateCreate)))).Methods(http.MethodPost)
h.Handle("/templates/{id}",
bouncer.AdministratorAccess(httperror.LoggerHandler(h.templateInspect))).Methods(http.MethodGet)
bouncer.AdministratorAccess(h.templateManagementCheck(httperror.LoggerHandler(h.templateInspect)))).Methods(http.MethodGet)
h.Handle("/templates/{id}",
bouncer.AdministratorAccess(httperror.LoggerHandler(h.templateUpdate))).Methods(http.MethodPut)
bouncer.AdministratorAccess(h.templateManagementCheck(httperror.LoggerHandler(h.templateUpdate)))).Methods(http.MethodPut)
h.Handle("/templates/{id}",
bouncer.AdministratorAccess(httperror.LoggerHandler(h.templateDelete))).Methods(http.MethodDelete)
bouncer.AdministratorAccess(h.templateManagementCheck(httperror.LoggerHandler(h.templateDelete)))).Methods(http.MethodDelete)
return h
}
func (handler *Handler) templateManagementCheck(next http.Handler) http.Handler {
return httperror.LoggerHandler(func(rw http.ResponseWriter, r *http.Request) *httperror.HandlerError {
settings, err := handler.SettingsService.Settings()
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve settings from the database", err}
}
if settings.TemplatesURL != "" {
return &httperror.HandlerError{http.StatusServiceUnavailable, "Portainer is configured to use external templates, template management is disabled", errTemplateManagementDisabled}
}
next.ServeHTTP(rw, r)
return nil
})
}
@@ -4,11 +4,11 @@ import (
"net/http"
"github.com/asaskevich/govalidator"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer"
"github.com/portainer/portainer/filesystem"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
)
type templateCreatePayload struct {
@@ -3,10 +3,10 @@ package templates
import (
"net/http"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
)
// DELETE request on /api/templates/:id
@@ -3,10 +3,10 @@ package templates
import (
"net/http"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
)
// GET request on /api/templates/:id
+26 -5
View File
@@ -1,18 +1,40 @@
package templates
import (
"encoding/json"
"net/http"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/response"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer"
"github.com/portainer/portainer/http/client"
"github.com/portainer/portainer/http/security"
)
// GET request on /api/templates
func (handler *Handler) templateList(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
templates, err := handler.TemplateService.Templates()
settings, err := handler.SettingsService.Settings()
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve templates from the database", err}
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve settings from the database", err}
}
var templates []portainer.Template
if settings.TemplatesURL == "" {
templates, err = handler.TemplateService.Templates()
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve templates from the database", err}
}
} else {
var templateData []byte
templateData, err = client.Get(settings.TemplatesURL)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve external templates", err}
}
err = json.Unmarshal(templateData, &templates)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to parse external templates", err}
}
}
securityContext, err := security.RetrieveRestrictedRequestContext(r)
@@ -21,6 +43,5 @@ func (handler *Handler) templateList(w http.ResponseWriter, r *http.Request) *ht
}
filteredTemplates := security.FilterTemplates(templates, securityContext)
return response.JSON(w, filteredTemplates)
}
@@ -3,10 +3,10 @@ package templates
import (
"net/http"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
)
type templateUpdatePayload struct {
+1 -1
View File
@@ -1,8 +1,8 @@
package upload
import (
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/security"
"net/http"

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