Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
755e56df69 | ||
|
|
4e01539bfa | ||
|
|
6151101028 |
13
.babelrc
13
.babelrc
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"plugins": ["lodash", "angularjs-annotate"],
|
||||
"presets": [
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
"modules": false,
|
||||
"useBuiltIns": "entry",
|
||||
"corejs": "2"
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
@@ -1,44 +1,29 @@
|
||||
version: "2"
|
||||
checks:
|
||||
argument-count:
|
||||
enabled: false
|
||||
complex-logic:
|
||||
enabled: false
|
||||
file-lines:
|
||||
enabled: false
|
||||
method-complexity:
|
||||
enabled: false
|
||||
method-count:
|
||||
enabled: false
|
||||
method-lines:
|
||||
enabled: false
|
||||
nested-control-flow:
|
||||
enabled: false
|
||||
return-statements:
|
||||
enabled: false
|
||||
similar-code:
|
||||
enabled: false
|
||||
identical-code:
|
||||
enabled: false
|
||||
plugins:
|
||||
---
|
||||
engines:
|
||||
gofmt:
|
||||
enabled: true
|
||||
golint:
|
||||
enabled: true
|
||||
govet:
|
||||
enabled: true
|
||||
csslint:
|
||||
enabled: true
|
||||
duplication:
|
||||
enabled: true
|
||||
config:
|
||||
languages:
|
||||
javascript:
|
||||
mass_threshold: 80
|
||||
eslint:
|
||||
enabled: true
|
||||
channel: "eslint-5"
|
||||
config:
|
||||
config: .eslintrc.yml
|
||||
exclude_patterns:
|
||||
- assets/
|
||||
- build/
|
||||
- dist/
|
||||
- distribution/
|
||||
- node_modules
|
||||
fixme:
|
||||
enabled: true
|
||||
ratings:
|
||||
paths:
|
||||
- "**.css"
|
||||
- "**.js"
|
||||
- "**.go"
|
||||
exclude_paths:
|
||||
- test/
|
||||
- webpack/
|
||||
- gruntfile.js
|
||||
- webpack.config.js
|
||||
- api/
|
||||
- "!app/kubernetes/**"
|
||||
- .github/
|
||||
- .tmp/
|
||||
|
||||
50
.codefresh/codefresh_develop.yml
Normal file
50
.codefresh/codefresh_develop.yml
Normal file
@@ -0,0 +1,50 @@
|
||||
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
|
||||
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
|
||||
when:
|
||||
branch:
|
||||
only:
|
||||
- develop
|
||||
46
.codefresh/codefresh_pullrequest.yml
Normal file
46
.codefresh/codefresh_pullrequest.yml
Normal file
@@ -0,0 +1,46 @@
|
||||
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
|
||||
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
|
||||
@@ -1,3 +0,0 @@
|
||||
node_modules/
|
||||
dist/
|
||||
test/
|
||||
294
.eslintrc.yml
294
.eslintrc.yml
@@ -1,28 +1,284 @@
|
||||
env:
|
||||
browser: true
|
||||
jquery: true
|
||||
node: true
|
||||
es6: true
|
||||
|
||||
globals:
|
||||
angular: true
|
||||
|
||||
# globals:
|
||||
# angular: true
|
||||
# $: true
|
||||
# _: true
|
||||
# moment: true
|
||||
# filesize: true
|
||||
# splitargs: true
|
||||
extends:
|
||||
- 'eslint:recommended'
|
||||
- prettier
|
||||
|
||||
plugins:
|
||||
- import
|
||||
|
||||
parserOptions:
|
||||
ecmaVersion: 2018
|
||||
sourceType: module
|
||||
ecmaFeatures:
|
||||
modules: true
|
||||
|
||||
# http://eslint.org/docs/rules/
|
||||
rules:
|
||||
no-control-regex: off
|
||||
no-empty: warn
|
||||
no-empty-function: warn
|
||||
# Possible Errors
|
||||
no-await-in-loop: off
|
||||
no-cond-assign: error
|
||||
no-console: off
|
||||
no-constant-condition: error
|
||||
no-control-regex: error
|
||||
no-debugger: error
|
||||
no-dupe-args: error
|
||||
no-dupe-keys: error
|
||||
no-duplicate-case: error
|
||||
no-empty-character-class: error
|
||||
no-empty: error
|
||||
no-ex-assign: error
|
||||
no-extra-boolean-cast: error
|
||||
no-extra-parens: off
|
||||
no-extra-semi: error
|
||||
no-func-assign: error
|
||||
no-inner-declarations:
|
||||
- error
|
||||
- functions
|
||||
no-invalid-regexp: error
|
||||
no-irregular-whitespace: error
|
||||
no-negated-in-lhs: error
|
||||
no-obj-calls: error
|
||||
no-prototype-builtins: off
|
||||
no-regex-spaces: error
|
||||
no-sparse-arrays: error
|
||||
no-template-curly-in-string: off
|
||||
no-unexpected-multiline: error
|
||||
no-unreachable: error
|
||||
no-unsafe-finally: off
|
||||
no-unsafe-negation: off
|
||||
use-isnan: error
|
||||
valid-jsdoc: off
|
||||
valid-typeof: error
|
||||
|
||||
# Best Practices
|
||||
accessor-pairs: error
|
||||
array-callback-return: off
|
||||
block-scoped-var: off
|
||||
class-methods-use-this: off
|
||||
complexity:
|
||||
- error
|
||||
- 6
|
||||
consistent-return: off
|
||||
curly: off
|
||||
default-case: off
|
||||
dot-location: off
|
||||
dot-notation: off
|
||||
eqeqeq: error
|
||||
guard-for-in: error
|
||||
no-alert: error
|
||||
no-caller: error
|
||||
no-case-declarations: error
|
||||
no-div-regex: error
|
||||
no-else-return: off
|
||||
no-empty-function: off
|
||||
no-empty-pattern: error
|
||||
no-eq-null: error
|
||||
no-eval: error
|
||||
no-extend-native: error
|
||||
no-extra-bind: error
|
||||
no-extra-label: off
|
||||
no-fallthrough: error
|
||||
no-floating-decimal: off
|
||||
no-global-assign: off
|
||||
no-implicit-coercion: off
|
||||
no-implied-eval: error
|
||||
no-invalid-this: off
|
||||
no-iterator: error
|
||||
no-labels:
|
||||
- error
|
||||
- allowLoop: true
|
||||
allowSwitch: true
|
||||
no-lone-blocks: error
|
||||
no-loop-func: error
|
||||
no-magic-number: off
|
||||
no-multi-spaces: off
|
||||
no-multi-str: off
|
||||
no-native-reassign: error
|
||||
no-new-func: error
|
||||
no-new-wrappers: error
|
||||
no-new: error
|
||||
no-octal-escape: error
|
||||
no-octal: error
|
||||
no-param-reassign: off
|
||||
no-proto: error
|
||||
no-redeclare: error
|
||||
no-restricted-properties: off
|
||||
no-return-assign: error
|
||||
no-return-await: off
|
||||
no-script-url: error
|
||||
no-self-assign: off
|
||||
no-self-compare: error
|
||||
no-sequences: off
|
||||
no-throw-literal: off
|
||||
no-unmodified-loop-condition: off
|
||||
no-unused-expressions: error
|
||||
no-unused-labels: off
|
||||
no-useless-call: error
|
||||
no-useless-concat: error
|
||||
no-useless-escape: off
|
||||
import/order: error
|
||||
no-useless-return: off
|
||||
no-void: error
|
||||
no-warning-comments: off
|
||||
no-with: error
|
||||
prefer-promise-reject-errors: off
|
||||
radix: error
|
||||
require-await: off
|
||||
vars-on-top: off
|
||||
wrap-iife: error
|
||||
yoda: off
|
||||
|
||||
# Strict
|
||||
strict: off
|
||||
|
||||
# Variables
|
||||
init-declarations: off
|
||||
no-catch-shadow: error
|
||||
no-delete-var: error
|
||||
no-label-var: error
|
||||
no-restricted-globals: off
|
||||
no-shadow-restricted-names: error
|
||||
no-shadow: off
|
||||
no-undef-init: error
|
||||
no-undef: off
|
||||
no-undefined: off
|
||||
no-unused-vars: off
|
||||
no-use-before-define: off
|
||||
|
||||
# Node.js and CommonJS
|
||||
callback-return: error
|
||||
global-require: error
|
||||
handle-callback-err: error
|
||||
no-mixed-requires: off
|
||||
no-new-require: off
|
||||
no-path-concat: error
|
||||
no-process-env: off
|
||||
no-process-exit: error
|
||||
no-restricted-modules: off
|
||||
no-sync: off
|
||||
|
||||
# Stylistic Issues
|
||||
array-bracket-spacing: off
|
||||
block-spacing: off
|
||||
brace-style: off
|
||||
camelcase: off
|
||||
capitalized-comments: off
|
||||
comma-dangle:
|
||||
- error
|
||||
- never
|
||||
comma-spacing: off
|
||||
comma-style: off
|
||||
computed-property-spacing: off
|
||||
consistent-this: off
|
||||
eol-last: off
|
||||
func-call-spacing: off
|
||||
func-name-matching: off
|
||||
func-names: off
|
||||
func-style: off
|
||||
id-length: off
|
||||
id-match: off
|
||||
indent: off
|
||||
jsx-quotes: off
|
||||
key-spacing: off
|
||||
keyword-spacing: off
|
||||
line-comment-position: off
|
||||
linebreak-style:
|
||||
- error
|
||||
- unix
|
||||
lines-around-comment: off
|
||||
lines-around-directive: off
|
||||
max-depth: off
|
||||
max-len: off
|
||||
max-nested-callbacks: off
|
||||
max-params: off
|
||||
max-statements-per-line: off
|
||||
max-statements:
|
||||
- error
|
||||
- 30
|
||||
multiline-ternary: off
|
||||
new-cap: off
|
||||
new-parens: off
|
||||
newline-after-var: off
|
||||
newline-before-return: off
|
||||
newline-per-chained-call: off
|
||||
no-array-constructor: off
|
||||
no-bitwise: off
|
||||
no-continue: off
|
||||
no-inline-comments: off
|
||||
no-lonely-if: off
|
||||
no-mixed-operators: off
|
||||
no-mixed-spaces-and-tabs: off
|
||||
no-multi-assign: off
|
||||
no-multiple-empty-lines: off
|
||||
no-negated-condition: off
|
||||
no-nested-ternary: off
|
||||
no-new-object: off
|
||||
no-plusplus: off
|
||||
no-restricted-syntax: off
|
||||
no-spaced-func: off
|
||||
no-tabs: off
|
||||
no-ternary: off
|
||||
no-trailing-spaces: off
|
||||
no-underscore-dangle: off
|
||||
no-unneeded-ternary: off
|
||||
object-curly-newline: off
|
||||
object-curly-spacing: off
|
||||
object-property-newline: off
|
||||
one-var-declaration-per-line: off
|
||||
one-var: off
|
||||
operator-assignment: off
|
||||
operator-linebreak: off
|
||||
padded-blocks: off
|
||||
quote-props: off
|
||||
quotes:
|
||||
- error
|
||||
- single
|
||||
require-jsdoc: off
|
||||
semi-spacing: off
|
||||
semi:
|
||||
- error
|
||||
- always
|
||||
sort-keys: off
|
||||
sort-vars: off
|
||||
space-before-blocks: off
|
||||
space-before-function-paren: off
|
||||
space-in-parens: off
|
||||
space-infix-ops: off
|
||||
space-unary-ops: off
|
||||
spaced-comment: off
|
||||
template-tag-spacing: off
|
||||
unicode-bom: off
|
||||
wrap-regex: off
|
||||
|
||||
# ECMAScript 6
|
||||
arrow-body-style: off
|
||||
arrow-parens: off
|
||||
arrow-spacing: off
|
||||
constructor-super: off
|
||||
generator-star-spacing: off
|
||||
no-class-assign: off
|
||||
no-confusing-arrow: off
|
||||
no-const-assign: off
|
||||
no-dupe-class-members: off
|
||||
no-duplicate-imports: off
|
||||
no-new-symbol: off
|
||||
no-restricted-imports: off
|
||||
no-this-before-super: off
|
||||
no-useless-computed-key: off
|
||||
no-useless-constructor: off
|
||||
no-useless-rename: off
|
||||
no-var: off
|
||||
object-shorthand: off
|
||||
prefer-arrow-callback: off
|
||||
prefer-const: off
|
||||
prefer-destructuring: off
|
||||
prefer-numeric-literals: off
|
||||
prefer-rest-params: off
|
||||
prefer-reflect: off
|
||||
prefer-spread: off
|
||||
prefer-template: off
|
||||
require-yield: off
|
||||
rest-spread-spacing: off
|
||||
sort-imports: off
|
||||
symbol-description: off
|
||||
template-curly-spacing: off
|
||||
yield-star-spacing: off
|
||||
|
||||
101
.github/ISSUE_TEMPLATE/Bug_report.md
vendored
101
.github/ISSUE_TEMPLATE/Bug_report.md
vendored
@@ -1,54 +1,47 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a bug report
|
||||
title: ''
|
||||
labels: bug/need-confirmation, kind/bug
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
<!--
|
||||
|
||||
Thanks for reporting a bug for Portainer !
|
||||
|
||||
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/
|
||||
|
||||
Before opening a new issue, make sure that we do not have any duplicates
|
||||
already open. You can ensure this by searching the issue list for this
|
||||
repository. If there is a duplicate, please close your issue and add a comment
|
||||
to the existing issue instead.
|
||||
|
||||
Also, be sure to check our FAQ and documentation first: https://documentation.portainer.io/
|
||||
-->
|
||||
|
||||
**Bug description**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Portainer Logs**
|
||||
Provide the logs of your Portainer container or Service.
|
||||
You can see how [here](https://documentation.portainer.io/archive/1.23.2/faq/#how-do-i-get-the-logs-from-portainer)
|
||||
|
||||
**Steps to reproduce the issue:**
|
||||
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Technical details:**
|
||||
|
||||
- Portainer version:
|
||||
- Docker version (managed by Portainer):
|
||||
- Kubernetes version (managed by Portainer):
|
||||
- Platform (windows/linux):
|
||||
- Command used to start Portainer (`docker run -p 9000:9000 portainer/portainer`):
|
||||
- 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.
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a bug report
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
|
||||
Thanks for reporting a bug for Portainer !
|
||||
|
||||
Do you need help or have a question? Come chat with us on Slack http://portainer.io/slack/ or gitter https://gitter.im/portainer/Lobby.
|
||||
|
||||
Before opening a new issue, make sure that we do not have any duplicates
|
||||
already open. You can ensure this by searching the issue list for this
|
||||
repository. If there is a duplicate, please close your issue and add a comment
|
||||
to the existing issue instead.
|
||||
|
||||
Also, be sure to check our FAQ and documentation first: https://portainer.readthedocs.io
|
||||
-->
|
||||
|
||||
**Bug description**
|
||||
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
Briefly describe what you were expecting.
|
||||
|
||||
**Steps to reproduce the issue:**
|
||||
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Technical details:**
|
||||
|
||||
* Portainer version:
|
||||
* Docker version (managed by Portainer):
|
||||
* Platform (windows/linux):
|
||||
* Command used to start Portainer (`docker run -p 9000:9000 portainer/portainer`):
|
||||
* Browser:
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
|
||||
40
.github/ISSUE_TEMPLATE/Custom.md
vendored
40
.github/ISSUE_TEMPLATE/Custom.md
vendored
@@ -1,25 +1,15 @@
|
||||
---
|
||||
name: Question
|
||||
about: Ask us a question about Portainer usage or deployment
|
||||
title: ''
|
||||
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/
|
||||
|
||||
Do you need help or have a question? Come chat with us on Slack http://portainer.slack.com/
|
||||
|
||||
Also, be sure to check our FAQ and documentation first: https://documentation.portainer.io/
|
||||
-->
|
||||
|
||||
**Question**:
|
||||
How can I deploy Portainer on... ?
|
||||
---
|
||||
name: Question
|
||||
about: Ask us a question about Portainer usage or deployment
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
|
||||
Do you need help or have a question? Come chat with us on Slack http://portainer.io/slack/ or gitter https://gitter.im/portainer/Lobby.
|
||||
|
||||
Also, be sure to check our FAQ and documentation first: https://portainer.readthedocs.io
|
||||
-->
|
||||
|
||||
**Question**:
|
||||
How can I deploy Portainer on... ?
|
||||
|
||||
65
.github/ISSUE_TEMPLATE/Feature_request.md
vendored
65
.github/ISSUE_TEMPLATE/Feature_request.md
vendored
@@ -1,34 +1,31 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest a feature/enhancement that should be added in Portainer
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
|
||||
Thanks for opening a feature request for Portainer !
|
||||
|
||||
Do you need help or have a question? Come chat with us on Slack http://portainer.slack.com/
|
||||
|
||||
Before opening a new issue, make sure that we do not have any duplicates
|
||||
already open. You can ensure this by searching the issue list for this
|
||||
repository. If there is a duplicate, please close your issue and add a comment
|
||||
to the existing issue instead.
|
||||
|
||||
Also, be sure to check our FAQ and documentation first: https://documentation.portainer.io/
|
||||
-->
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest a feature/enhancement that should be added in Portainer
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
|
||||
Thanks for opening a feature request for Portainer !
|
||||
|
||||
Do you need help or have a question? Come chat with us on Slack http://portainer.io/slack/ or gitter https://gitter.im/portainer/Lobby.
|
||||
|
||||
Before opening a new issue, make sure that we do not have any duplicates
|
||||
already open. You can ensure this by searching the issue list for this
|
||||
repository. If there is a duplicate, please close your issue and add a comment
|
||||
to the existing issue instead.
|
||||
|
||||
Also, be sure to check our FAQ and documentation first: https://portainer.readthedocs.io
|
||||
-->
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
|
||||
54
.github/stale.yml
vendored
54
.github/stale.yml
vendored
@@ -1,54 +0,0 @@
|
||||
# Config for Stalebot, limited to only `issues`
|
||||
only: issues
|
||||
|
||||
# Issues config
|
||||
issues:
|
||||
daysUntilStale: 60
|
||||
daysUntilClose: 7
|
||||
|
||||
# Limit the number of actions per hour, from 1-30. Default is 30
|
||||
limitPerRun: 30
|
||||
|
||||
# Issues with these labels will never be considered stale
|
||||
exemptLabels:
|
||||
- kind/enhancement
|
||||
- kind/question
|
||||
- kind/style
|
||||
- kind/workaround
|
||||
- kind/refactor
|
||||
- bug/need-confirmation
|
||||
- bug/confirmed
|
||||
- status/discuss
|
||||
|
||||
# Only issues with all of these labels are checked if stale. Defaults to `[]` (disabled)
|
||||
onlyLabels: []
|
||||
|
||||
# Set to true to ignore issues in a project (defaults to false)
|
||||
exemptProjects: true
|
||||
# Set to true to ignore issues in a milestone (defaults to false)
|
||||
exemptMilestones: true
|
||||
# Set to true to ignore issues with an assignee (defaults to false)
|
||||
exemptAssignees: true
|
||||
|
||||
# Label to use when marking an issue as stale
|
||||
staleLabel: status/stale
|
||||
|
||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
This issue has been marked as stale as it has not had recent activity,
|
||||
it will be closed if no further activity occurs in the next 7 days.
|
||||
If you believe that it has been incorrectly labelled as stale,
|
||||
leave a comment and the label will be removed.
|
||||
|
||||
# Comment to post when removing the stale label.
|
||||
# unmarkComment: >
|
||||
# Your comment here.
|
||||
|
||||
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||
closeComment: >
|
||||
Since no further activity has appeared on this issue it will be closed.
|
||||
If you believe that it has been incorrectly closed, leave a comment
|
||||
mentioning `ametdoohan`, `balasu` or `keverv` and one of our staff will then review the issue.
|
||||
|
||||
Note - If it is an old bug report, make sure that it is reproduceable in the
|
||||
latest version of Portainer as it may have already been fixed.
|
||||
19
.github/workflows/rebase.yml
vendored
19
.github/workflows/rebase.yml
vendored
@@ -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 }}
|
||||
9
.gitignore
vendored
9
.gitignore
vendored
@@ -4,12 +4,3 @@ dist
|
||||
portainer-checksum.txt
|
||||
api/cmd/portainer/portainer*
|
||||
.tmp
|
||||
**/.vscode/settings.json
|
||||
**/.vscode/tasks.json
|
||||
|
||||
.eslintcache
|
||||
__debug_bin
|
||||
|
||||
api/docs
|
||||
.idea
|
||||
.env
|
||||
|
||||
13
.prettierrc
13
.prettierrc
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"printWidth": 180,
|
||||
"singleQuote": true,
|
||||
"htmlWhitespaceSensitivity": "strict",
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.html"],
|
||||
"options": {
|
||||
"parser": "angular"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,167 +0,0 @@
|
||||
{
|
||||
// Place your portainer workspace snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and
|
||||
// description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope
|
||||
// is left empty or omitted, the snippet gets applied to all languages. The prefix is what is
|
||||
// used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
|
||||
// $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders.
|
||||
// Placeholders with the same ids are connected.
|
||||
// Example:
|
||||
// "Print to console": {
|
||||
// "scope": "javascript,typescript",
|
||||
// "prefix": "log",
|
||||
// "body": [
|
||||
// "console.log('$1');",
|
||||
// "$2"
|
||||
// ],
|
||||
// "description": "Log output to console"
|
||||
// }
|
||||
"Component": {
|
||||
"scope": "javascript",
|
||||
"prefix": "mycomponent",
|
||||
"description": "Dummy Angularjs Component",
|
||||
"body": [
|
||||
"import angular from 'angular';",
|
||||
"import controller from './${TM_FILENAME_BASE}Controller'",
|
||||
"",
|
||||
"angular.module('portainer.${TM_DIRECTORY/.*\\/app\\/([^\\/]*)(\\/.*)?$/$1/}').component('$TM_FILENAME_BASE', {",
|
||||
" templateUrl: './$TM_FILENAME_BASE.html',",
|
||||
" controller,",
|
||||
"});",
|
||||
""
|
||||
]
|
||||
},
|
||||
"Controller": {
|
||||
"scope": "javascript",
|
||||
"prefix": "mycontroller",
|
||||
"body": [
|
||||
"class ${TM_FILENAME_BASE/(.*)/${1:/capitalize}/} {",
|
||||
"\t/* @ngInject */",
|
||||
"\tconstructor($0) {",
|
||||
"\t}",
|
||||
"}",
|
||||
"",
|
||||
"export default ${TM_FILENAME_BASE/(.*)/${1:/capitalize}/};"
|
||||
],
|
||||
"description": "Dummy ES6+ controller"
|
||||
},
|
||||
"Service": {
|
||||
"scope": "javascript",
|
||||
"prefix": "myservice",
|
||||
"description": "Dummy ES6+ service",
|
||||
"body": [
|
||||
"import angular from 'angular';",
|
||||
"import PortainerError from 'Portainer/error';",
|
||||
"",
|
||||
"class $1 {",
|
||||
" /* @ngInject */",
|
||||
" constructor(\\$async, $0) {",
|
||||
" this.\\$async = \\$async;",
|
||||
"",
|
||||
" this.getAsync = this.getAsync.bind(this);",
|
||||
" this.getAllAsync = this.getAllAsync.bind(this);",
|
||||
" this.createAsync = this.createAsync.bind(this);",
|
||||
" this.updateAsync = this.updateAsync.bind(this);",
|
||||
" this.deleteAsync = this.deleteAsync.bind(this);",
|
||||
" }",
|
||||
"",
|
||||
" /**",
|
||||
" * GET",
|
||||
" */",
|
||||
" async getAsync() {",
|
||||
" try {",
|
||||
"",
|
||||
" } catch (err) {",
|
||||
" throw new PortainerError('', err);",
|
||||
" }",
|
||||
" }",
|
||||
"",
|
||||
" async getAllAsync() {",
|
||||
" try {",
|
||||
"",
|
||||
" } catch (err) {",
|
||||
" throw new PortainerError('', err);",
|
||||
" }",
|
||||
" }",
|
||||
"",
|
||||
" get() {",
|
||||
" if () {",
|
||||
" return this.\\$async(this.getAsync);",
|
||||
" }",
|
||||
" return this.\\$async(this.getAllAsync);",
|
||||
" }",
|
||||
"",
|
||||
" /**",
|
||||
" * CREATE",
|
||||
" */",
|
||||
" async createAsync() {",
|
||||
" try {",
|
||||
"",
|
||||
" } catch (err) {",
|
||||
" throw new PortainerError('', err);",
|
||||
" }",
|
||||
" }",
|
||||
"",
|
||||
" create() {",
|
||||
" return this.\\$async(this.createAsync);",
|
||||
" }",
|
||||
"",
|
||||
" /**",
|
||||
" * UPDATE",
|
||||
" */",
|
||||
" async updateAsync() {",
|
||||
" try {",
|
||||
"",
|
||||
" } catch (err) {",
|
||||
" throw new PortainerError('', err);",
|
||||
" }",
|
||||
" }",
|
||||
"",
|
||||
" update() {",
|
||||
" return this.\\$async(this.updateAsync);",
|
||||
" }",
|
||||
"",
|
||||
" /**",
|
||||
" * DELETE",
|
||||
" */",
|
||||
" async deleteAsync() {",
|
||||
" try {",
|
||||
"",
|
||||
" } catch (err) {",
|
||||
" throw new PortainerError('', err);",
|
||||
" }",
|
||||
" }",
|
||||
"",
|
||||
" delete() {",
|
||||
" return this.\\$async(this.deleteAsync);",
|
||||
" }",
|
||||
"}",
|
||||
"",
|
||||
"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]"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
# Open Source License Attribution
|
||||
|
||||
This application uses Open Source components. You can find the source
|
||||
code of their open source projects along with license information below.
|
||||
We acknowledge and are grateful to these developers for their contributions
|
||||
to open source.
|
||||
|
||||
### [angular-json-tree](https://github.com/awendland/angular-json-tree)
|
||||
|
||||
by [Alex Wendland](https://github.com/awendland) is licensed under [CC BY 4.0 License](https://creativecommons.org/licenses/by/4.0/)
|
||||
|
||||
### [caniuse-db](https://github.com/Fyrd/caniuse)
|
||||
|
||||
by [caniuse.com](caniuse.com) is licensed under [CC BY 4.0 License](https://creativecommons.org/licenses/by/4.0/)
|
||||
|
||||
### [caniuse-lite](https://github.com/ben-eb/caniuse-lite)
|
||||
|
||||
by [caniuse.com](caniuse.com) is licensed under [CC BY 4.0 License](https://creativecommons.org/licenses/by/4.0/)
|
||||
|
||||
### [spdx-exceptions](https://github.com/jslicense/spdx-exceptions.json)
|
||||
|
||||
by Kyle Mitchell using [SPDX](https://spdx.dev/) from Linux Foundation licensed under [CC BY 3.0 License](https://creativecommons.org/licenses/by/3.0/)
|
||||
|
||||
### [fontawesome-free](https://github.com/FortAwesome/Font-Awesome) Icons
|
||||
|
||||
by [Fort Awesome](https://fortawesome.com/) is licensed under [CC BY 4.0 License](https://creativecommons.org/licenses/by/4.0/)
|
||||
|
||||
Portainer also contains the following code, which is licensed under the [MIT license](https://opensource.org/licenses/MIT):
|
||||
|
||||
UI For Docker: Copyright (c) 2013-2016 Michael Crosby (crosbymichael.com), Kevan Ahlquist (kevanahlquist.com), Anthony Lapenna (portainer.io)
|
||||
|
||||
rdash-angular: Copyright (c) [2014][elliot hesp]
|
||||
127
CONTRIBUTING.md
127
CONTRIBUTING.md
@@ -2,22 +2,36 @@
|
||||
|
||||
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.
|
||||
|
||||
- Please open a discussion in a new issue / existing issue to talk about the changes you'd like to bring
|
||||
- Develop in a topic branch, not master/develop
|
||||
* Please open a discussion in a new issue / existing issue to talk about the changes you'd like to bring
|
||||
* Develop in a topic branch, not master/develop
|
||||
|
||||
When creating a new branch, prefix it with the _type_ of the change (see section **Commit Message Format** below), the associated opened issue number, a dash and some text describing the issue (using dash as a separator).
|
||||
When creating a new branch, prefix it with the *type* of the change (see section **Commit Message Format** below), the associated opened issue number, a dash and some text describing the issue (using dash as a separator).
|
||||
|
||||
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? Have a look at the issues labeled with the `good first issue` label: https://github.com/portainer/portainer/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22
|
||||
Want to contribute but don't know where to start?
|
||||
|
||||
## Commit Message Format
|
||||
Some of the open issues are labeled with prefix `exp/`, this is used to mark them as available for contributors to work on. All of these have an attributed difficulty level:
|
||||
|
||||
* **beginner**: a task that should be accessible with users not familiar with the codebase
|
||||
* **intermediate**: a task that require some understanding of the project codebase or some experience in
|
||||
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:
|
||||
|
||||
* 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
|
||||
|
||||
Each commit message should include a **type**, a **scope** and a **subject**:
|
||||
|
||||
@@ -33,103 +47,30 @@ 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:
|
||||
|
||||
- **feat**: A new feature
|
||||
- **fix**: A bug fix
|
||||
- **docs**: Documentation only changes
|
||||
- **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing
|
||||
* **feat**: A new feature
|
||||
* **fix**: A bug fix
|
||||
* **docs**: Documentation only changes
|
||||
* **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing
|
||||
semi-colons, etc)
|
||||
- **refactor**: A code change that neither fixes a bug or adds a feature
|
||||
- **test**: Adding missing tests
|
||||
- **chore**: Changes to the build process or auxiliary tools and libraries such as documentation
|
||||
* **refactor**: A code change that neither fixes a bug or adds a feature
|
||||
* **test**: Adding missing tests
|
||||
* **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 `status/` labels, such as `status/1-functional-review` or `status/2-technical-review`.
|
||||
|
||||
### Bug report
|
||||
|
||||

|
||||
|
||||
### Feature request
|
||||
|
||||
The feature request process is similar to the bug report process but has an extra functional validation before the technical validation as well as a documentation validation before the testing phase.
|
||||
|
||||

|
||||
|
||||
## Build Portainer locally
|
||||
|
||||
Ensure you have Docker, Node.js, yarn, and Golang installed in the correct versions.
|
||||
|
||||
Install dependencies with yarn:
|
||||
|
||||
```sh
|
||||
$ yarn
|
||||
```
|
||||
|
||||
Then build and run the project:
|
||||
|
||||
```sh
|
||||
$ 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]
|
||||
|
||||
## Licensing
|
||||
|
||||
See the [LICENSE](https://github.com/portainer/portainer/blob/develop/LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution.
|
||||
|
||||
We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes.
|
||||
* use the imperative, present tense: "change" not "changed" nor "changes"
|
||||
* don't capitalize first letter
|
||||
* no dot (.) at the end
|
||||
|
||||
46
LICENSE
46
LICENSE
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2018 Portainer.io
|
||||
Portainer: Copyright (c) 2016 Portainer.io
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
@@ -14,4 +14,46 @@ freely, subject to the following restrictions:
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
Portainer contains code which was originally under this license:
|
||||
|
||||
UI For Docker: Copyright (c) 2013-2016 Michael Crosby (crosbymichael.com), Kevan Ahlquist (kevanahlquist.com), Anthony Lapenna (portainer.io)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
rdash-angular: Copyright (c) [2014] [Elliot Hesp]
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
59
README.md
59
README.md
@@ -1,19 +1,27 @@
|
||||
|
||||
<p align="center">
|
||||
<img title="portainer" src='https://github.com/portainer/portainer/blob/develop/app/assets/images/logo_alt.png?raw=true' />
|
||||
<img title="portainer" src='https://portainer.io/images/logo_alt.png' />
|
||||
</p>
|
||||
|
||||
[](https://hub.docker.com/r/portainer/portainer/)
|
||||
[](http://microbadger.com/images/portainer/portainer 'Image size')
|
||||
[](https://portainer.visualstudio.com/Portainer%20CI/_build/latest?definitionId=3&branchName=develop)
|
||||
[](http://microbadger.com/images/portainer/portainer "Image size")
|
||||
[](http://portainer.readthedocs.io/en/stable/?badge=stable)
|
||||
[]( https://g.codefresh.io/repositories/portainer/portainer/builds?filter=trigger:build;branch:develop;service:5922a08a3a1aab000116fcc6~portainer-ci)
|
||||
[](https://codeclimate.com/github/portainer/portainer)
|
||||
[](https://portainer.io/slack/)
|
||||
[](https://gitter.im/portainer/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
||||
[](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=YHXZJQNJQ36H6)
|
||||
|
||||
**_Portainer_** is a lightweight management UI which allows you to **easily** manage your different Docker environments (Docker hosts or Swarm clusters).
|
||||
**_Portainer_** is meant to be as **simple** to deploy as it is to use. It consists of a single container that can run on any Docker engine (can be deployed as Linux container or a Windows native container, supports other platforms too).
|
||||
**_Portainer_** allows you to manage all your Docker resources (containers, images, volumes, networks and more!) It is compatible with the _standalone Docker_ engine and with _Docker Swarm mode_.
|
||||
|
||||
**_Portainer_** is meant to be as **simple** to deploy as it is to use. It consists of a single container that can run on any Docker engine (can be deployed as Linux container or a Windows native container).
|
||||
|
||||
**_Portainer_** allows you to manage your Docker containers, images, volumes, networks and more ! It is compatible with the *standalone Docker* engine and with *Docker Swarm mode*.
|
||||
|
||||
## Demo
|
||||
|
||||
<img src="https://portainer.io/images/screenshots/portainer.gif" width="77%"/>
|
||||
|
||||
You can try out the public demo instance: http://demo.portainer.io/ (login with the username **admin** and the password **tryportainer**).
|
||||
|
||||
Please note that the public demo cluster is **reset every 15min**.
|
||||
@@ -24,45 +32,32 @@ Alternatively, you can deploy a copy of the demo stack inside a [play-with-docke
|
||||
- Sign in with your [Docker ID](https://docs.docker.com/docker-id)
|
||||
- Follow [these](https://github.com/portainer/portainer-demo/blob/master/play-with-docker/docker-stack.yml#L5-L8) steps.
|
||||
|
||||
Unlike the public demo, the playground sessions are deleted after 4 hours. Apart from that, all the settings are the same, including default credentials.
|
||||
Unlike the public demo, the playground sessions are deleted after 4 hours. Apart from that, all the settings are same, including default credentials.
|
||||
|
||||
## Getting started
|
||||
|
||||
- [Deploy Portainer](https://documentation.portainer.io/quickstart/)
|
||||
- [Documentation](https://documentation.portainer.io)
|
||||
- [Building Portainer](https://documentation.portainer.io/contributing/instructions/)
|
||||
* [Deploy Portainer](https://portainer.readthedocs.io/en/latest/deployment.html)
|
||||
* [Documentation](https://portainer.readthedocs.io)
|
||||
|
||||
## Getting help
|
||||
|
||||
For FORMAL Support, please purchase a support subscription from here: https://www.portainer.io/products/portainer-business
|
||||
|
||||
For community support: You can find more information about Portainer's community support framework policy here: https://www.portainer.io/products/community-edition/customer-success
|
||||
|
||||
- Issues: https://github.com/portainer/portainer/issues
|
||||
- FAQ: https://documentation.portainer.io
|
||||
- Slack (chat): https://portainer.io/slack/
|
||||
* Issues: https://github.com/portainer/portainer/issues
|
||||
* FAQ: https://portainer.readthedocs.io/en/latest/faq.html
|
||||
* Slack (chat): https://portainer.io/slack/
|
||||
* Gitter (chat): https://gitter.im/portainer/Lobby
|
||||
|
||||
## Reporting bugs and contributing
|
||||
|
||||
- Want to report a bug or request a feature? Please open [an issue](https://github.com/portainer/portainer/issues/new).
|
||||
- Want to help us build **_portainer_**? Follow our [contribution guidelines](https://documentation.portainer.io/contributing/instructions/) to build it locally and make a pull request. We need all the help we can get!
|
||||
|
||||
## Security
|
||||
|
||||
- Here at Portainer, we believe in [responsible disclosure](https://en.wikipedia.org/wiki/Responsible_disclosure) of security issues. If you have found a security issue, please report it to <security@portainer.io>.
|
||||
|
||||
## Privacy
|
||||
|
||||
**To make sure we focus our development effort in the right places we need to know which features get used most often. To give us this information we use [Matomo Analytics](https://matomo.org/), which is hosted in Germany and is fully GDPR compliant.**
|
||||
|
||||
When Portainer first starts, you are given the option to DISABLE analytics. If you **don't** choose to disable it, we collect anonymous usage as per [our privacy policy](https://www.portainer.io/documentation/in-app-analytics-and-privacy-policy/). **Please note**, there is no personally identifiable information sent or stored at any time and we only use the data to help us improve Portainer.
|
||||
* Want to report a bug or request a feature? Please open [an issue](https://github.com/portainer/portainer/issues/new).
|
||||
* Want to help us build **_portainer_**? Follow our [contribution guidelines](https://portainer.readthedocs.io/en/latest/contribute.html) to build it locally and make a pull request. We need all the help we can get!
|
||||
|
||||
## Limitations
|
||||
|
||||
Portainer supports "Current - 2 docker versions only. Prior versions may operate, however these are not supported.
|
||||
**_Portainer_** has full support for the following Docker versions:
|
||||
|
||||
## Licensing
|
||||
* Docker 1.10 to the latest version
|
||||
* Docker Swarm >= 1.2.3
|
||||
|
||||
Portainer is licensed under the zlib license. See [LICENSE](./LICENSE) for reference.
|
||||
Partial support for the following Docker versions (some features may not be available):
|
||||
|
||||
Portainer also contains code from open source projects. See [ATTRIBUTIONS.md](./ATTRIBUTIONS.md) for a list.
|
||||
* Docker 1.9
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
package adminmonitor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
)
|
||||
|
||||
var logFatalf = log.Fatalf
|
||||
|
||||
type Monitor struct {
|
||||
timeout time.Duration
|
||||
datastore portainer.DataStore
|
||||
shutdownCtx context.Context
|
||||
cancellationFunc context.CancelFunc
|
||||
}
|
||||
|
||||
// New creates a monitor that when started will wait for the timeout duration and then shutdown the application unless it has been initialized.
|
||||
func New(timeout time.Duration, datastore portainer.DataStore, shutdownCtx context.Context) *Monitor {
|
||||
return &Monitor{
|
||||
timeout: timeout,
|
||||
datastore: datastore,
|
||||
shutdownCtx: shutdownCtx,
|
||||
}
|
||||
}
|
||||
|
||||
// Starts starts the monitor. Active monitor could be stopped or shuttted down by cancelling the shutdown context.
|
||||
func (m *Monitor) Start() {
|
||||
cancellationCtx, cancellationFunc := context.WithCancel(context.Background())
|
||||
m.cancellationFunc = cancellationFunc
|
||||
|
||||
go func() {
|
||||
log.Println("[DEBUG] [internal,init] [message: start initialization monitor ]")
|
||||
select {
|
||||
case <-time.After(m.timeout):
|
||||
initialized, err := m.WasInitialized()
|
||||
if err != nil {
|
||||
logFatalf("%s", err)
|
||||
}
|
||||
if !initialized {
|
||||
logFatalf("[FATAL] [internal,init] No administrator account was created in %f mins. Shutting down the Portainer instance for security reasons", m.timeout.Minutes())
|
||||
}
|
||||
case <-cancellationCtx.Done():
|
||||
log.Println("[DEBUG] [internal,init] [message: canceling initialization monitor]")
|
||||
case <-m.shutdownCtx.Done():
|
||||
log.Println("[DEBUG] [internal,init] [message: shutting down initialization monitor]")
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Stop stops monitor. Safe to call even if monitor wasn't started.
|
||||
func (m *Monitor) Stop() {
|
||||
if m.cancellationFunc == nil {
|
||||
return
|
||||
}
|
||||
m.cancellationFunc()
|
||||
m.cancellationFunc = nil
|
||||
}
|
||||
|
||||
// WasInitialized is a system initialization check
|
||||
func (m *Monitor) WasInitialized() (bool, error) {
|
||||
users, err := m.datastore.User().UsersByRole(portainer.AdministratorRole)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return len(users) > 0, nil
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
package adminmonitor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
i "github.com/portainer/portainer/api/internal/testhelpers"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_stopWithoutStarting(t *testing.T) {
|
||||
monitor := New(1*time.Minute, nil, nil)
|
||||
monitor.Stop()
|
||||
}
|
||||
|
||||
func Test_stopCouldBeCalledMultipleTimes(t *testing.T) {
|
||||
monitor := New(1*time.Minute, nil, nil)
|
||||
monitor.Stop()
|
||||
monitor.Stop()
|
||||
}
|
||||
|
||||
func Test_canStopStartedMonitor(t *testing.T) {
|
||||
monitor := New(1*time.Minute, nil, context.Background())
|
||||
monitor.Start()
|
||||
assert.NotNil(t, monitor.cancellationFunc, "cancellation function is missing in started monitor")
|
||||
|
||||
monitor.Stop()
|
||||
assert.Nil(t, monitor.cancellationFunc, "cancellation function should absent in stopped monitor")
|
||||
}
|
||||
|
||||
func Test_start_shouldFatalAfterTimeout_ifNotInitialized(t *testing.T) {
|
||||
timeout := 10 * time.Millisecond
|
||||
|
||||
datastore := i.NewDatastore(i.WithUsers([]portainer.User{}))
|
||||
|
||||
var fataled bool
|
||||
origLogFatalf := logFatalf
|
||||
logFatalf = func(s string, v ...interface{}) { fataled = true }
|
||||
defer func() {
|
||||
logFatalf = origLogFatalf
|
||||
}()
|
||||
|
||||
monitor := New(timeout, datastore, context.Background())
|
||||
monitor.Start()
|
||||
<-time.After(2 * timeout)
|
||||
|
||||
assert.True(t, fataled, "monitor should been timeout and fatal")
|
||||
}
|
||||
@@ -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).
|
||||
@@ -7,13 +7,13 @@ import (
|
||||
|
||||
// TarFileInBuffer will create a tar archive containing a single file named via fileName and using the content
|
||||
// specified in fileContent. Returns the archive as a byte array.
|
||||
func TarFileInBuffer(fileContent []byte, fileName string, mode int64) ([]byte, error) {
|
||||
func TarFileInBuffer(fileContent []byte, fileName string) ([]byte, error) {
|
||||
var buffer bytes.Buffer
|
||||
tarWriter := tar.NewWriter(&buffer)
|
||||
|
||||
header := &tar.Header{
|
||||
Name: fileName,
|
||||
Mode: mode,
|
||||
Mode: 0600,
|
||||
Size: int64(len(fileContent)),
|
||||
}
|
||||
|
||||
|
||||
@@ -1,119 +0,0 @@
|
||||
package archive
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// TarGzDir creates a tar.gz archive and returns it's path.
|
||||
// abosolutePath should be an absolute path to a directory.
|
||||
// Archive name will be <directoryName>.tar.gz and will be placed next to the directory.
|
||||
func TarGzDir(absolutePath string) (string, error) {
|
||||
targzPath := filepath.Join(absolutePath, fmt.Sprintf("%s.tar.gz", filepath.Base(absolutePath)))
|
||||
outFile, err := os.Create(targzPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer outFile.Close()
|
||||
|
||||
zipWriter := gzip.NewWriter(outFile)
|
||||
defer zipWriter.Close()
|
||||
tarWriter := tar.NewWriter(zipWriter)
|
||||
defer tarWriter.Close()
|
||||
|
||||
err = filepath.Walk(absolutePath, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if path == targzPath {
|
||||
return nil // skip archive file
|
||||
}
|
||||
|
||||
pathInArchive := filepath.Clean(strings.TrimPrefix(path, absolutePath))
|
||||
if pathInArchive == "" {
|
||||
return nil // skip root dir
|
||||
}
|
||||
|
||||
return addToArchive(tarWriter, pathInArchive, path, info)
|
||||
})
|
||||
|
||||
return targzPath, err
|
||||
}
|
||||
|
||||
func addToArchive(tarWriter *tar.Writer, pathInArchive string, path string, info os.FileInfo) error {
|
||||
header, err := tar.FileInfoHeader(info, info.Name())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
header.Name = pathInArchive // use relative paths in archive
|
||||
|
||||
err = tarWriter.WriteHeader(header)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = io.Copy(tarWriter, file)
|
||||
return err
|
||||
}
|
||||
|
||||
// ExtractTarGz reads a .tar.gz archive from the reader and extracts it into outputDirPath directory
|
||||
func ExtractTarGz(r io.Reader, outputDirPath string) error {
|
||||
zipReader, err := gzip.NewReader(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer zipReader.Close()
|
||||
|
||||
tarReader := tar.NewReader(zipReader)
|
||||
|
||||
for {
|
||||
header, err := tarReader.Next()
|
||||
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch header.Typeflag {
|
||||
case tar.TypeDir:
|
||||
// skip, dir will be created with a file
|
||||
case tar.TypeReg:
|
||||
p := filepath.Clean(filepath.Join(outputDirPath, header.Name))
|
||||
if err := os.MkdirAll(filepath.Dir(p), 0744); err != nil {
|
||||
return fmt.Errorf("Failed to extract dir %s", filepath.Dir(p))
|
||||
}
|
||||
outFile, err := os.Create(p)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to create file %s", header.Name)
|
||||
}
|
||||
if _, err := io.Copy(outFile, tarReader); err != nil {
|
||||
return fmt.Errorf("Failed to extract file %s", header.Name)
|
||||
}
|
||||
outFile.Close()
|
||||
default:
|
||||
return fmt.Errorf("Tar: uknown type: %v in %s",
|
||||
header.Typeflag,
|
||||
header.Name)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
package archive
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/pkg/ioutils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func listFiles(dir string) []string {
|
||||
items := make([]string, 0)
|
||||
filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||
if path == dir {
|
||||
return nil
|
||||
}
|
||||
items = append(items, path)
|
||||
return nil
|
||||
})
|
||||
|
||||
return items
|
||||
}
|
||||
|
||||
func Test_shouldCreateArhive(t *testing.T) {
|
||||
tmpdir, _ := ioutils.TempDir("", "backup")
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
content := []byte("content")
|
||||
ioutil.WriteFile(path.Join(tmpdir, "outer"), content, 0600)
|
||||
os.MkdirAll(path.Join(tmpdir, "dir"), 0700)
|
||||
ioutil.WriteFile(path.Join(tmpdir, "dir", ".dotfile"), content, 0600)
|
||||
ioutil.WriteFile(path.Join(tmpdir, "dir", "inner"), content, 0600)
|
||||
|
||||
gzPath, err := TarGzDir(tmpdir)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, filepath.Join(tmpdir, fmt.Sprintf("%s.tar.gz", filepath.Base(tmpdir))), gzPath)
|
||||
|
||||
extractionDir, _ := ioutils.TempDir("", "extract")
|
||||
defer os.RemoveAll(extractionDir)
|
||||
|
||||
cmd := exec.Command("tar", "-xzf", gzPath, "-C", extractionDir)
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
t.Fatal("Failed to extract archive: ", err)
|
||||
}
|
||||
extractedFiles := listFiles(extractionDir)
|
||||
|
||||
wasExtracted := func(p string) {
|
||||
fullpath := path.Join(extractionDir, p)
|
||||
assert.Contains(t, extractedFiles, fullpath)
|
||||
copyContent, _ := ioutil.ReadFile(fullpath)
|
||||
assert.Equal(t, content, copyContent)
|
||||
}
|
||||
|
||||
wasExtracted("outer")
|
||||
wasExtracted("dir/inner")
|
||||
wasExtracted("dir/.dotfile")
|
||||
}
|
||||
|
||||
func Test_shouldCreateArhiveXXXXX(t *testing.T) {
|
||||
tmpdir, _ := ioutils.TempDir("", "backup")
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
content := []byte("content")
|
||||
ioutil.WriteFile(path.Join(tmpdir, "outer"), content, 0600)
|
||||
os.MkdirAll(path.Join(tmpdir, "dir"), 0700)
|
||||
ioutil.WriteFile(path.Join(tmpdir, "dir", ".dotfile"), content, 0600)
|
||||
ioutil.WriteFile(path.Join(tmpdir, "dir", "inner"), content, 0600)
|
||||
|
||||
gzPath, err := TarGzDir(tmpdir)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, filepath.Join(tmpdir, fmt.Sprintf("%s.tar.gz", filepath.Base(tmpdir))), gzPath)
|
||||
|
||||
extractionDir, _ := ioutils.TempDir("", "extract")
|
||||
defer os.RemoveAll(extractionDir)
|
||||
|
||||
r, _ := os.Open(gzPath)
|
||||
ExtractTarGz(r, extractionDir)
|
||||
if err != nil {
|
||||
t.Fatal("Failed to extract archive: ", err)
|
||||
}
|
||||
extractedFiles := listFiles(extractionDir)
|
||||
|
||||
wasExtracted := func(p string) {
|
||||
fullpath := path.Join(extractionDir, p)
|
||||
assert.Contains(t, extractedFiles, fullpath)
|
||||
copyContent, _ := ioutil.ReadFile(fullpath)
|
||||
assert.Equal(t, content, copyContent)
|
||||
}
|
||||
|
||||
wasExtracted("outer")
|
||||
wasExtracted("dir/inner")
|
||||
wasExtracted("dir/.dotfile")
|
||||
}
|
||||
BIN
api/archive/testdata/sample_archive.zip
vendored
BIN
api/archive/testdata/sample_archive.zip
vendored
Binary file not shown.
@@ -1,114 +0,0 @@
|
||||
package archive
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/pkg/errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// UnzipArchive will unzip an archive from bytes into the dest destination folder on disk
|
||||
func UnzipArchive(archiveData []byte, dest string) error {
|
||||
zipReader, err := zip.NewReader(bytes.NewReader(archiveData), int64(len(archiveData)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, zipFile := range zipReader.File {
|
||||
err := extractFileFromArchive(zipFile, dest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func extractFileFromArchive(file *zip.File, dest string) error {
|
||||
f, err := file.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
data, err := ioutil.ReadAll(f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fpath := filepath.Join(dest, file.Name)
|
||||
|
||||
outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = io.Copy(outFile, bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return outFile.Close()
|
||||
}
|
||||
|
||||
// UnzipFile will decompress a zip archive, moving all files and folders
|
||||
// within the zip file (parameter 1) to an output directory (parameter 2).
|
||||
func UnzipFile(src string, dest string) error {
|
||||
r, err := zip.OpenReader(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
for _, f := range r.File {
|
||||
p := filepath.Join(dest, f.Name)
|
||||
|
||||
// Check for ZipSlip. More Info: http://bit.ly/2MsjAWE
|
||||
if !strings.HasPrefix(p, filepath.Clean(dest)+string(os.PathSeparator)) {
|
||||
return fmt.Errorf("%s: illegal file path", p)
|
||||
}
|
||||
|
||||
if f.FileInfo().IsDir() {
|
||||
// Make Folder
|
||||
os.MkdirAll(p, os.ModePerm)
|
||||
continue
|
||||
}
|
||||
|
||||
err = unzipFile(f, p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func unzipFile(f *zip.File, p string) error {
|
||||
// Make File
|
||||
if err := os.MkdirAll(filepath.Dir(p), os.ModePerm); err != nil {
|
||||
return errors.Wrapf(err, "unzipFile: can't make a path %s", p)
|
||||
}
|
||||
outFile, err := os.OpenFile(p, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "unzipFile: can't create file %s", p)
|
||||
}
|
||||
defer outFile.Close()
|
||||
rc, err := f.Open()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "unzipFile: can't open zip file %s in the archive", f.Name)
|
||||
}
|
||||
defer rc.Close()
|
||||
|
||||
_, err = io.Copy(outFile, rc)
|
||||
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "unzipFile: can't copy an archived file content")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package archive
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestUnzipFile(t *testing.T) {
|
||||
dir, err := ioutil.TempDir("", "unzip-test-")
|
||||
assert.NoError(t, err)
|
||||
defer os.RemoveAll(dir)
|
||||
/*
|
||||
Archive structure.
|
||||
├── 0
|
||||
│ ├── 1
|
||||
│ │ └── 2.txt
|
||||
│ └── 1.txt
|
||||
└── 0.txt
|
||||
*/
|
||||
|
||||
err = UnzipFile("./testdata/sample_archive.zip", dir)
|
||||
|
||||
assert.NoError(t, err)
|
||||
archiveDir := dir + "/sample_archive"
|
||||
assert.FileExists(t, filepath.Join(archiveDir, "0.txt"))
|
||||
assert.FileExists(t, filepath.Join(archiveDir, "0", "1.txt"))
|
||||
assert.FileExists(t, filepath.Join(archiveDir, "0", "1", "2.txt"))
|
||||
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
package backup
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/archive"
|
||||
"github.com/portainer/portainer/api/crypto"
|
||||
"github.com/portainer/portainer/api/http/offlinegate"
|
||||
)
|
||||
|
||||
const rwxr__r__ os.FileMode = 0744
|
||||
|
||||
var filesToBackup = []string{"compose", "config.json", "custom_templates", "edge_jobs", "edge_stacks", "extensions", "portainer.key", "portainer.pub", "tls"}
|
||||
|
||||
// Creates a tar.gz system archive and encrypts it if password is not empty. Returns a path to the archive file.
|
||||
func CreateBackupArchive(password string, gate *offlinegate.OfflineGate, datastore portainer.DataStore, filestorePath string) (string, error) {
|
||||
unlock := gate.Lock()
|
||||
defer unlock()
|
||||
|
||||
backupDirPath := filepath.Join(filestorePath, "backup", time.Now().Format("2006-01-02_15-04-05"))
|
||||
if err := os.MkdirAll(backupDirPath, rwxr__r__); err != nil {
|
||||
return "", errors.Wrap(err, "Failed to create backup dir")
|
||||
}
|
||||
|
||||
if err := backupDb(backupDirPath, datastore); err != nil {
|
||||
return "", errors.Wrap(err, "Failed to backup database")
|
||||
}
|
||||
|
||||
for _, filename := range filesToBackup {
|
||||
err := copyPath(filepath.Join(filestorePath, filename), backupDirPath)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "Failed to create backup file")
|
||||
}
|
||||
}
|
||||
|
||||
archivePath, err := archive.TarGzDir(backupDirPath)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "Failed to make an archive")
|
||||
}
|
||||
|
||||
if password != "" {
|
||||
archivePath, err = encrypt(archivePath, password)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "Failed to encrypt backup with the password")
|
||||
}
|
||||
}
|
||||
|
||||
return archivePath, nil
|
||||
}
|
||||
|
||||
func backupDb(backupDirPath string, datastore portainer.DataStore) error {
|
||||
backupWriter, err := os.Create(filepath.Join(backupDirPath, "portainer.db"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = datastore.BackupTo(backupWriter); err != nil {
|
||||
return err
|
||||
}
|
||||
return backupWriter.Close()
|
||||
}
|
||||
|
||||
func encrypt(path string, passphrase string) (string, error) {
|
||||
in, err := os.Open(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer in.Close()
|
||||
|
||||
outFileName := fmt.Sprintf("%s.encrypted", path)
|
||||
out, err := os.Create(outFileName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
err = crypto.AesEncrypt(in, out, []byte(passphrase))
|
||||
|
||||
return outFileName, err
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
package backup
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func copyPath(path string, toDir string) error {
|
||||
info, err := os.Stat(path)
|
||||
if err != nil && errors.Is(err, os.ErrNotExist) {
|
||||
// skip copy if file does not exist
|
||||
return nil
|
||||
}
|
||||
|
||||
if !info.IsDir() {
|
||||
destination := filepath.Join(toDir, info.Name())
|
||||
return copyFile(path, destination)
|
||||
}
|
||||
|
||||
return copyDir(path, toDir)
|
||||
}
|
||||
|
||||
func copyDir(fromDir, toDir string) error {
|
||||
cleanedSourcePath := filepath.Clean(fromDir)
|
||||
parentDirectory := filepath.Dir(cleanedSourcePath)
|
||||
err := filepath.Walk(cleanedSourcePath, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
destination := filepath.Join(toDir, strings.TrimPrefix(path, parentDirectory))
|
||||
if info.IsDir() {
|
||||
return nil // skip directory creations
|
||||
}
|
||||
|
||||
if info.Mode()&os.ModeSymlink != 0 { // entry is a symlink
|
||||
return nil // don't copy symlinks
|
||||
}
|
||||
|
||||
return copyFile(path, destination)
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// copies regular a file from src to dst
|
||||
func copyFile(src, dst string) error {
|
||||
from, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer from.Close()
|
||||
|
||||
// has to include 'execute' bit, otherwise fails. MkdirAll follows `mkdir -m` restrictions
|
||||
if err := os.MkdirAll(filepath.Dir(dst), 0744); err != nil {
|
||||
return err
|
||||
}
|
||||
to, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer to.Close()
|
||||
|
||||
_, err = io.Copy(to, from)
|
||||
return err
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
package backup
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/pkg/ioutils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func listFiles(dir string) []string {
|
||||
items := make([]string, 0)
|
||||
filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||
if path == dir {
|
||||
return nil
|
||||
}
|
||||
items = append(items, path)
|
||||
return nil
|
||||
})
|
||||
|
||||
return items
|
||||
}
|
||||
|
||||
func contains(t *testing.T, list []string, path string) {
|
||||
assert.Contains(t, list, path)
|
||||
copyContent, _ := ioutil.ReadFile(path)
|
||||
assert.Equal(t, "content\n", string(copyContent))
|
||||
}
|
||||
|
||||
func Test_copyFile_returnsError_whenSourceDoesNotExist(t *testing.T) {
|
||||
tmpdir, _ := ioutils.TempDir("", "backup")
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
err := copyFile("does-not-exist", tmpdir)
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func Test_copyFile_shouldMakeAbackup(t *testing.T) {
|
||||
tmpdir, _ := ioutils.TempDir("", "backup")
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
content := []byte("content")
|
||||
ioutil.WriteFile(path.Join(tmpdir, "origin"), content, 0600)
|
||||
|
||||
err := copyFile(path.Join(tmpdir, "origin"), path.Join(tmpdir, "copy"))
|
||||
assert.Nil(t, err)
|
||||
|
||||
copyContent, _ := ioutil.ReadFile(path.Join(tmpdir, "copy"))
|
||||
assert.Equal(t, content, copyContent)
|
||||
}
|
||||
|
||||
func Test_copyDir_shouldCopyAllFilesAndDirectories(t *testing.T) {
|
||||
destination, _ := ioutils.TempDir("", "destination")
|
||||
defer os.RemoveAll(destination)
|
||||
err := copyDir("./test_assets/copy_test", destination)
|
||||
assert.Nil(t, err)
|
||||
|
||||
createdFiles := listFiles(destination)
|
||||
|
||||
contains(t, createdFiles, filepath.Join(destination, "copy_test", "outer"))
|
||||
contains(t, createdFiles, filepath.Join(destination, "copy_test", "dir", ".dotfile"))
|
||||
contains(t, createdFiles, filepath.Join(destination, "copy_test", "dir", "inner"))
|
||||
}
|
||||
|
||||
func Test_backupPath_shouldSkipWhenNotExist(t *testing.T) {
|
||||
tmpdir, _ := ioutils.TempDir("", "backup")
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
err := copyPath("does-not-exists", tmpdir)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Empty(t, listFiles(tmpdir))
|
||||
}
|
||||
|
||||
func Test_backupPath_shouldCopyFile(t *testing.T) {
|
||||
tmpdir, _ := ioutils.TempDir("", "backup")
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
content := []byte("content")
|
||||
ioutil.WriteFile(path.Join(tmpdir, "file"), content, 0600)
|
||||
|
||||
os.MkdirAll(path.Join(tmpdir, "backup"), 0700)
|
||||
err := copyPath(path.Join(tmpdir, "file"), path.Join(tmpdir, "backup"))
|
||||
assert.Nil(t, err)
|
||||
|
||||
copyContent, err := ioutil.ReadFile(path.Join(tmpdir, "backup", "file"))
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, content, copyContent)
|
||||
}
|
||||
|
||||
func Test_backupPath_shouldCopyDir(t *testing.T) {
|
||||
destination, _ := ioutils.TempDir("", "destination")
|
||||
defer os.RemoveAll(destination)
|
||||
err := copyPath("./test_assets/copy_test", destination)
|
||||
assert.Nil(t, err)
|
||||
|
||||
createdFiles := listFiles(destination)
|
||||
|
||||
contains(t, createdFiles, filepath.Join(destination, "copy_test", "outer"))
|
||||
contains(t, createdFiles, filepath.Join(destination, "copy_test", "dir", ".dotfile"))
|
||||
contains(t, createdFiles, filepath.Join(destination, "copy_test", "dir", "inner"))
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
package backup
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/archive"
|
||||
"github.com/portainer/portainer/api/crypto"
|
||||
"github.com/portainer/portainer/api/http/offlinegate"
|
||||
)
|
||||
|
||||
var filesToRestore = append(filesToBackup, "portainer.db")
|
||||
|
||||
// Restores system state from backup archive, will trigger system shutdown, when finished.
|
||||
func RestoreArchive(archive io.Reader, password string, filestorePath string, gate *offlinegate.OfflineGate, datastore portainer.DataStore, shutdownTrigger context.CancelFunc) error {
|
||||
var err error
|
||||
if password != "" {
|
||||
archive, err = decrypt(archive, password)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to decrypt the archive")
|
||||
}
|
||||
}
|
||||
|
||||
restorePath := filepath.Join(filestorePath, "restore", time.Now().Format("20060102150405"))
|
||||
defer os.RemoveAll(filepath.Dir(restorePath))
|
||||
|
||||
err = extractArchive(archive, restorePath)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "cannot extract files from the archive. Please ensure the password is correct and try again")
|
||||
}
|
||||
|
||||
unlock := gate.Lock()
|
||||
defer unlock()
|
||||
|
||||
if err = datastore.Close(); err != nil {
|
||||
return errors.Wrap(err, "Failed to stop db")
|
||||
}
|
||||
|
||||
if err = restoreFiles(restorePath, filestorePath); err != nil {
|
||||
return errors.Wrap(err, "failed to restore the system state")
|
||||
}
|
||||
|
||||
shutdownTrigger()
|
||||
return nil
|
||||
}
|
||||
|
||||
func decrypt(r io.Reader, password string) (io.Reader, error) {
|
||||
return crypto.AesDecrypt(r, []byte(password))
|
||||
}
|
||||
|
||||
func extractArchive(r io.Reader, destinationDirPath string) error {
|
||||
return archive.ExtractTarGz(r, destinationDirPath)
|
||||
}
|
||||
|
||||
func restoreFiles(srcDir string, destinationDir string) error {
|
||||
for _, filename := range filesToRestore {
|
||||
err := copyPath(filepath.Join(srcDir, filename), destinationDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
content
|
||||
@@ -1 +0,0 @@
|
||||
content
|
||||
@@ -1 +0,0 @@
|
||||
content
|
||||
@@ -1,73 +0,0 @@
|
||||
package bolttest
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/portainer/portainer/api/bolt"
|
||||
"github.com/portainer/portainer/api/filesystem"
|
||||
)
|
||||
|
||||
var errTempDir = errors.New("can't create a temp dir")
|
||||
|
||||
func MustNewTestStore(init bool) (*bolt.Store, func()) {
|
||||
store, teardown, err := NewTestStore(init)
|
||||
if err != nil {
|
||||
if !errors.Is(err, errTempDir) {
|
||||
teardown()
|
||||
}
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
return store, teardown
|
||||
}
|
||||
|
||||
func NewTestStore(init bool) (*bolt.Store, func(), error) {
|
||||
// Creates unique temp directory in a concurrency friendly manner.
|
||||
dataStorePath, err := ioutil.TempDir("", "boltdb")
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(errTempDir, err.Error())
|
||||
}
|
||||
|
||||
fileService, err := filesystem.NewService(dataStorePath, "")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
store, err := bolt.NewStore(dataStorePath, fileService)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
err = store.Open()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if init {
|
||||
err = store.Init()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
teardown := func() {
|
||||
teardown(store, dataStorePath)
|
||||
}
|
||||
|
||||
return store, teardown, nil
|
||||
}
|
||||
|
||||
func teardown(store *bolt.Store, dataStorePath string) {
|
||||
err := store.Close()
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
err = os.RemoveAll(dataStorePath)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
package customtemplate
|
||||
|
||||
import (
|
||||
"github.com/boltdb/bolt"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/bolt/internal"
|
||||
)
|
||||
|
||||
const (
|
||||
// BucketName represents the name of the bucket where this service stores data.
|
||||
BucketName = "customtemplates"
|
||||
)
|
||||
|
||||
// Service represents a service for managing custom template data.
|
||||
type Service struct {
|
||||
connection *internal.DbConnection
|
||||
}
|
||||
|
||||
// NewService creates a new instance of a service.
|
||||
func NewService(connection *internal.DbConnection) (*Service, error) {
|
||||
err := internal.CreateBucket(connection, BucketName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Service{
|
||||
connection: connection,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CustomTemplates return an array containing all the custom templates.
|
||||
func (service *Service) CustomTemplates() ([]portainer.CustomTemplate, error) {
|
||||
var customTemplates = make([]portainer.CustomTemplate, 0)
|
||||
|
||||
err := service.connection.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 customTemplate portainer.CustomTemplate
|
||||
err := internal.UnmarshalObjectWithJsoniter(v, &customTemplate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
customTemplates = append(customTemplates, customTemplate)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return customTemplates, err
|
||||
}
|
||||
|
||||
// CustomTemplate returns an custom template by ID.
|
||||
func (service *Service) CustomTemplate(ID portainer.CustomTemplateID) (*portainer.CustomTemplate, error) {
|
||||
var customTemplate portainer.CustomTemplate
|
||||
identifier := internal.Itob(int(ID))
|
||||
|
||||
err := internal.GetObject(service.connection, BucketName, identifier, &customTemplate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &customTemplate, nil
|
||||
}
|
||||
|
||||
// UpdateCustomTemplate updates an custom template.
|
||||
func (service *Service) UpdateCustomTemplate(ID portainer.CustomTemplateID, customTemplate *portainer.CustomTemplate) error {
|
||||
identifier := internal.Itob(int(ID))
|
||||
return internal.UpdateObject(service.connection, BucketName, identifier, customTemplate)
|
||||
}
|
||||
|
||||
// DeleteCustomTemplate deletes an custom template.
|
||||
func (service *Service) DeleteCustomTemplate(ID portainer.CustomTemplateID) error {
|
||||
identifier := internal.Itob(int(ID))
|
||||
return internal.DeleteObject(service.connection, BucketName, identifier)
|
||||
}
|
||||
|
||||
// CreateCustomTemplate assign an ID to a new custom template and saves it.
|
||||
func (service *Service) CreateCustomTemplate(customTemplate *portainer.CustomTemplate) error {
|
||||
return service.connection.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(BucketName))
|
||||
|
||||
data, err := internal.MarshalObject(customTemplate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return bucket.Put(internal.Itob(int(customTemplate.ID)), data)
|
||||
})
|
||||
}
|
||||
|
||||
// GetNextIdentifier returns the next identifier for a custom template.
|
||||
func (service *Service) GetNextIdentifier() int {
|
||||
return internal.GetNextIdentifier(service.connection, BucketName)
|
||||
}
|
||||
@@ -1,101 +1,87 @@
|
||||
package bolt
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
"path"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
portainer "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"
|
||||
"github.com/portainer/portainer/api/bolt/edgejob"
|
||||
"github.com/portainer/portainer/api/bolt/edgestack"
|
||||
"github.com/portainer/portainer/api/bolt/endpoint"
|
||||
"github.com/portainer/portainer/api/bolt/endpointgroup"
|
||||
"github.com/portainer/portainer/api/bolt/endpointrelation"
|
||||
"github.com/portainer/portainer/api/bolt/errors"
|
||||
"github.com/portainer/portainer/api/bolt/extension"
|
||||
"github.com/portainer/portainer/api/bolt/internal"
|
||||
"github.com/portainer/portainer/api/bolt/migrator"
|
||||
"github.com/portainer/portainer/api/bolt/registry"
|
||||
"github.com/portainer/portainer/api/bolt/resourcecontrol"
|
||||
"github.com/portainer/portainer/api/bolt/role"
|
||||
"github.com/portainer/portainer/api/bolt/schedule"
|
||||
"github.com/portainer/portainer/api/bolt/settings"
|
||||
"github.com/portainer/portainer/api/bolt/stack"
|
||||
"github.com/portainer/portainer/api/bolt/tag"
|
||||
"github.com/portainer/portainer/api/bolt/team"
|
||||
"github.com/portainer/portainer/api/bolt/teammembership"
|
||||
"github.com/portainer/portainer/api/bolt/tunnelserver"
|
||||
"github.com/portainer/portainer/api/bolt/user"
|
||||
"github.com/portainer/portainer/api/bolt/version"
|
||||
"github.com/portainer/portainer/api/bolt/webhook"
|
||||
"github.com/portainer/portainer/api/internal/authorization"
|
||||
)
|
||||
|
||||
const (
|
||||
databaseFileName = "portainer.db"
|
||||
"github.com/portainer/portainer"
|
||||
)
|
||||
|
||||
// Store defines the implementation of portainer.DataStore using
|
||||
// BoltDB as the storage system.
|
||||
type Store struct {
|
||||
path string
|
||||
connection *internal.DbConnection
|
||||
isNew bool
|
||||
fileService portainer.FileService
|
||||
CustomTemplateService *customtemplate.Service
|
||||
DockerHubService *dockerhub.Service
|
||||
EdgeGroupService *edgegroup.Service
|
||||
EdgeJobService *edgejob.Service
|
||||
EdgeStackService *edgestack.Service
|
||||
EndpointGroupService *endpointgroup.Service
|
||||
EndpointService *endpoint.Service
|
||||
EndpointRelationService *endpointrelation.Service
|
||||
ExtensionService *extension.Service
|
||||
RegistryService *registry.Service
|
||||
ResourceControlService *resourcecontrol.Service
|
||||
RoleService *role.Service
|
||||
ScheduleService *schedule.Service
|
||||
SettingsService *settings.Service
|
||||
StackService *stack.Service
|
||||
TagService *tag.Service
|
||||
TeamMembershipService *teammembership.Service
|
||||
TeamService *team.Service
|
||||
TunnelServerService *tunnelserver.Service
|
||||
UserService *user.Service
|
||||
VersionService *version.Service
|
||||
WebhookService *webhook.Service
|
||||
// Path where is stored the BoltDB database.
|
||||
Path string
|
||||
|
||||
// Services
|
||||
UserService *UserService
|
||||
TeamService *TeamService
|
||||
TeamMembershipService *TeamMembershipService
|
||||
EndpointService *EndpointService
|
||||
EndpointGroupService *EndpointGroupService
|
||||
ResourceControlService *ResourceControlService
|
||||
VersionService *VersionService
|
||||
SettingsService *SettingsService
|
||||
RegistryService *RegistryService
|
||||
DockerHubService *DockerHubService
|
||||
StackService *StackService
|
||||
|
||||
db *bolt.DB
|
||||
checkForDataMigration bool
|
||||
}
|
||||
|
||||
func (store *Store) edition() portainer.SoftwareEdition {
|
||||
edition, err := store.VersionService.Edition()
|
||||
if err == errors.ErrObjectNotFound {
|
||||
edition = portainer.PortainerCE
|
||||
}
|
||||
return edition
|
||||
}
|
||||
const (
|
||||
databaseFileName = "portainer.db"
|
||||
versionBucketName = "version"
|
||||
userBucketName = "users"
|
||||
teamBucketName = "teams"
|
||||
teamMembershipBucketName = "team_membership"
|
||||
endpointBucketName = "endpoints"
|
||||
endpointGroupBucketName = "endpoint_groups"
|
||||
resourceControlBucketName = "resource_control"
|
||||
settingsBucketName = "settings"
|
||||
registryBucketName = "registries"
|
||||
dockerhubBucketName = "dockerhub"
|
||||
stackBucketName = "stacks"
|
||||
)
|
||||
|
||||
// NewStore initializes a new Store and the associated services
|
||||
func NewStore(storePath string, fileService portainer.FileService) (*Store, error) {
|
||||
func NewStore(storePath string) (*Store, error) {
|
||||
store := &Store{
|
||||
path: storePath,
|
||||
fileService: fileService,
|
||||
isNew: true,
|
||||
connection: &internal.DbConnection{},
|
||||
Path: storePath,
|
||||
UserService: &UserService{},
|
||||
TeamService: &TeamService{},
|
||||
TeamMembershipService: &TeamMembershipService{},
|
||||
EndpointService: &EndpointService{},
|
||||
EndpointGroupService: &EndpointGroupService{},
|
||||
ResourceControlService: &ResourceControlService{},
|
||||
VersionService: &VersionService{},
|
||||
SettingsService: &SettingsService{},
|
||||
RegistryService: &RegistryService{},
|
||||
DockerHubService: &DockerHubService{},
|
||||
StackService: &StackService{},
|
||||
}
|
||||
store.UserService.store = store
|
||||
store.TeamService.store = store
|
||||
store.TeamMembershipService.store = store
|
||||
store.EndpointService.store = store
|
||||
store.EndpointGroupService.store = store
|
||||
store.ResourceControlService.store = store
|
||||
store.VersionService.store = store
|
||||
store.SettingsService.store = store
|
||||
store.RegistryService.store = store
|
||||
store.DockerHubService.store = store
|
||||
store.StackService.store = store
|
||||
|
||||
databasePath := path.Join(storePath, databaseFileName)
|
||||
databaseFileExists, err := fileService.FileExists(databasePath)
|
||||
if err != nil {
|
||||
_, err := os.Stat(storePath + "/" + databaseFileName)
|
||||
if err != nil && os.IsNotExist(err) {
|
||||
store.checkForDataMigration = false
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if databaseFileExists {
|
||||
store.isNew = false
|
||||
} else {
|
||||
store.checkForDataMigration = true
|
||||
}
|
||||
|
||||
return store, nil
|
||||
@@ -103,92 +89,86 @@ func NewStore(storePath string, fileService portainer.FileService) (*Store, erro
|
||||
|
||||
// Open opens and initializes the BoltDB database.
|
||||
func (store *Store) Open() error {
|
||||
databasePath := path.Join(store.path, databaseFileName)
|
||||
db, err := bolt.Open(databasePath, 0600, &bolt.Options{Timeout: 1 * time.Second})
|
||||
path := store.Path + "/" + databaseFileName
|
||||
|
||||
db, err := bolt.Open(path, 0600, &bolt.Options{Timeout: 1 * time.Second})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
store.connection.DB = db
|
||||
store.db = db
|
||||
|
||||
return store.initServices()
|
||||
bucketsToCreate := []string{versionBucketName, userBucketName, teamBucketName, endpointBucketName,
|
||||
endpointGroupBucketName, resourceControlBucketName, teamMembershipBucketName, settingsBucketName,
|
||||
registryBucketName, dockerhubBucketName, stackBucketName}
|
||||
|
||||
return db.Update(func(tx *bolt.Tx) error {
|
||||
|
||||
for _, bucket := range bucketsToCreate {
|
||||
_, err := tx.CreateBucketIfNotExists([]byte(bucket))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Init creates the default data set.
|
||||
func (store *Store) Init() error {
|
||||
groups, err := store.EndpointGroupService.EndpointGroups()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(groups) == 0 {
|
||||
unassignedGroup := &portainer.EndpointGroup{
|
||||
Name: "Unassigned",
|
||||
Description: "Unassigned endpoints",
|
||||
Labels: []portainer.Pair{},
|
||||
AuthorizedUsers: []portainer.UserID{},
|
||||
AuthorizedTeams: []portainer.TeamID{},
|
||||
}
|
||||
|
||||
return store.EndpointGroupService.CreateEndpointGroup(unassignedGroup)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close closes the BoltDB database.
|
||||
func (store *Store) Close() error {
|
||||
if store.connection.DB != nil {
|
||||
return store.connection.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsNew returns true if the database was just created and false if it is re-using
|
||||
// existing data.
|
||||
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
|
||||
if store.db != nil {
|
||||
return store.db.Close()
|
||||
}
|
||||
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.
|
||||
// if force is true, then migrate regardless.
|
||||
func (store *Store) MigrateData(force bool) error {
|
||||
if store.isNew && !force {
|
||||
return store.VersionService.StoreDBVersion(portainer.DBVersion)
|
||||
func (store *Store) MigrateData() error {
|
||||
if !store.checkForDataMigration {
|
||||
err := store.VersionService.StoreDBVersion(portainer.DBVersion)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
version, err := store.VersionService.DBVersion()
|
||||
if err == errors.ErrObjectNotFound {
|
||||
if err == portainer.ErrDBVersionNotFound {
|
||||
version = 0
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if version < portainer.DBVersion {
|
||||
migratorParams := &migrator.Parameters{
|
||||
DB: store.connection.DB,
|
||||
DatabaseVersion: version,
|
||||
EndpointGroupService: store.EndpointGroupService,
|
||||
EndpointService: store.EndpointService,
|
||||
EndpointRelationService: store.EndpointRelationService,
|
||||
ExtensionService: store.ExtensionService,
|
||||
RegistryService: store.RegistryService,
|
||||
ResourceControlService: store.ResourceControlService,
|
||||
RoleService: store.RoleService,
|
||||
ScheduleService: store.ScheduleService,
|
||||
SettingsService: store.SettingsService,
|
||||
StackService: store.StackService,
|
||||
TagService: store.TagService,
|
||||
TeamMembershipService: store.TeamMembershipService,
|
||||
UserService: store.UserService,
|
||||
VersionService: store.VersionService,
|
||||
FileService: store.fileService,
|
||||
AuthorizationService: authorization.NewService(store),
|
||||
}
|
||||
migrator := migrator.NewMigrator(migratorParams)
|
||||
|
||||
log.Printf("Migrating database from version %v to %v.\n", version, portainer.DBVersion)
|
||||
migrator := NewMigrator(store, version)
|
||||
err = migrator.Migrate()
|
||||
if err != nil {
|
||||
log.Printf("An error occurred during database migration: %s\n", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// BackupTo backs up db to a provided writer.
|
||||
// It does hot backup and doesn't block other database reads and writes
|
||||
func (store *Store) BackupTo(w io.Writer) error {
|
||||
return store.connection.View(func(tx *bolt.Tx) error {
|
||||
_, err := tx.WriteTo(w)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
package dockerhub
|
||||
|
||||
import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/bolt/internal"
|
||||
)
|
||||
|
||||
const (
|
||||
// BucketName represents the name of the bucket where this service stores data.
|
||||
BucketName = "dockerhub"
|
||||
dockerHubKey = "DOCKERHUB"
|
||||
)
|
||||
|
||||
// Service represents a service for managing Dockerhub data.
|
||||
type Service struct {
|
||||
connection *internal.DbConnection
|
||||
}
|
||||
|
||||
// NewService creates a new instance of a service.
|
||||
func NewService(connection *internal.DbConnection) (*Service, error) {
|
||||
err := internal.CreateBucket(connection, BucketName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Service{
|
||||
connection: connection,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DockerHub returns the DockerHub object.
|
||||
func (service *Service) DockerHub() (*portainer.DockerHub, error) {
|
||||
var dockerhub portainer.DockerHub
|
||||
|
||||
err := internal.GetObject(service.connection, BucketName, []byte(dockerHubKey), &dockerhub)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &dockerhub, nil
|
||||
}
|
||||
|
||||
// UpdateDockerHub updates a DockerHub object.
|
||||
func (service *Service) UpdateDockerHub(dockerhub *portainer.DockerHub) error {
|
||||
return internal.UpdateObject(service.connection, BucketName, []byte(dockerHubKey), dockerhub)
|
||||
}
|
||||
61
api/bolt/dockerhub_service.go
Normal file
61
api/bolt/dockerhub_service.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package bolt
|
||||
|
||||
import (
|
||||
"github.com/portainer/portainer"
|
||||
"github.com/portainer/portainer/bolt/internal"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
)
|
||||
|
||||
// DockerHubService represents a service for managing registries.
|
||||
type DockerHubService struct {
|
||||
store *Store
|
||||
}
|
||||
|
||||
const (
|
||||
dbDockerHubKey = "DOCKERHUB"
|
||||
)
|
||||
|
||||
// DockerHub returns the DockerHub object.
|
||||
func (service *DockerHubService) DockerHub() (*portainer.DockerHub, error) {
|
||||
var data []byte
|
||||
err := service.store.db.View(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(dockerhubBucketName))
|
||||
value := bucket.Get([]byte(dbDockerHubKey))
|
||||
if value == nil {
|
||||
return portainer.ErrDockerHubNotFound
|
||||
}
|
||||
|
||||
data = make([]byte, len(value))
|
||||
copy(data, value)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var dockerhub portainer.DockerHub
|
||||
err = internal.UnmarshalDockerHub(data, &dockerhub)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &dockerhub, nil
|
||||
}
|
||||
|
||||
// StoreDockerHub persists a DockerHub object.
|
||||
func (service *DockerHubService) StoreDockerHub(dockerhub *portainer.DockerHub) error {
|
||||
return service.store.db.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(dockerhubBucketName))
|
||||
|
||||
data, err := internal.MarshalDockerHub(dockerhub)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = bucket.Put([]byte(dbDockerHubKey), data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
package edgegroup
|
||||
|
||||
import (
|
||||
"github.com/boltdb/bolt"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/bolt/internal"
|
||||
)
|
||||
|
||||
const (
|
||||
// BucketName represents the name of the bucket where this service stores data.
|
||||
BucketName = "edgegroups"
|
||||
)
|
||||
|
||||
// Service represents a service for managing Edge group data.
|
||||
type Service struct {
|
||||
connection *internal.DbConnection
|
||||
}
|
||||
|
||||
// NewService creates a new instance of a service.
|
||||
func NewService(connection *internal.DbConnection) (*Service, error) {
|
||||
err := internal.CreateBucket(connection, BucketName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Service{
|
||||
connection: connection,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// EdgeGroups return an array containing all the Edge groups.
|
||||
func (service *Service) EdgeGroups() ([]portainer.EdgeGroup, error) {
|
||||
var groups = make([]portainer.EdgeGroup, 0)
|
||||
|
||||
err := service.connection.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 group portainer.EdgeGroup
|
||||
err := internal.UnmarshalObjectWithJsoniter(v, &group)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
groups = append(groups, group)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return groups, err
|
||||
}
|
||||
|
||||
// EdgeGroup returns an Edge group by ID.
|
||||
func (service *Service) EdgeGroup(ID portainer.EdgeGroupID) (*portainer.EdgeGroup, error) {
|
||||
var group portainer.EdgeGroup
|
||||
identifier := internal.Itob(int(ID))
|
||||
|
||||
err := internal.GetObject(service.connection, BucketName, identifier, &group)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &group, nil
|
||||
}
|
||||
|
||||
// UpdateEdgeGroup updates an Edge group.
|
||||
func (service *Service) UpdateEdgeGroup(ID portainer.EdgeGroupID, group *portainer.EdgeGroup) error {
|
||||
identifier := internal.Itob(int(ID))
|
||||
return internal.UpdateObject(service.connection, BucketName, identifier, group)
|
||||
}
|
||||
|
||||
// DeleteEdgeGroup deletes an Edge group.
|
||||
func (service *Service) DeleteEdgeGroup(ID portainer.EdgeGroupID) error {
|
||||
identifier := internal.Itob(int(ID))
|
||||
return internal.DeleteObject(service.connection, BucketName, identifier)
|
||||
}
|
||||
|
||||
// CreateEdgeGroup assign an ID to a new Edge group and saves it.
|
||||
func (service *Service) CreateEdgeGroup(group *portainer.EdgeGroup) error {
|
||||
return service.connection.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(BucketName))
|
||||
|
||||
id, _ := bucket.NextSequence()
|
||||
group.ID = portainer.EdgeGroupID(id)
|
||||
|
||||
data, err := internal.MarshalObject(group)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return bucket.Put(internal.Itob(int(group.ID)), data)
|
||||
})
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
package edgejob
|
||||
|
||||
import (
|
||||
"github.com/boltdb/bolt"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/bolt/internal"
|
||||
)
|
||||
|
||||
const (
|
||||
// BucketName represents the name of the bucket where this service stores data.
|
||||
BucketName = "edgejobs"
|
||||
)
|
||||
|
||||
// Service represents a service for managing edge jobs data.
|
||||
type Service struct {
|
||||
connection *internal.DbConnection
|
||||
}
|
||||
|
||||
// NewService creates a new instance of a service.
|
||||
func NewService(connection *internal.DbConnection) (*Service, error) {
|
||||
err := internal.CreateBucket(connection, BucketName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Service{
|
||||
connection: connection,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// EdgeJobs returns a list of Edge jobs
|
||||
func (service *Service) EdgeJobs() ([]portainer.EdgeJob, error) {
|
||||
var edgeJobs = make([]portainer.EdgeJob, 0)
|
||||
|
||||
err := service.connection.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 edgeJob portainer.EdgeJob
|
||||
err := internal.UnmarshalObject(v, &edgeJob)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
edgeJobs = append(edgeJobs, edgeJob)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return edgeJobs, err
|
||||
}
|
||||
|
||||
// EdgeJob returns an Edge job by ID
|
||||
func (service *Service) EdgeJob(ID portainer.EdgeJobID) (*portainer.EdgeJob, error) {
|
||||
var edgeJob portainer.EdgeJob
|
||||
identifier := internal.Itob(int(ID))
|
||||
|
||||
err := internal.GetObject(service.connection, BucketName, identifier, &edgeJob)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &edgeJob, nil
|
||||
}
|
||||
|
||||
// CreateEdgeJob creates a new Edge job
|
||||
func (service *Service) CreateEdgeJob(edgeJob *portainer.EdgeJob) error {
|
||||
return service.connection.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(BucketName))
|
||||
|
||||
if edgeJob.ID == 0 {
|
||||
id, _ := bucket.NextSequence()
|
||||
edgeJob.ID = portainer.EdgeJobID(id)
|
||||
}
|
||||
|
||||
data, err := internal.MarshalObject(edgeJob)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return bucket.Put(internal.Itob(int(edgeJob.ID)), data)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateEdgeJob updates an Edge job by ID
|
||||
func (service *Service) UpdateEdgeJob(ID portainer.EdgeJobID, edgeJob *portainer.EdgeJob) error {
|
||||
identifier := internal.Itob(int(ID))
|
||||
return internal.UpdateObject(service.connection, BucketName, identifier, edgeJob)
|
||||
}
|
||||
|
||||
// DeleteEdgeJob deletes an Edge job
|
||||
func (service *Service) DeleteEdgeJob(ID portainer.EdgeJobID) error {
|
||||
identifier := internal.Itob(int(ID))
|
||||
return internal.DeleteObject(service.connection, BucketName, identifier)
|
||||
}
|
||||
|
||||
// GetNextIdentifier returns the next identifier for an endpoint.
|
||||
func (service *Service) GetNextIdentifier() int {
|
||||
return internal.GetNextIdentifier(service.connection, BucketName)
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
package edgestack
|
||||
|
||||
import (
|
||||
"github.com/boltdb/bolt"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/bolt/internal"
|
||||
)
|
||||
|
||||
const (
|
||||
// BucketName represents the name of the bucket where this service stores data.
|
||||
BucketName = "edge_stack"
|
||||
)
|
||||
|
||||
// Service represents a service for managing Edge stack data.
|
||||
type Service struct {
|
||||
connection *internal.DbConnection
|
||||
}
|
||||
|
||||
// NewService creates a new instance of a service.
|
||||
func NewService(connection *internal.DbConnection) (*Service, error) {
|
||||
err := internal.CreateBucket(connection, BucketName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Service{
|
||||
connection: connection,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// EdgeStacks returns an array containing all edge stacks
|
||||
func (service *Service) EdgeStacks() ([]portainer.EdgeStack, error) {
|
||||
var stacks = make([]portainer.EdgeStack, 0)
|
||||
|
||||
err := service.connection.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 stack portainer.EdgeStack
|
||||
err := internal.UnmarshalObject(v, &stack)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stacks = append(stacks, stack)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return stacks, err
|
||||
}
|
||||
|
||||
// EdgeStack returns an Edge stack by ID.
|
||||
func (service *Service) EdgeStack(ID portainer.EdgeStackID) (*portainer.EdgeStack, error) {
|
||||
var stack portainer.EdgeStack
|
||||
identifier := internal.Itob(int(ID))
|
||||
|
||||
err := internal.GetObject(service.connection, BucketName, identifier, &stack)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &stack, nil
|
||||
}
|
||||
|
||||
// CreateEdgeStack assign an ID to a new Edge stack and saves it.
|
||||
func (service *Service) CreateEdgeStack(edgeStack *portainer.EdgeStack) error {
|
||||
return service.connection.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(BucketName))
|
||||
|
||||
if edgeStack.ID == 0 {
|
||||
id, _ := bucket.NextSequence()
|
||||
edgeStack.ID = portainer.EdgeStackID(id)
|
||||
}
|
||||
|
||||
data, err := internal.MarshalObject(edgeStack)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return bucket.Put(internal.Itob(int(edgeStack.ID)), data)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateEdgeStack updates an Edge stack.
|
||||
func (service *Service) UpdateEdgeStack(ID portainer.EdgeStackID, edgeStack *portainer.EdgeStack) error {
|
||||
identifier := internal.Itob(int(ID))
|
||||
return internal.UpdateObject(service.connection, BucketName, identifier, edgeStack)
|
||||
}
|
||||
|
||||
// DeleteEdgeStack deletes an Edge stack.
|
||||
func (service *Service) DeleteEdgeStack(ID portainer.EdgeStackID) error {
|
||||
identifier := internal.Itob(int(ID))
|
||||
return internal.DeleteObject(service.connection, BucketName, identifier)
|
||||
}
|
||||
|
||||
// GetNextIdentifier returns the next identifier for an endpoint.
|
||||
func (service *Service) GetNextIdentifier() int {
|
||||
return internal.GetNextIdentifier(service.connection, BucketName)
|
||||
}
|
||||
@@ -1,145 +0,0 @@
|
||||
package endpoint
|
||||
|
||||
import (
|
||||
"github.com/boltdb/bolt"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/bolt/internal"
|
||||
)
|
||||
|
||||
const (
|
||||
// BucketName represents the name of the bucket where this service stores data.
|
||||
BucketName = "endpoints"
|
||||
)
|
||||
|
||||
// Service represents a service for managing endpoint data.
|
||||
type Service struct {
|
||||
connection *internal.DbConnection
|
||||
}
|
||||
|
||||
// NewService creates a new instance of a service.
|
||||
func NewService(connection *internal.DbConnection) (*Service, error) {
|
||||
err := internal.CreateBucket(connection, BucketName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Service{
|
||||
connection: connection,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Endpoint returns an endpoint by ID.
|
||||
func (service *Service) Endpoint(ID portainer.EndpointID) (*portainer.Endpoint, error) {
|
||||
var endpoint portainer.Endpoint
|
||||
identifier := internal.Itob(int(ID))
|
||||
|
||||
err := internal.GetObject(service.connection, BucketName, identifier, &endpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &endpoint, nil
|
||||
}
|
||||
|
||||
// UpdateEndpoint updates an endpoint.
|
||||
func (service *Service) UpdateEndpoint(ID portainer.EndpointID, endpoint *portainer.Endpoint) error {
|
||||
identifier := internal.Itob(int(ID))
|
||||
return internal.UpdateObject(service.connection, BucketName, identifier, endpoint)
|
||||
}
|
||||
|
||||
// DeleteEndpoint deletes an endpoint.
|
||||
func (service *Service) DeleteEndpoint(ID portainer.EndpointID) error {
|
||||
identifier := internal.Itob(int(ID))
|
||||
return internal.DeleteObject(service.connection, BucketName, identifier)
|
||||
}
|
||||
|
||||
// Endpoints return an array containing all the endpoints.
|
||||
func (service *Service) Endpoints() ([]portainer.Endpoint, error) {
|
||||
var endpoints = make([]portainer.Endpoint, 0)
|
||||
|
||||
err := service.connection.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 endpoint portainer.Endpoint
|
||||
err := internal.UnmarshalObjectWithJsoniter(v, &endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
endpoints = append(endpoints, endpoint)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return endpoints, err
|
||||
}
|
||||
|
||||
// CreateEndpoint assign an ID to a new endpoint and saves it.
|
||||
func (service *Service) CreateEndpoint(endpoint *portainer.Endpoint) error {
|
||||
return service.connection.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(BucketName))
|
||||
|
||||
// We manually manage sequences for endpoints
|
||||
err := bucket.SetSequence(uint64(endpoint.ID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data, err := internal.MarshalObject(endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return bucket.Put(internal.Itob(int(endpoint.ID)), data)
|
||||
})
|
||||
}
|
||||
|
||||
// GetNextIdentifier returns the next identifier for an endpoint.
|
||||
func (service *Service) GetNextIdentifier() int {
|
||||
return internal.GetNextIdentifier(service.connection, BucketName)
|
||||
}
|
||||
|
||||
// Synchronize creates, updates and deletes endpoints inside a single transaction.
|
||||
func (service *Service) Synchronize(toCreate, toUpdate, toDelete []*portainer.Endpoint) error {
|
||||
return service.connection.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(BucketName))
|
||||
|
||||
for _, endpoint := range toCreate {
|
||||
id, _ := bucket.NextSequence()
|
||||
endpoint.ID = portainer.EndpointID(id)
|
||||
|
||||
data, err := internal.MarshalObject(endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = bucket.Put(internal.Itob(int(endpoint.ID)), data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, endpoint := range toUpdate {
|
||||
data, err := internal.MarshalObject(endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = bucket.Put(internal.Itob(int(endpoint.ID)), data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, endpoint := range toDelete {
|
||||
err := bucket.Delete(internal.Itob(int(endpoint.ID)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
114
api/bolt/endpoint_group_service.go
Normal file
114
api/bolt/endpoint_group_service.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package bolt
|
||||
|
||||
import (
|
||||
"github.com/portainer/portainer"
|
||||
"github.com/portainer/portainer/bolt/internal"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
)
|
||||
|
||||
// EndpointGroupService represents a service for managing endpoint groups.
|
||||
type EndpointGroupService struct {
|
||||
store *Store
|
||||
}
|
||||
|
||||
// EndpointGroup returns an endpoint group by ID.
|
||||
func (service *EndpointGroupService) EndpointGroup(ID portainer.EndpointGroupID) (*portainer.EndpointGroup, error) {
|
||||
var data []byte
|
||||
err := service.store.db.View(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(endpointGroupBucketName))
|
||||
value := bucket.Get(internal.Itob(int(ID)))
|
||||
if value == nil {
|
||||
return portainer.ErrEndpointGroupNotFound
|
||||
}
|
||||
|
||||
data = make([]byte, len(value))
|
||||
copy(data, value)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var endpointGroup portainer.EndpointGroup
|
||||
err = internal.UnmarshalEndpointGroup(data, &endpointGroup)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &endpointGroup, nil
|
||||
}
|
||||
|
||||
// EndpointGroups return an array containing all the endpoint groups.
|
||||
func (service *EndpointGroupService) EndpointGroups() ([]portainer.EndpointGroup, error) {
|
||||
var endpointGroups = make([]portainer.EndpointGroup, 0)
|
||||
err := service.store.db.View(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(endpointGroupBucketName))
|
||||
|
||||
cursor := bucket.Cursor()
|
||||
for k, v := cursor.First(); k != nil; k, v = cursor.Next() {
|
||||
var endpointGroup portainer.EndpointGroup
|
||||
err := internal.UnmarshalEndpointGroup(v, &endpointGroup)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
endpointGroups = append(endpointGroups, endpointGroup)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return endpointGroups, nil
|
||||
}
|
||||
|
||||
// CreateEndpointGroup assign an ID to a new endpoint group and saves it.
|
||||
func (service *EndpointGroupService) CreateEndpointGroup(endpointGroup *portainer.EndpointGroup) error {
|
||||
return service.store.db.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(endpointGroupBucketName))
|
||||
|
||||
id, _ := bucket.NextSequence()
|
||||
endpointGroup.ID = portainer.EndpointGroupID(id)
|
||||
|
||||
data, err := internal.MarshalEndpointGroup(endpointGroup)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = bucket.Put(internal.Itob(int(endpointGroup.ID)), data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateEndpointGroup updates an endpoint group.
|
||||
func (service *EndpointGroupService) UpdateEndpointGroup(ID portainer.EndpointGroupID, endpointGroup *portainer.EndpointGroup) error {
|
||||
data, err := internal.MarshalEndpointGroup(endpointGroup)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return service.store.db.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(endpointGroupBucketName))
|
||||
err = bucket.Put(internal.Itob(int(ID)), data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteEndpointGroup deletes an endpoint group.
|
||||
func (service *EndpointGroupService) DeleteEndpointGroup(ID portainer.EndpointGroupID) error {
|
||||
return service.store.db.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(endpointGroupBucketName))
|
||||
err := bucket.Delete(internal.Itob(int(ID)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
154
api/bolt/endpoint_service.go
Normal file
154
api/bolt/endpoint_service.go
Normal file
@@ -0,0 +1,154 @@
|
||||
package bolt
|
||||
|
||||
import (
|
||||
"github.com/portainer/portainer"
|
||||
"github.com/portainer/portainer/bolt/internal"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
)
|
||||
|
||||
// EndpointService represents a service for managing endpoints.
|
||||
type EndpointService struct {
|
||||
store *Store
|
||||
}
|
||||
|
||||
// Endpoint returns an endpoint by ID.
|
||||
func (service *EndpointService) Endpoint(ID portainer.EndpointID) (*portainer.Endpoint, error) {
|
||||
var data []byte
|
||||
err := service.store.db.View(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(endpointBucketName))
|
||||
value := bucket.Get(internal.Itob(int(ID)))
|
||||
if value == nil {
|
||||
return portainer.ErrEndpointNotFound
|
||||
}
|
||||
|
||||
data = make([]byte, len(value))
|
||||
copy(data, value)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var endpoint portainer.Endpoint
|
||||
err = internal.UnmarshalEndpoint(data, &endpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &endpoint, nil
|
||||
}
|
||||
|
||||
// Endpoints return an array containing all the endpoints.
|
||||
func (service *EndpointService) Endpoints() ([]portainer.Endpoint, error) {
|
||||
var endpoints = make([]portainer.Endpoint, 0)
|
||||
err := service.store.db.View(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(endpointBucketName))
|
||||
|
||||
cursor := bucket.Cursor()
|
||||
for k, v := cursor.First(); k != nil; k, v = cursor.Next() {
|
||||
var endpoint portainer.Endpoint
|
||||
err := internal.UnmarshalEndpoint(v, &endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
endpoints = append(endpoints, endpoint)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return endpoints, nil
|
||||
}
|
||||
|
||||
// Synchronize creates, updates and deletes endpoints inside a single transaction.
|
||||
func (service *EndpointService) Synchronize(toCreate, toUpdate, toDelete []*portainer.Endpoint) error {
|
||||
return service.store.db.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(endpointBucketName))
|
||||
|
||||
for _, endpoint := range toCreate {
|
||||
err := storeNewEndpoint(endpoint, bucket)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, endpoint := range toUpdate {
|
||||
err := marshalAndStoreEndpoint(endpoint, bucket)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, endpoint := range toDelete {
|
||||
err := bucket.Delete(internal.Itob(int(endpoint.ID)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// CreateEndpoint assign an ID to a new endpoint and saves it.
|
||||
func (service *EndpointService) CreateEndpoint(endpoint *portainer.Endpoint) error {
|
||||
return service.store.db.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(endpointBucketName))
|
||||
err := storeNewEndpoint(endpoint, bucket)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateEndpoint updates an endpoint.
|
||||
func (service *EndpointService) UpdateEndpoint(ID portainer.EndpointID, endpoint *portainer.Endpoint) error {
|
||||
data, err := internal.MarshalEndpoint(endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return service.store.db.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(endpointBucketName))
|
||||
err = bucket.Put(internal.Itob(int(ID)), data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteEndpoint deletes an endpoint.
|
||||
func (service *EndpointService) DeleteEndpoint(ID portainer.EndpointID) error {
|
||||
return service.store.db.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(endpointBucketName))
|
||||
err := bucket.Delete(internal.Itob(int(ID)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func marshalAndStoreEndpoint(endpoint *portainer.Endpoint, bucket *bolt.Bucket) error {
|
||||
data, err := internal.MarshalEndpoint(endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = bucket.Put(internal.Itob(int(endpoint.ID)), data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func storeNewEndpoint(endpoint *portainer.Endpoint, bucket *bolt.Bucket) error {
|
||||
id, _ := bucket.NextSequence()
|
||||
endpoint.ID = portainer.EndpointID(id)
|
||||
return marshalAndStoreEndpoint(endpoint, bucket)
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
package endpointgroup
|
||||
|
||||
import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/bolt/internal"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
)
|
||||
|
||||
const (
|
||||
// BucketName represents the name of the bucket where this service stores data.
|
||||
BucketName = "endpoint_groups"
|
||||
)
|
||||
|
||||
// Service represents a service for managing endpoint data.
|
||||
type Service struct {
|
||||
connection *internal.DbConnection
|
||||
}
|
||||
|
||||
// NewService creates a new instance of a service.
|
||||
func NewService(connection *internal.DbConnection) (*Service, error) {
|
||||
err := internal.CreateBucket(connection, BucketName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Service{
|
||||
connection: connection,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// EndpointGroup returns an endpoint group by ID.
|
||||
func (service *Service) EndpointGroup(ID portainer.EndpointGroupID) (*portainer.EndpointGroup, error) {
|
||||
var endpointGroup portainer.EndpointGroup
|
||||
identifier := internal.Itob(int(ID))
|
||||
|
||||
err := internal.GetObject(service.connection, BucketName, identifier, &endpointGroup)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &endpointGroup, nil
|
||||
}
|
||||
|
||||
// UpdateEndpointGroup updates an endpoint group.
|
||||
func (service *Service) UpdateEndpointGroup(ID portainer.EndpointGroupID, endpointGroup *portainer.EndpointGroup) error {
|
||||
identifier := internal.Itob(int(ID))
|
||||
return internal.UpdateObject(service.connection, BucketName, identifier, endpointGroup)
|
||||
}
|
||||
|
||||
// DeleteEndpointGroup deletes an endpoint group.
|
||||
func (service *Service) DeleteEndpointGroup(ID portainer.EndpointGroupID) error {
|
||||
identifier := internal.Itob(int(ID))
|
||||
return internal.DeleteObject(service.connection, BucketName, identifier)
|
||||
}
|
||||
|
||||
// EndpointGroups return an array containing all the endpoint groups.
|
||||
func (service *Service) EndpointGroups() ([]portainer.EndpointGroup, error) {
|
||||
var endpointGroups = make([]portainer.EndpointGroup, 0)
|
||||
|
||||
err := service.connection.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 endpointGroup portainer.EndpointGroup
|
||||
err := internal.UnmarshalObject(v, &endpointGroup)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
endpointGroups = append(endpointGroups, endpointGroup)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return endpointGroups, err
|
||||
}
|
||||
|
||||
// CreateEndpointGroup assign an ID to a new endpoint group and saves it.
|
||||
func (service *Service) CreateEndpointGroup(endpointGroup *portainer.EndpointGroup) error {
|
||||
return service.connection.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(BucketName))
|
||||
|
||||
id, _ := bucket.NextSequence()
|
||||
endpointGroup.ID = portainer.EndpointGroupID(id)
|
||||
|
||||
data, err := internal.MarshalObject(endpointGroup)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return bucket.Put(internal.Itob(int(endpointGroup.ID)), data)
|
||||
})
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
package endpointrelation
|
||||
|
||||
import (
|
||||
"github.com/boltdb/bolt"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/bolt/internal"
|
||||
)
|
||||
|
||||
const (
|
||||
// BucketName represents the name of the bucket where this service stores data.
|
||||
BucketName = "endpoint_relations"
|
||||
)
|
||||
|
||||
// Service represents a service for managing endpoint relation data.
|
||||
type Service struct {
|
||||
connection *internal.DbConnection
|
||||
}
|
||||
|
||||
// NewService creates a new instance of a service.
|
||||
func NewService(connection *internal.DbConnection) (*Service, error) {
|
||||
err := internal.CreateBucket(connection, BucketName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Service{
|
||||
connection: connection,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// EndpointRelation returns a Endpoint relation object by EndpointID
|
||||
func (service *Service) EndpointRelation(endpointID portainer.EndpointID) (*portainer.EndpointRelation, error) {
|
||||
var endpointRelation portainer.EndpointRelation
|
||||
identifier := internal.Itob(int(endpointID))
|
||||
|
||||
err := internal.GetObject(service.connection, BucketName, identifier, &endpointRelation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &endpointRelation, nil
|
||||
}
|
||||
|
||||
// CreateEndpointRelation saves endpointRelation
|
||||
func (service *Service) CreateEndpointRelation(endpointRelation *portainer.EndpointRelation) error {
|
||||
return service.connection.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(BucketName))
|
||||
|
||||
data, err := internal.MarshalObject(endpointRelation)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return bucket.Put(internal.Itob(int(endpointRelation.EndpointID)), data)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateEndpointRelation updates an Endpoint relation object
|
||||
func (service *Service) UpdateEndpointRelation(EndpointID portainer.EndpointID, endpointRelation *portainer.EndpointRelation) error {
|
||||
identifier := internal.Itob(int(EndpointID))
|
||||
return internal.UpdateObject(service.connection, BucketName, identifier, endpointRelation)
|
||||
}
|
||||
|
||||
// DeleteEndpointRelation deletes an Endpoint relation object
|
||||
func (service *Service) DeleteEndpointRelation(EndpointID portainer.EndpointID) error {
|
||||
identifier := internal.Itob(int(EndpointID))
|
||||
return internal.DeleteObject(service.connection, BucketName, identifier)
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package errors
|
||||
|
||||
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 documentation to downgrade it: https://documentation.portainer.io/v2.0-be/downgrade/be-to-ce/")
|
||||
)
|
||||
@@ -1,86 +0,0 @@
|
||||
package extension
|
||||
|
||||
import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/bolt/internal"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
)
|
||||
|
||||
const (
|
||||
// BucketName represents the name of the bucket where this service stores data.
|
||||
BucketName = "extension"
|
||||
)
|
||||
|
||||
// Service represents a service for managing endpoint data.
|
||||
type Service struct {
|
||||
connection *internal.DbConnection
|
||||
}
|
||||
|
||||
// NewService creates a new instance of a service.
|
||||
func NewService(connection *internal.DbConnection) (*Service, error) {
|
||||
err := internal.CreateBucket(connection, BucketName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Service{
|
||||
connection: connection,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Extension returns a extension by ID
|
||||
func (service *Service) Extension(ID portainer.ExtensionID) (*portainer.Extension, error) {
|
||||
var extension portainer.Extension
|
||||
identifier := internal.Itob(int(ID))
|
||||
|
||||
err := internal.GetObject(service.connection, BucketName, identifier, &extension)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &extension, nil
|
||||
}
|
||||
|
||||
// Extensions return an array containing all the extensions.
|
||||
func (service *Service) Extensions() ([]portainer.Extension, error) {
|
||||
var extensions = make([]portainer.Extension, 0)
|
||||
|
||||
err := service.connection.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 extension portainer.Extension
|
||||
err := internal.UnmarshalObject(v, &extension)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
extensions = append(extensions, extension)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return extensions, err
|
||||
}
|
||||
|
||||
// Persist persists a extension inside the database.
|
||||
func (service *Service) Persist(extension *portainer.Extension) error {
|
||||
return service.connection.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(BucketName))
|
||||
|
||||
data, err := internal.MarshalObject(extension)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return bucket.Put(internal.Itob(int(extension.ID)), data)
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteExtension deletes a Extension.
|
||||
func (service *Service) DeleteExtension(ID portainer.ExtensionID) error {
|
||||
identifier := internal.Itob(int(ID))
|
||||
return internal.DeleteObject(service.connection, BucketName, identifier)
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
package bolt
|
||||
|
||||
import (
|
||||
"github.com/gofrs/uuid"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/bolt/errors"
|
||||
)
|
||||
|
||||
// Init creates the default data set.
|
||||
func (store *Store) Init() error {
|
||||
instanceID, err := store.VersionService.InstanceID()
|
||||
if err == errors.ErrObjectNotFound {
|
||||
uid, err := uuid.NewV4()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
instanceID = uid.String()
|
||||
err = store.VersionService.StoreInstanceID(instanceID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = store.SettingsService.Settings()
|
||||
if err == errors.ErrObjectNotFound {
|
||||
defaultSettings := &portainer.Settings{
|
||||
AuthenticationMethod: portainer.AuthenticationInternal,
|
||||
BlackListedLabels: make([]portainer.Pair, 0),
|
||||
LDAPSettings: portainer.LDAPSettings{
|
||||
AnonymousMode: true,
|
||||
AutoCreateUsers: true,
|
||||
TLSConfig: portainer.TLSConfiguration{},
|
||||
SearchSettings: []portainer.LDAPSearchSettings{
|
||||
portainer.LDAPSearchSettings{},
|
||||
},
|
||||
GroupSearchSettings: []portainer.LDAPGroupSearchSettings{
|
||||
portainer.LDAPGroupSearchSettings{},
|
||||
},
|
||||
},
|
||||
OAuthSettings: portainer.OAuthSettings{},
|
||||
|
||||
EdgeAgentCheckinInterval: portainer.DefaultEdgeAgentCheckinIntervalInSeconds,
|
||||
TemplatesURL: portainer.DefaultTemplatesURL,
|
||||
UserSessionTimeout: portainer.DefaultUserSessionTimeout,
|
||||
}
|
||||
|
||||
err = store.SettingsService.UpdateSettings(defaultSettings)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = store.DockerHubService.DockerHub()
|
||||
if err == errors.ErrObjectNotFound {
|
||||
defaultDockerHub := &portainer.DockerHub{
|
||||
Authentication: false,
|
||||
Username: "",
|
||||
Password: "",
|
||||
}
|
||||
|
||||
err := store.DockerHubService.UpdateDockerHub(defaultDockerHub)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
groups, err := store.EndpointGroupService.EndpointGroups()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(groups) == 0 {
|
||||
unassignedGroup := &portainer.EndpointGroup{
|
||||
Name: "Unassigned",
|
||||
Description: "Unassigned endpoints",
|
||||
Labels: []portainer.Pair{},
|
||||
UserAccessPolicies: portainer.UserAccessPolicies{},
|
||||
TeamAccessPolicies: portainer.TeamAccessPolicies{},
|
||||
TagIDs: []portainer.TagID{},
|
||||
}
|
||||
|
||||
err = store.EndpointGroupService.CreateEndpointGroup(unassignedGroup)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
"github.com/portainer/portainer/api/bolt/errors"
|
||||
)
|
||||
|
||||
type DbConnection struct {
|
||||
*bolt.DB
|
||||
}
|
||||
|
||||
// Itob returns an 8-byte big endian representation of v.
|
||||
// This function is typically used for encoding integer IDs to byte slices
|
||||
// so that they can be used as BoltDB keys.
|
||||
func Itob(v int) []byte {
|
||||
b := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(b, uint64(v))
|
||||
return b
|
||||
}
|
||||
|
||||
// CreateBucket is a generic function used to create a bucket inside a bolt database.
|
||||
func CreateBucket(connection *DbConnection, bucketName string) error {
|
||||
return connection.Update(func(tx *bolt.Tx) error {
|
||||
_, err := tx.CreateBucketIfNotExists([]byte(bucketName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// GetObject is a generic function used to retrieve an unmarshalled object from a bolt database.
|
||||
func GetObject(connection *DbConnection, bucketName string, key []byte, object interface{}) error {
|
||||
var data []byte
|
||||
|
||||
err := connection.View(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(bucketName))
|
||||
|
||||
value := bucket.Get(key)
|
||||
if value == nil {
|
||||
return errors.ErrObjectNotFound
|
||||
}
|
||||
|
||||
data = make([]byte, len(value))
|
||||
copy(data, value)
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return UnmarshalObject(data, object)
|
||||
}
|
||||
|
||||
// UpdateObject is a generic function used to update an object inside a bolt database.
|
||||
func UpdateObject(connection *DbConnection, bucketName string, key []byte, object interface{}) error {
|
||||
return connection.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(bucketName))
|
||||
|
||||
data, err := MarshalObject(object)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = bucket.Put(key, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteObject is a generic function used to delete an object inside a bolt database.
|
||||
func DeleteObject(connection *DbConnection, bucketName string, key []byte) error {
|
||||
return connection.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(bucketName))
|
||||
return bucket.Delete(key)
|
||||
})
|
||||
}
|
||||
|
||||
// GetNextIdentifier is a generic function that returns the specified bucket identifier incremented by 1.
|
||||
func GetNextIdentifier(connection *DbConnection, bucketName string) int {
|
||||
var identifier int
|
||||
|
||||
connection.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(bucketName))
|
||||
id, err := bucket.NextSequence()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
identifier = int(id)
|
||||
return nil
|
||||
})
|
||||
|
||||
return identifier
|
||||
}
|
||||
117
api/bolt/internal/internal.go
Normal file
117
api/bolt/internal/internal.go
Normal file
@@ -0,0 +1,117 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"github.com/portainer/portainer"
|
||||
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
// MarshalUser encodes a user to binary format.
|
||||
func MarshalUser(user *portainer.User) ([]byte, error) {
|
||||
return json.Marshal(user)
|
||||
}
|
||||
|
||||
// UnmarshalUser decodes a user from a binary data.
|
||||
func UnmarshalUser(data []byte, user *portainer.User) error {
|
||||
return json.Unmarshal(data, user)
|
||||
}
|
||||
|
||||
// MarshalTeam encodes a team to binary format.
|
||||
func MarshalTeam(team *portainer.Team) ([]byte, error) {
|
||||
return json.Marshal(team)
|
||||
}
|
||||
|
||||
// UnmarshalTeam decodes a team from a binary data.
|
||||
func UnmarshalTeam(data []byte, team *portainer.Team) error {
|
||||
return json.Unmarshal(data, team)
|
||||
}
|
||||
|
||||
// MarshalTeamMembership encodes a team membership to binary format.
|
||||
func MarshalTeamMembership(membership *portainer.TeamMembership) ([]byte, error) {
|
||||
return json.Marshal(membership)
|
||||
}
|
||||
|
||||
// UnmarshalTeamMembership decodes a team membership from a binary data.
|
||||
func UnmarshalTeamMembership(data []byte, membership *portainer.TeamMembership) error {
|
||||
return json.Unmarshal(data, membership)
|
||||
}
|
||||
|
||||
// MarshalEndpoint encodes an endpoint to binary format.
|
||||
func MarshalEndpoint(endpoint *portainer.Endpoint) ([]byte, error) {
|
||||
return json.Marshal(endpoint)
|
||||
}
|
||||
|
||||
// UnmarshalEndpoint decodes an endpoint from a binary data.
|
||||
func UnmarshalEndpoint(data []byte, endpoint *portainer.Endpoint) error {
|
||||
return json.Unmarshal(data, endpoint)
|
||||
}
|
||||
|
||||
// MarshalEndpointGroup encodes an endpoint group to binary format.
|
||||
func MarshalEndpointGroup(group *portainer.EndpointGroup) ([]byte, error) {
|
||||
return json.Marshal(group)
|
||||
}
|
||||
|
||||
// UnmarshalEndpointGroup decodes an endpoint group from a binary data.
|
||||
func UnmarshalEndpointGroup(data []byte, group *portainer.EndpointGroup) error {
|
||||
return json.Unmarshal(data, group)
|
||||
}
|
||||
|
||||
// MarshalStack encodes a stack to binary format.
|
||||
func MarshalStack(stack *portainer.Stack) ([]byte, error) {
|
||||
return json.Marshal(stack)
|
||||
}
|
||||
|
||||
// UnmarshalStack decodes a stack from a binary data.
|
||||
func UnmarshalStack(data []byte, stack *portainer.Stack) error {
|
||||
return json.Unmarshal(data, stack)
|
||||
}
|
||||
|
||||
// MarshalRegistry encodes a registry to binary format.
|
||||
func MarshalRegistry(registry *portainer.Registry) ([]byte, error) {
|
||||
return json.Marshal(registry)
|
||||
}
|
||||
|
||||
// UnmarshalRegistry decodes a registry from a binary data.
|
||||
func UnmarshalRegistry(data []byte, registry *portainer.Registry) error {
|
||||
return json.Unmarshal(data, registry)
|
||||
}
|
||||
|
||||
// MarshalResourceControl encodes a resource control object to binary format.
|
||||
func MarshalResourceControl(rc *portainer.ResourceControl) ([]byte, error) {
|
||||
return json.Marshal(rc)
|
||||
}
|
||||
|
||||
// UnmarshalResourceControl decodes a resource control object from a binary data.
|
||||
func UnmarshalResourceControl(data []byte, rc *portainer.ResourceControl) error {
|
||||
return json.Unmarshal(data, rc)
|
||||
}
|
||||
|
||||
// MarshalSettings encodes a settings object to binary format.
|
||||
func MarshalSettings(settings *portainer.Settings) ([]byte, error) {
|
||||
return json.Marshal(settings)
|
||||
}
|
||||
|
||||
// UnmarshalSettings decodes a settings object from a binary data.
|
||||
func UnmarshalSettings(data []byte, settings *portainer.Settings) error {
|
||||
return json.Unmarshal(data, settings)
|
||||
}
|
||||
|
||||
// MarshalDockerHub encodes a Dockerhub object to binary format.
|
||||
func MarshalDockerHub(settings *portainer.DockerHub) ([]byte, error) {
|
||||
return json.Marshal(settings)
|
||||
}
|
||||
|
||||
// UnmarshalDockerHub decodes a Dockerhub object from a binary data.
|
||||
func UnmarshalDockerHub(data []byte, settings *portainer.DockerHub) error {
|
||||
return json.Unmarshal(data, settings)
|
||||
}
|
||||
|
||||
// Itob returns an 8-byte big endian representation of v.
|
||||
// This function is typically used for encoding integer IDs to byte slices
|
||||
// so that they can be used as BoltDB keys.
|
||||
func Itob(v int) []byte {
|
||||
b := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(b, uint64(v))
|
||||
return b
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
)
|
||||
|
||||
// MarshalObject encodes an object to binary format
|
||||
func MarshalObject(object interface{}) ([]byte, error) {
|
||||
return json.Marshal(object)
|
||||
}
|
||||
|
||||
// UnmarshalObject decodes an object from binary data
|
||||
func UnmarshalObject(data []byte, object interface{}) error {
|
||||
return json.Unmarshal(data, object)
|
||||
}
|
||||
|
||||
// UnmarshalObjectWithJsoniter decodes an object from binary data
|
||||
// using the jsoniter library. It is mainly used to accelerate endpoint
|
||||
// decoding at the moment.
|
||||
func UnmarshalObjectWithJsoniter(data []byte, object interface{}) error {
|
||||
var jsoni = jsoniter.ConfigCompatibleWithStandardLibrary
|
||||
return jsoni.Unmarshal(data, &object)
|
||||
}
|
||||
39
api/bolt/migrate_dbversion0.go
Normal file
39
api/bolt/migrate_dbversion0.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package bolt
|
||||
|
||||
import (
|
||||
"github.com/boltdb/bolt"
|
||||
"github.com/portainer/portainer"
|
||||
)
|
||||
|
||||
func (m *Migrator) updateAdminUserToDBVersion1() error {
|
||||
u, err := m.UserService.UserByUsername("admin")
|
||||
if err == nil {
|
||||
admin := &portainer.User{
|
||||
Username: "admin",
|
||||
Password: u.Password,
|
||||
Role: portainer.AdministratorRole,
|
||||
}
|
||||
err = m.UserService.CreateUser(admin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = m.removeLegacyAdminUser()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if err != nil && err != portainer.ErrUserNotFound {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Migrator) removeLegacyAdminUser() error {
|
||||
return m.store.db.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(userBucketName))
|
||||
err := bucket.Delete([]byte("admin"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
package migrator
|
||||
package bolt
|
||||
|
||||
import (
|
||||
"github.com/boltdb/bolt"
|
||||
"github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/bolt/internal"
|
||||
"github.com/portainer/portainer"
|
||||
"github.com/portainer/portainer/bolt/internal"
|
||||
)
|
||||
|
||||
func (m *Migrator) updateResourceControlsToDBVersion2() error {
|
||||
@@ -16,7 +16,7 @@ func (m *Migrator) updateResourceControlsToDBVersion2() error {
|
||||
resourceControl.SubResourceIDs = []string{}
|
||||
resourceControl.TeamAccesses = []portainer.TeamResourceAccess{}
|
||||
|
||||
owner, err := m.userService.User(resourceControl.OwnerID)
|
||||
owner, err := m.UserService.User(resourceControl.OwnerID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -33,7 +33,7 @@ func (m *Migrator) updateResourceControlsToDBVersion2() error {
|
||||
resourceControl.UserAccesses = []portainer.UserResourceAccess{userAccess}
|
||||
}
|
||||
|
||||
err = m.resourceControlService.CreateResourceControl(&resourceControl)
|
||||
err = m.ResourceControlService.CreateResourceControl(&resourceControl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -43,14 +43,14 @@ func (m *Migrator) updateResourceControlsToDBVersion2() error {
|
||||
}
|
||||
|
||||
func (m *Migrator) updateEndpointsToDBVersion2() error {
|
||||
legacyEndpoints, err := m.endpointService.Endpoints()
|
||||
legacyEndpoints, err := m.EndpointService.Endpoints()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, endpoint := range legacyEndpoints {
|
||||
endpoint.AuthorizedTeams = []portainer.TeamID{}
|
||||
err = m.endpointService.UpdateEndpoint(endpoint.ID, &endpoint)
|
||||
err = m.EndpointService.UpdateEndpoint(endpoint.ID, &endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -61,12 +61,12 @@ func (m *Migrator) updateEndpointsToDBVersion2() error {
|
||||
|
||||
func (m *Migrator) retrieveLegacyResourceControls() ([]portainer.ResourceControl, error) {
|
||||
legacyResourceControls := make([]portainer.ResourceControl, 0)
|
||||
err := m.db.View(func(tx *bolt.Tx) error {
|
||||
err := m.store.db.View(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte("containerResourceControl"))
|
||||
cursor := bucket.Cursor()
|
||||
for k, v := cursor.First(); k != nil; k, v = cursor.Next() {
|
||||
var resourceControl portainer.ResourceControl
|
||||
err := internal.UnmarshalObject(v, &resourceControl)
|
||||
err := internal.UnmarshalResourceControl(v, &resourceControl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -78,7 +78,7 @@ func (m *Migrator) retrieveLegacyResourceControls() ([]portainer.ResourceControl
|
||||
cursor = bucket.Cursor()
|
||||
for k, v := cursor.First(); k != nil; k, v = cursor.Next() {
|
||||
var resourceControl portainer.ResourceControl
|
||||
err := internal.UnmarshalObject(v, &resourceControl)
|
||||
err := internal.UnmarshalResourceControl(v, &resourceControl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -90,7 +90,7 @@ func (m *Migrator) retrieveLegacyResourceControls() ([]portainer.ResourceControl
|
||||
cursor = bucket.Cursor()
|
||||
for k, v := cursor.First(); k != nil; k, v = cursor.Next() {
|
||||
var resourceControl portainer.ResourceControl
|
||||
err := internal.UnmarshalObject(v, &resourceControl)
|
||||
err := internal.UnmarshalResourceControl(v, &resourceControl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
package migrator
|
||||
package bolt
|
||||
|
||||
import "github.com/portainer/portainer/api"
|
||||
import "github.com/portainer/portainer"
|
||||
|
||||
func (m *Migrator) updateSettingsToDBVersion3() error {
|
||||
legacySettings, err := m.settingsService.Settings()
|
||||
legacySettings, err := m.SettingsService.Settings()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -16,5 +16,10 @@ func (m *Migrator) updateSettingsToDBVersion3() error {
|
||||
},
|
||||
}
|
||||
|
||||
return m.settingsService.UpdateSettings(legacySettings)
|
||||
err = m.SettingsService.StoreSettings(legacySettings)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
package migrator
|
||||
package bolt
|
||||
|
||||
import "github.com/portainer/portainer/api"
|
||||
import "github.com/portainer/portainer"
|
||||
|
||||
func (m *Migrator) updateEndpointsToDBVersion4() error {
|
||||
legacyEndpoints, err := m.endpointService.Endpoints()
|
||||
legacyEndpoints, err := m.EndpointService.Endpoints()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -17,8 +17,7 @@ func (m *Migrator) updateEndpointsToDBVersion4() error {
|
||||
endpoint.TLSConfig.TLSCertPath = endpoint.TLSCertPath
|
||||
endpoint.TLSConfig.TLSKeyPath = endpoint.TLSKeyPath
|
||||
}
|
||||
|
||||
err = m.endpointService.UpdateEndpoint(endpoint.ID, &endpoint)
|
||||
err = m.EndpointService.UpdateEndpoint(endpoint.ID, &endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
16
api/bolt/migrate_dbversion4.go
Normal file
16
api/bolt/migrate_dbversion4.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package bolt
|
||||
|
||||
func (m *Migrator) updateSettingsToVersion5() error {
|
||||
legacySettings, err := m.SettingsService.Settings()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
legacySettings.AllowBindMountsForRegularUsers = true
|
||||
|
||||
err = m.SettingsService.StoreSettings(legacySettings)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
16
api/bolt/migrate_dbversion5.go
Normal file
16
api/bolt/migrate_dbversion5.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package bolt
|
||||
|
||||
func (m *Migrator) updateSettingsToVersion6() error {
|
||||
legacySettings, err := m.SettingsService.Settings()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
legacySettings.AllowPrivilegedModeForRegularUsers = true
|
||||
|
||||
err = m.SettingsService.StoreSettings(legacySettings)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
16
api/bolt/migrate_dbversion6.go
Normal file
16
api/bolt/migrate_dbversion6.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package bolt
|
||||
|
||||
func (m *Migrator) updateSettingsToVersion7() error {
|
||||
legacySettings, err := m.SettingsService.Settings()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
legacySettings.DisplayDonationHeader = true
|
||||
|
||||
err = m.SettingsService.StoreSettings(legacySettings)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,16 +1,16 @@
|
||||
package migrator
|
||||
package bolt
|
||||
|
||||
import "github.com/portainer/portainer/api"
|
||||
import "github.com/portainer/portainer"
|
||||
|
||||
func (m *Migrator) updateEndpointsToVersion8() error {
|
||||
legacyEndpoints, err := m.endpointService.Endpoints()
|
||||
legacyEndpoints, err := m.EndpointService.Endpoints()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, endpoint := range legacyEndpoints {
|
||||
endpoint.Extensions = []portainer.EndpointExtension{}
|
||||
err = m.endpointService.UpdateEndpoint(endpoint.ID, &endpoint)
|
||||
err = m.EndpointService.UpdateEndpoint(endpoint.ID, &endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1,16 +1,16 @@
|
||||
package migrator
|
||||
package bolt
|
||||
|
||||
import "github.com/portainer/portainer/api"
|
||||
import "github.com/portainer/portainer"
|
||||
|
||||
func (m *Migrator) updateEndpointsToVersion9() error {
|
||||
legacyEndpoints, err := m.endpointService.Endpoints()
|
||||
legacyEndpoints, err := m.EndpointService.Endpoints()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, endpoint := range legacyEndpoints {
|
||||
endpoint.GroupID = portainer.EndpointGroupID(1)
|
||||
err = m.endpointService.UpdateEndpoint(endpoint.ID, &endpoint)
|
||||
err = m.EndpointService.UpdateEndpoint(endpoint.ID, &endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1,16 +1,16 @@
|
||||
package migrator
|
||||
package bolt
|
||||
|
||||
import "github.com/portainer/portainer/api"
|
||||
import "github.com/portainer/portainer"
|
||||
|
||||
func (m *Migrator) updateEndpointsToVersion10() error {
|
||||
legacyEndpoints, err := m.endpointService.Endpoints()
|
||||
legacyEndpoints, err := m.EndpointService.Endpoints()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, endpoint := range legacyEndpoints {
|
||||
endpoint.Type = portainer.DockerEnvironment
|
||||
err = m.endpointService.UpdateEndpoint(endpoint.ID, &endpoint)
|
||||
err = m.EndpointService.UpdateEndpoint(endpoint.ID, &endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
120
api/bolt/migrator.go
Normal file
120
api/bolt/migrator.go
Normal file
@@ -0,0 +1,120 @@
|
||||
package bolt
|
||||
|
||||
import "github.com/portainer/portainer"
|
||||
|
||||
// Migrator defines a service to migrate data after a Portainer version update.
|
||||
type Migrator struct {
|
||||
UserService *UserService
|
||||
EndpointService *EndpointService
|
||||
ResourceControlService *ResourceControlService
|
||||
SettingsService *SettingsService
|
||||
VersionService *VersionService
|
||||
CurrentDBVersion int
|
||||
store *Store
|
||||
}
|
||||
|
||||
// NewMigrator creates a new Migrator.
|
||||
func NewMigrator(store *Store, version int) *Migrator {
|
||||
return &Migrator{
|
||||
UserService: store.UserService,
|
||||
EndpointService: store.EndpointService,
|
||||
ResourceControlService: store.ResourceControlService,
|
||||
SettingsService: store.SettingsService,
|
||||
VersionService: store.VersionService,
|
||||
CurrentDBVersion: version,
|
||||
store: store,
|
||||
}
|
||||
}
|
||||
|
||||
// Migrate checks the database version and migrate the existing data to the most recent data model.
|
||||
func (m *Migrator) Migrate() error {
|
||||
|
||||
// Portainer < 1.12
|
||||
if m.CurrentDBVersion < 1 {
|
||||
err := m.updateAdminUserToDBVersion1()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 1.12.x
|
||||
if m.CurrentDBVersion < 2 {
|
||||
err := m.updateResourceControlsToDBVersion2()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = m.updateEndpointsToDBVersion2()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 1.13.x
|
||||
if m.CurrentDBVersion < 3 {
|
||||
err := m.updateSettingsToDBVersion3()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 1.14.0
|
||||
if m.CurrentDBVersion < 4 {
|
||||
err := m.updateEndpointsToDBVersion4()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/portainer/portainer/issues/1235
|
||||
if m.CurrentDBVersion < 5 {
|
||||
err := m.updateSettingsToVersion5()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/portainer/portainer/issues/1236
|
||||
if m.CurrentDBVersion < 6 {
|
||||
err := m.updateSettingsToVersion6()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/portainer/portainer/issues/1449
|
||||
if m.CurrentDBVersion < 7 {
|
||||
err := m.updateSettingsToVersion7()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if m.CurrentDBVersion < 8 {
|
||||
err := m.updateEndpointsToVersion8()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// https: //github.com/portainer/portainer/issues/1396
|
||||
if m.CurrentDBVersion < 9 {
|
||||
err := m.updateEndpointsToVersion9()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/portainer/portainer/issues/461
|
||||
if m.CurrentDBVersion < 10 {
|
||||
err := m.updateEndpointsToVersion10()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err := m.VersionService.StoreDBVersion(portainer.DBVersion)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package migrator
|
||||
|
||||
import (
|
||||
"github.com/boltdb/bolt"
|
||||
"github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/bolt/errors"
|
||||
"github.com/portainer/portainer/api/bolt/user"
|
||||
)
|
||||
|
||||
func (m *Migrator) updateAdminUserToDBVersion1() error {
|
||||
u, err := m.userService.UserByUsername("admin")
|
||||
if err == nil {
|
||||
admin := &portainer.User{
|
||||
Username: "admin",
|
||||
Password: u.Password,
|
||||
Role: portainer.AdministratorRole,
|
||||
}
|
||||
err = m.userService.CreateUser(admin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = m.removeLegacyAdminUser()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if err != nil && err != errors.ErrObjectNotFound {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Migrator) removeLegacyAdminUser() error {
|
||||
return m.db.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(user.BucketName))
|
||||
return bucket.Delete([]byte("admin"))
|
||||
})
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package migrator
|
||||
|
||||
import "github.com/portainer/portainer/api"
|
||||
|
||||
func (m *Migrator) updateEndpointsToVersion11() error {
|
||||
legacyEndpoints, err := m.endpointService.Endpoints()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, endpoint := range legacyEndpoints {
|
||||
if endpoint.Type == portainer.AgentOnDockerEnvironment {
|
||||
endpoint.TLSConfig.TLS = true
|
||||
endpoint.TLSConfig.TLSSkipVerify = true
|
||||
} else {
|
||||
if endpoint.TLSConfig.TLSSkipVerify && !endpoint.TLSConfig.TLS {
|
||||
endpoint.TLSConfig.TLSSkipVerify = false
|
||||
}
|
||||
}
|
||||
|
||||
err = m.endpointService.UpdateEndpoint(endpoint.ID, &endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
package migrator
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
"github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/bolt/internal"
|
||||
"github.com/portainer/portainer/api/bolt/stack"
|
||||
)
|
||||
|
||||
func (m *Migrator) updateEndpointsToVersion12() error {
|
||||
legacyEndpoints, err := m.endpointService.Endpoints()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, endpoint := range legacyEndpoints {
|
||||
endpoint.Tags = []string{}
|
||||
|
||||
err = m.endpointService.UpdateEndpoint(endpoint.ID, &endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Migrator) updateEndpointGroupsToVersion12() error {
|
||||
legacyEndpointGroups, err := m.endpointGroupService.EndpointGroups()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, group := range legacyEndpointGroups {
|
||||
group.Tags = []string{}
|
||||
|
||||
err = m.endpointGroupService.UpdateEndpointGroup(group.ID, &group)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type legacyStack struct {
|
||||
ID string `json:"Id"`
|
||||
Name string `json:"Name"`
|
||||
EndpointID portainer.EndpointID `json:"EndpointId"`
|
||||
SwarmID string `json:"SwarmId"`
|
||||
EntryPoint string `json:"EntryPoint"`
|
||||
Env []portainer.Pair `json:"Env"`
|
||||
ProjectPath string
|
||||
}
|
||||
|
||||
func (m *Migrator) updateStacksToVersion12() error {
|
||||
legacyStacks, err := m.retrieveLegacyStacks()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, legacyStack := range legacyStacks {
|
||||
err := m.convertLegacyStack(&legacyStack)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Migrator) convertLegacyStack(s *legacyStack) error {
|
||||
stackID := m.stackService.GetNextIdentifier()
|
||||
stack := &portainer.Stack{
|
||||
ID: portainer.StackID(stackID),
|
||||
Name: s.Name,
|
||||
Type: portainer.DockerSwarmStack,
|
||||
SwarmID: s.SwarmID,
|
||||
EndpointID: 0,
|
||||
EntryPoint: s.EntryPoint,
|
||||
Env: s.Env,
|
||||
}
|
||||
|
||||
stack.ProjectPath = strings.Replace(s.ProjectPath, s.ID, strconv.Itoa(stackID), 1)
|
||||
err := m.fileService.Rename(s.ProjectPath, stack.ProjectPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = m.deleteLegacyStack(s.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return m.stackService.CreateStack(stack)
|
||||
}
|
||||
|
||||
func (m *Migrator) deleteLegacyStack(legacyID string) error {
|
||||
return m.db.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(stack.BucketName))
|
||||
return bucket.Delete([]byte(legacyID))
|
||||
})
|
||||
}
|
||||
|
||||
func (m *Migrator) retrieveLegacyStacks() ([]legacyStack, error) {
|
||||
var legacyStacks = make([]legacyStack, 0)
|
||||
err := m.db.View(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(stack.BucketName))
|
||||
cursor := bucket.Cursor()
|
||||
|
||||
for k, v := cursor.First(); k != nil; k, v = cursor.Next() {
|
||||
var stack legacyStack
|
||||
err := internal.UnmarshalObject(v, &stack)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
legacyStacks = append(legacyStacks, stack)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return legacyStacks, err
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package migrator
|
||||
|
||||
import "github.com/portainer/portainer/api"
|
||||
|
||||
func (m *Migrator) updateSettingsToVersion13() error {
|
||||
legacySettings, err := m.settingsService.Settings()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
legacySettings.LDAPSettings.AutoCreateUsers = false
|
||||
legacySettings.LDAPSettings.GroupSearchSettings = []portainer.LDAPGroupSearchSettings{
|
||||
portainer.LDAPGroupSearchSettings{},
|
||||
}
|
||||
|
||||
return m.settingsService.UpdateSettings(legacySettings)
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package migrator
|
||||
|
||||
func (m *Migrator) updateSettingsToDBVersion15() error {
|
||||
legacySettings, err := m.settingsService.Settings()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
legacySettings.EnableHostManagementFeatures = false
|
||||
return m.settingsService.UpdateSettings(legacySettings)
|
||||
}
|
||||
|
||||
func (m *Migrator) updateTemplatesToVersion15() error {
|
||||
// Removed with the entire template management layer, part of https://github.com/portainer/portainer/issues/3707
|
||||
return nil
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package migrator
|
||||
|
||||
func (m *Migrator) updateSettingsToDBVersion16() error {
|
||||
legacySettings, err := m.settingsService.Settings()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if legacySettings.SnapshotInterval == "" {
|
||||
legacySettings.SnapshotInterval = "5m"
|
||||
}
|
||||
|
||||
return m.settingsService.UpdateSettings(legacySettings)
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package migrator
|
||||
|
||||
func (m *Migrator) updateExtensionsToDBVersion17() error {
|
||||
legacyExtensions, err := m.extensionService.Extensions()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, extension := range legacyExtensions {
|
||||
extension.License.Valid = true
|
||||
|
||||
err = m.extensionService.Persist(&extension)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
package migrator
|
||||
|
||||
import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
)
|
||||
|
||||
func (m *Migrator) updateUsersToDBVersion18() error {
|
||||
legacyUsers, err := m.userService.Users()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, user := range legacyUsers {
|
||||
user.PortainerAuthorizations = map[portainer.Authorization]bool{
|
||||
portainer.OperationPortainerDockerHubInspect: true,
|
||||
portainer.OperationPortainerEndpointGroupList: true,
|
||||
portainer.OperationPortainerEndpointList: true,
|
||||
portainer.OperationPortainerEndpointInspect: true,
|
||||
portainer.OperationPortainerEndpointExtensionAdd: true,
|
||||
portainer.OperationPortainerEndpointExtensionRemove: true,
|
||||
portainer.OperationPortainerExtensionList: true,
|
||||
portainer.OperationPortainerMOTD: true,
|
||||
portainer.OperationPortainerRegistryList: true,
|
||||
portainer.OperationPortainerRegistryInspect: true,
|
||||
portainer.OperationPortainerTeamList: true,
|
||||
portainer.OperationPortainerTemplateList: true,
|
||||
portainer.OperationPortainerTemplateInspect: true,
|
||||
portainer.OperationPortainerUserList: true,
|
||||
portainer.OperationPortainerUserMemberships: true,
|
||||
}
|
||||
|
||||
err = m.userService.UpdateUser(user.ID, &user)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Migrator) updateEndpointsToDBVersion18() error {
|
||||
legacyEndpoints, err := m.endpointService.Endpoints()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, endpoint := range legacyEndpoints {
|
||||
endpoint.UserAccessPolicies = make(portainer.UserAccessPolicies)
|
||||
for _, userID := range endpoint.AuthorizedUsers {
|
||||
endpoint.UserAccessPolicies[userID] = portainer.AccessPolicy{
|
||||
RoleID: 4,
|
||||
}
|
||||
}
|
||||
|
||||
endpoint.TeamAccessPolicies = make(portainer.TeamAccessPolicies)
|
||||
for _, teamID := range endpoint.AuthorizedTeams {
|
||||
endpoint.TeamAccessPolicies[teamID] = portainer.AccessPolicy{
|
||||
RoleID: 4,
|
||||
}
|
||||
}
|
||||
|
||||
err = m.endpointService.UpdateEndpoint(endpoint.ID, &endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Migrator) updateEndpointGroupsToDBVersion18() error {
|
||||
legacyEndpointGroups, err := m.endpointGroupService.EndpointGroups()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, endpointGroup := range legacyEndpointGroups {
|
||||
endpointGroup.UserAccessPolicies = make(portainer.UserAccessPolicies)
|
||||
for _, userID := range endpointGroup.AuthorizedUsers {
|
||||
endpointGroup.UserAccessPolicies[userID] = portainer.AccessPolicy{
|
||||
RoleID: 4,
|
||||
}
|
||||
}
|
||||
|
||||
endpointGroup.TeamAccessPolicies = make(portainer.TeamAccessPolicies)
|
||||
for _, teamID := range endpointGroup.AuthorizedTeams {
|
||||
endpointGroup.TeamAccessPolicies[teamID] = portainer.AccessPolicy{
|
||||
RoleID: 4,
|
||||
}
|
||||
}
|
||||
|
||||
err = m.endpointGroupService.UpdateEndpointGroup(endpointGroup.ID, &endpointGroup)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Migrator) updateRegistriesToDBVersion18() error {
|
||||
legacyRegistries, err := m.registryService.Registries()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, registry := range legacyRegistries {
|
||||
registry.UserAccessPolicies = make(portainer.UserAccessPolicies)
|
||||
for _, userID := range registry.AuthorizedUsers {
|
||||
registry.UserAccessPolicies[userID] = portainer.AccessPolicy{}
|
||||
}
|
||||
|
||||
registry.TeamAccessPolicies = make(portainer.TeamAccessPolicies)
|
||||
for _, teamID := range registry.AuthorizedTeams {
|
||||
registry.TeamAccessPolicies[teamID] = portainer.AccessPolicy{}
|
||||
}
|
||||
|
||||
err = m.registryService.UpdateRegistry(registry.ID, ®istry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package migrator
|
||||
|
||||
import portainer "github.com/portainer/portainer/api"
|
||||
|
||||
func (m *Migrator) updateSettingsToDBVersion19() error {
|
||||
legacySettings, err := m.settingsService.Settings()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if legacySettings.EdgeAgentCheckinInterval == 0 {
|
||||
legacySettings.EdgeAgentCheckinInterval = portainer.DefaultEdgeAgentCheckinIntervalInSeconds
|
||||
}
|
||||
|
||||
return m.settingsService.UpdateSettings(legacySettings)
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
package migrator
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
const scheduleScriptExecutionJobType = 1
|
||||
|
||||
func (m *Migrator) updateUsersToDBVersion20() error {
|
||||
return m.authorizationService.UpdateUsersAuthorizations()
|
||||
}
|
||||
|
||||
func (m *Migrator) updateSettingsToDBVersion20() error {
|
||||
legacySettings, err := m.settingsService.Settings()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
legacySettings.AllowVolumeBrowserForRegularUsers = false
|
||||
|
||||
return m.settingsService.UpdateSettings(legacySettings)
|
||||
}
|
||||
|
||||
func (m *Migrator) updateSchedulesToDBVersion20() error {
|
||||
legacySchedules, err := m.scheduleService.Schedules()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, schedule := range legacySchedules {
|
||||
if schedule.JobType == scheduleScriptExecutionJobType {
|
||||
if schedule.CronExpression == "0 0 * * *" {
|
||||
schedule.CronExpression = "0 * * * *"
|
||||
} else if schedule.CronExpression == "0 0 0/2 * *" {
|
||||
schedule.CronExpression = "0 */2 * * *"
|
||||
} else if schedule.CronExpression == "0 0 0 * *" {
|
||||
schedule.CronExpression = "0 0 * * *"
|
||||
} else {
|
||||
revisedCronExpression := strings.Split(schedule.CronExpression, " ")
|
||||
if len(revisedCronExpression) == 5 {
|
||||
continue
|
||||
}
|
||||
|
||||
revisedCronExpression = revisedCronExpression[1:]
|
||||
schedule.CronExpression = strings.Join(revisedCronExpression, " ")
|
||||
}
|
||||
|
||||
err := m.scheduleService.UpdateSchedule(schedule.ID, &schedule)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
package migrator
|
||||
|
||||
import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/internal/authorization"
|
||||
)
|
||||
|
||||
func (m *Migrator) updateResourceControlsToDBVersion22() error {
|
||||
legacyResourceControls, err := m.resourceControlService.ResourceControls()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, resourceControl := range legacyResourceControls {
|
||||
resourceControl.AdministratorsOnly = false
|
||||
|
||||
err := m.resourceControlService.UpdateResourceControl(resourceControl.ID, &resourceControl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Migrator) updateUsersAndRolesToDBVersion22() error {
|
||||
legacyUsers, err := m.userService.Users()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings, err := m.settingsService.Settings()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, user := range legacyUsers {
|
||||
user.PortainerAuthorizations = authorization.DefaultPortainerAuthorizations()
|
||||
err = m.userService.UpdateUser(user.ID, &user)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
endpointAdministratorRole, err := m.roleService.Role(portainer.RoleID(1))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
endpointAdministratorRole.Priority = 1
|
||||
endpointAdministratorRole.Authorizations = authorization.DefaultEndpointAuthorizationsForEndpointAdministratorRole()
|
||||
|
||||
err = m.roleService.UpdateRole(endpointAdministratorRole.ID, endpointAdministratorRole)
|
||||
|
||||
helpDeskRole, err := m.roleService.Role(portainer.RoleID(2))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
helpDeskRole.Priority = 2
|
||||
helpDeskRole.Authorizations = authorization.DefaultEndpointAuthorizationsForHelpDeskRole(settings.AllowVolumeBrowserForRegularUsers)
|
||||
|
||||
err = m.roleService.UpdateRole(helpDeskRole.ID, helpDeskRole)
|
||||
|
||||
standardUserRole, err := m.roleService.Role(portainer.RoleID(3))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
standardUserRole.Priority = 3
|
||||
standardUserRole.Authorizations = authorization.DefaultEndpointAuthorizationsForStandardUserRole(settings.AllowVolumeBrowserForRegularUsers)
|
||||
|
||||
err = m.roleService.UpdateRole(standardUserRole.ID, standardUserRole)
|
||||
|
||||
readOnlyUserRole, err := m.roleService.Role(portainer.RoleID(4))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
readOnlyUserRole.Priority = 4
|
||||
readOnlyUserRole.Authorizations = authorization.DefaultEndpointAuthorizationsForReadOnlyUserRole(settings.AllowVolumeBrowserForRegularUsers)
|
||||
|
||||
err = m.roleService.UpdateRole(readOnlyUserRole.ID, readOnlyUserRole)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return m.authorizationService.UpdateUsersAuthorizations()
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
package migrator
|
||||
|
||||
import "github.com/portainer/portainer/api"
|
||||
|
||||
func (m *Migrator) updateTagsToDBVersion23() error {
|
||||
tags, err := m.tagService.Tags()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, tag := range tags {
|
||||
tag.EndpointGroups = make(map[portainer.EndpointGroupID]bool)
|
||||
tag.Endpoints = make(map[portainer.EndpointID]bool)
|
||||
err = m.tagService.UpdateTag(tag.ID, &tag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Migrator) updateEndpointsAndEndpointGroupsToDBVersion23() error {
|
||||
tags, err := m.tagService.Tags()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tagsNameMap := make(map[string]portainer.Tag)
|
||||
for _, tag := range tags {
|
||||
tagsNameMap[tag.Name] = tag
|
||||
}
|
||||
|
||||
endpoints, err := m.endpointService.Endpoints()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, endpoint := range endpoints {
|
||||
endpointTags := make([]portainer.TagID, 0)
|
||||
for _, tagName := range endpoint.Tags {
|
||||
tag, ok := tagsNameMap[tagName]
|
||||
if ok {
|
||||
endpointTags = append(endpointTags, tag.ID)
|
||||
tag.Endpoints[endpoint.ID] = true
|
||||
}
|
||||
}
|
||||
endpoint.TagIDs = endpointTags
|
||||
err = m.endpointService.UpdateEndpoint(endpoint.ID, &endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
relation := &portainer.EndpointRelation{
|
||||
EndpointID: endpoint.ID,
|
||||
EdgeStacks: map[portainer.EdgeStackID]bool{},
|
||||
}
|
||||
|
||||
err = m.endpointRelationService.CreateEndpointRelation(relation)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
endpointGroups, err := m.endpointGroupService.EndpointGroups()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, endpointGroup := range endpointGroups {
|
||||
endpointGroupTags := make([]portainer.TagID, 0)
|
||||
for _, tagName := range endpointGroup.Tags {
|
||||
tag, ok := tagsNameMap[tagName]
|
||||
if ok {
|
||||
endpointGroupTags = append(endpointGroupTags, tag.ID)
|
||||
tag.EndpointGroups[endpointGroup.ID] = true
|
||||
}
|
||||
}
|
||||
endpointGroup.TagIDs = endpointGroupTags
|
||||
err = m.endpointGroupService.UpdateEndpointGroup(endpointGroup.ID, &endpointGroup)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, tag := range tagsNameMap {
|
||||
err = m.tagService.UpdateTag(tag.ID, &tag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package migrator
|
||||
|
||||
import portainer "github.com/portainer/portainer/api"
|
||||
|
||||
func (m *Migrator) updateSettingsToDB24() error {
|
||||
legacySettings, err := m.settingsService.Settings()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
legacySettings.AllowHostNamespaceForRegularUsers = true
|
||||
legacySettings.AllowDeviceMappingForRegularUsers = true
|
||||
legacySettings.AllowStackManagementForRegularUsers = true
|
||||
|
||||
return m.settingsService.UpdateSettings(legacySettings)
|
||||
}
|
||||
|
||||
func (m *Migrator) updateStacksToDB24() error {
|
||||
stacks, err := m.stackService.Stacks()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for idx := range stacks {
|
||||
stack := &stacks[idx]
|
||||
stack.Status = portainer.StackStatusActive
|
||||
err := m.stackService.UpdateStack(stack.ID, stack)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package migrator
|
||||
|
||||
import (
|
||||
"github.com/portainer/portainer/api"
|
||||
)
|
||||
|
||||
func (m *Migrator) updateSettingsToDB25() error {
|
||||
legacySettings, err := m.settingsService.Settings()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if legacySettings.TemplatesURL == "" {
|
||||
legacySettings.TemplatesURL = portainer.DefaultTemplatesURL
|
||||
}
|
||||
|
||||
legacySettings.UserSessionTimeout = portainer.DefaultUserSessionTimeout
|
||||
legacySettings.EnableTelemetry = true
|
||||
|
||||
legacySettings.AllowContainerCapabilitiesForRegularUsers = true
|
||||
|
||||
return m.settingsService.UpdateSettings(legacySettings)
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
package migrator
|
||||
|
||||
import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
)
|
||||
|
||||
func (m *Migrator) updateEndpointSettingsToDB25() error {
|
||||
settings, err := m.settingsService.Settings()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
endpoints, err := m.endpointService.Endpoints()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i := range endpoints {
|
||||
endpoint := endpoints[i]
|
||||
|
||||
securitySettings := portainer.EndpointSecuritySettings{}
|
||||
|
||||
if endpoint.Type == portainer.EdgeAgentOnDockerEnvironment ||
|
||||
endpoint.Type == portainer.AgentOnDockerEnvironment ||
|
||||
endpoint.Type == portainer.DockerEnvironment {
|
||||
|
||||
securitySettings = portainer.EndpointSecuritySettings{
|
||||
AllowBindMountsForRegularUsers: settings.AllowBindMountsForRegularUsers,
|
||||
AllowContainerCapabilitiesForRegularUsers: settings.AllowContainerCapabilitiesForRegularUsers,
|
||||
AllowDeviceMappingForRegularUsers: settings.AllowDeviceMappingForRegularUsers,
|
||||
AllowHostNamespaceForRegularUsers: settings.AllowHostNamespaceForRegularUsers,
|
||||
AllowPrivilegedModeForRegularUsers: settings.AllowPrivilegedModeForRegularUsers,
|
||||
AllowStackManagementForRegularUsers: settings.AllowStackManagementForRegularUsers,
|
||||
}
|
||||
|
||||
if endpoint.Type == portainer.AgentOnDockerEnvironment || endpoint.Type == portainer.EdgeAgentOnDockerEnvironment {
|
||||
securitySettings.AllowVolumeBrowserForRegularUsers = settings.AllowVolumeBrowserForRegularUsers
|
||||
securitySettings.EnableHostManagementFeatures = settings.EnableHostManagementFeatures
|
||||
}
|
||||
}
|
||||
|
||||
endpoint.SecuritySettings = securitySettings
|
||||
|
||||
err = m.endpointService.UpdateEndpoint(endpoint.ID, &endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package migrator
|
||||
|
||||
func (m *Migrator) migrateDBVersionTo30() error {
|
||||
if err := m.migrateSettings(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Migrator) migrateSettings() error {
|
||||
legacySettings, err := m.settingsService.Settings()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
legacySettings.OAuthSettings.SSO = false
|
||||
legacySettings.OAuthSettings.LogoutURI = ""
|
||||
return m.settingsService.UpdateSettings(legacySettings)
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
package migrator
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
"github.com/portainer/portainer/api/bolt/internal"
|
||||
"github.com/portainer/portainer/api/bolt/settings"
|
||||
)
|
||||
|
||||
var (
|
||||
testingDBStorePath string
|
||||
testingDBFileName string
|
||||
dummyLogoURL string
|
||||
dbConn *bolt.DB
|
||||
settingsService *settings.Service
|
||||
)
|
||||
|
||||
// initTestingDBConn creates a raw bolt DB connection
|
||||
// for unit testing usage only since using NewStore will cause cycle import inside migrator pkg
|
||||
func initTestingDBConn(storePath, fileName string) (*bolt.DB, error) {
|
||||
databasePath := path.Join(storePath, fileName)
|
||||
dbConn, err := bolt.Open(databasePath, 0600, &bolt.Options{Timeout: 1 * time.Second})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dbConn, nil
|
||||
}
|
||||
|
||||
// initTestingDBConn creates a settings service with raw bolt DB connection
|
||||
// for unit testing usage only since using NewStore will cause cycle import inside migrator pkg
|
||||
func initTestingSettingsService(dbConn *bolt.DB, preSetObj map[string]interface{}) (*settings.Service, error) {
|
||||
internalDBConn := &internal.DbConnection{
|
||||
DB: dbConn,
|
||||
}
|
||||
settingsService, err := settings.NewService(internalDBConn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
//insert a obj
|
||||
if err := internal.UpdateObject(internalDBConn, "settings", []byte("SETTINGS"), preSetObj); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return settingsService, nil
|
||||
}
|
||||
|
||||
func setup() error {
|
||||
testingDBStorePath, _ = os.Getwd()
|
||||
testingDBFileName = "portainer-ee-mig-30.db"
|
||||
dummyLogoURL = "example.com"
|
||||
var err error
|
||||
dbConn, err = initTestingDBConn(testingDBStorePath, testingDBFileName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dummySettingsObj := map[string]interface{}{
|
||||
"LogoURL": dummyLogoURL,
|
||||
}
|
||||
settingsService, err = initTestingSettingsService(dbConn, dummySettingsObj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestMigrateSettings(t *testing.T) {
|
||||
if err := setup(); err != nil {
|
||||
t.Errorf("failed to complete testing setups, err: %v", err)
|
||||
}
|
||||
defer dbConn.Close()
|
||||
defer os.Remove(testingDBFileName)
|
||||
m := &Migrator{
|
||||
db: dbConn,
|
||||
settingsService: settingsService,
|
||||
}
|
||||
if err := m.migrateSettings(); err != nil {
|
||||
t.Errorf("failed to update settings: %v", err)
|
||||
}
|
||||
updatedSettings, err := m.settingsService.Settings()
|
||||
if err != nil {
|
||||
t.Errorf("failed to retrieve the updated settings: %v", err)
|
||||
}
|
||||
if updatedSettings.LogoURL != dummyLogoURL {
|
||||
t.Errorf("unexpected value changes in the updated settings, want LogoURL value: %s, got LogoURL value: %s", dummyLogoURL, updatedSettings.LogoURL)
|
||||
}
|
||||
if updatedSettings.OAuthSettings.SSO != false {
|
||||
t.Errorf("unexpected default OAuth SSO setting, want: false, got: %t", updatedSettings.OAuthSettings.SSO)
|
||||
}
|
||||
if updatedSettings.OAuthSettings.LogoutURI != "" {
|
||||
t.Errorf("unexpected default OAuth HideInternalAuth setting, want:, got: %s", updatedSettings.OAuthSettings.LogoutURI)
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package migrator
|
||||
|
||||
func (m *Migrator) updateSettingsToVersion5() error {
|
||||
legacySettings, err := m.settingsService.Settings()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
legacySettings.AllowBindMountsForRegularUsers = true
|
||||
return m.settingsService.UpdateSettings(legacySettings)
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package migrator
|
||||
|
||||
func (m *Migrator) updateSettingsToVersion6() error {
|
||||
legacySettings, err := m.settingsService.Settings()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
legacySettings.AllowPrivilegedModeForRegularUsers = true
|
||||
return m.settingsService.UpdateSettings(legacySettings)
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package migrator
|
||||
|
||||
func (m *Migrator) updateSettingsToVersion7() error {
|
||||
legacySettings, err := m.settingsService.Settings()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
legacySettings.DisplayDonationHeader = true
|
||||
|
||||
return m.settingsService.UpdateSettings(legacySettings)
|
||||
}
|
||||
@@ -1,370 +0,0 @@
|
||||
package migrator
|
||||
|
||||
import (
|
||||
"github.com/boltdb/bolt"
|
||||
portainer "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"
|
||||
"github.com/portainer/portainer/api/bolt/extension"
|
||||
"github.com/portainer/portainer/api/bolt/registry"
|
||||
"github.com/portainer/portainer/api/bolt/resourcecontrol"
|
||||
"github.com/portainer/portainer/api/bolt/role"
|
||||
"github.com/portainer/portainer/api/bolt/schedule"
|
||||
"github.com/portainer/portainer/api/bolt/settings"
|
||||
"github.com/portainer/portainer/api/bolt/stack"
|
||||
"github.com/portainer/portainer/api/bolt/tag"
|
||||
"github.com/portainer/portainer/api/bolt/teammembership"
|
||||
"github.com/portainer/portainer/api/bolt/user"
|
||||
"github.com/portainer/portainer/api/bolt/version"
|
||||
"github.com/portainer/portainer/api/internal/authorization"
|
||||
)
|
||||
|
||||
type (
|
||||
// Migrator defines a service to migrate data after a Portainer version update.
|
||||
Migrator struct {
|
||||
currentDBVersion int
|
||||
db *bolt.DB
|
||||
endpointGroupService *endpointgroup.Service
|
||||
endpointService *endpoint.Service
|
||||
endpointRelationService *endpointrelation.Service
|
||||
extensionService *extension.Service
|
||||
registryService *registry.Service
|
||||
resourceControlService *resourcecontrol.Service
|
||||
roleService *role.Service
|
||||
scheduleService *schedule.Service
|
||||
settingsService *settings.Service
|
||||
stackService *stack.Service
|
||||
tagService *tag.Service
|
||||
teamMembershipService *teammembership.Service
|
||||
userService *user.Service
|
||||
versionService *version.Service
|
||||
fileService portainer.FileService
|
||||
authorizationService *authorization.Service
|
||||
}
|
||||
|
||||
// Parameters represents the required parameters to create a new Migrator instance.
|
||||
Parameters struct {
|
||||
DB *bolt.DB
|
||||
DatabaseVersion int
|
||||
EndpointGroupService *endpointgroup.Service
|
||||
EndpointService *endpoint.Service
|
||||
EndpointRelationService *endpointrelation.Service
|
||||
ExtensionService *extension.Service
|
||||
RegistryService *registry.Service
|
||||
ResourceControlService *resourcecontrol.Service
|
||||
RoleService *role.Service
|
||||
ScheduleService *schedule.Service
|
||||
SettingsService *settings.Service
|
||||
StackService *stack.Service
|
||||
TagService *tag.Service
|
||||
TeamMembershipService *teammembership.Service
|
||||
UserService *user.Service
|
||||
VersionService *version.Service
|
||||
FileService portainer.FileService
|
||||
AuthorizationService *authorization.Service
|
||||
}
|
||||
)
|
||||
|
||||
// NewMigrator creates a new Migrator.
|
||||
func NewMigrator(parameters *Parameters) *Migrator {
|
||||
return &Migrator{
|
||||
db: parameters.DB,
|
||||
currentDBVersion: parameters.DatabaseVersion,
|
||||
endpointGroupService: parameters.EndpointGroupService,
|
||||
endpointService: parameters.EndpointService,
|
||||
endpointRelationService: parameters.EndpointRelationService,
|
||||
extensionService: parameters.ExtensionService,
|
||||
registryService: parameters.RegistryService,
|
||||
resourceControlService: parameters.ResourceControlService,
|
||||
roleService: parameters.RoleService,
|
||||
scheduleService: parameters.ScheduleService,
|
||||
settingsService: parameters.SettingsService,
|
||||
tagService: parameters.TagService,
|
||||
teamMembershipService: parameters.TeamMembershipService,
|
||||
stackService: parameters.StackService,
|
||||
userService: parameters.UserService,
|
||||
versionService: parameters.VersionService,
|
||||
fileService: parameters.FileService,
|
||||
authorizationService: parameters.AuthorizationService,
|
||||
}
|
||||
}
|
||||
|
||||
// Migrate checks the database version and migrate the existing data to the most recent data model.
|
||||
func (m *Migrator) Migrate() error {
|
||||
// Portainer < 1.12
|
||||
if m.currentDBVersion < 1 {
|
||||
err := m.updateAdminUserToDBVersion1()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 1.12.x
|
||||
if m.currentDBVersion < 2 {
|
||||
err := m.updateResourceControlsToDBVersion2()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = m.updateEndpointsToDBVersion2()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 1.13.x
|
||||
if m.currentDBVersion < 3 {
|
||||
err := m.updateSettingsToDBVersion3()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 1.14.0
|
||||
if m.currentDBVersion < 4 {
|
||||
err := m.updateEndpointsToDBVersion4()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/portainer/portainer/issues/1235
|
||||
if m.currentDBVersion < 5 {
|
||||
err := m.updateSettingsToVersion5()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/portainer/portainer/issues/1236
|
||||
if m.currentDBVersion < 6 {
|
||||
err := m.updateSettingsToVersion6()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/portainer/portainer/issues/1449
|
||||
if m.currentDBVersion < 7 {
|
||||
err := m.updateSettingsToVersion7()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if m.currentDBVersion < 8 {
|
||||
err := m.updateEndpointsToVersion8()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// https: //github.com/portainer/portainer/issues/1396
|
||||
if m.currentDBVersion < 9 {
|
||||
err := m.updateEndpointsToVersion9()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/portainer/portainer/issues/461
|
||||
if m.currentDBVersion < 10 {
|
||||
err := m.updateEndpointsToVersion10()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/portainer/portainer/issues/1906
|
||||
if m.currentDBVersion < 11 {
|
||||
err := m.updateEndpointsToVersion11()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 1.18.0
|
||||
if m.currentDBVersion < 12 {
|
||||
err := m.updateEndpointsToVersion12()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = m.updateEndpointGroupsToVersion12()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = m.updateStacksToVersion12()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 1.19.0
|
||||
if m.currentDBVersion < 13 {
|
||||
err := m.updateSettingsToVersion13()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 1.19.2
|
||||
if m.currentDBVersion < 14 {
|
||||
err := m.updateResourceControlsToDBVersion14()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 1.20.0
|
||||
if m.currentDBVersion < 15 {
|
||||
err := m.updateSettingsToDBVersion15()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = m.updateTemplatesToVersion15()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if m.currentDBVersion < 16 {
|
||||
err := m.updateSettingsToDBVersion16()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 1.20.1
|
||||
if m.currentDBVersion < 17 {
|
||||
err := m.updateExtensionsToDBVersion17()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 1.21.0
|
||||
if m.currentDBVersion < 18 {
|
||||
err := m.updateUsersToDBVersion18()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = m.updateEndpointsToDBVersion18()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = m.updateEndpointGroupsToDBVersion18()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = m.updateRegistriesToDBVersion18()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 1.22.0
|
||||
if m.currentDBVersion < 19 {
|
||||
err := m.updateSettingsToDBVersion19()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 1.22.1
|
||||
if m.currentDBVersion < 20 {
|
||||
err := m.updateUsersToDBVersion20()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = m.updateSettingsToDBVersion20()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = m.updateSchedulesToDBVersion20()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 1.23.0
|
||||
// DBVersion 21 is missing as it was shipped as via hotfix 1.22.2
|
||||
if m.currentDBVersion < 22 {
|
||||
err := m.updateResourceControlsToDBVersion22()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = m.updateUsersAndRolesToDBVersion22()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 1.24.0
|
||||
if m.currentDBVersion < 23 {
|
||||
err := m.updateTagsToDBVersion23()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = m.updateEndpointsAndEndpointGroupsToDBVersion23()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 1.24.1
|
||||
if m.currentDBVersion < 24 {
|
||||
err := m.updateSettingsToDB24()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 2.0.0
|
||||
if m.currentDBVersion < 25 {
|
||||
err := m.updateSettingsToDB25()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = m.updateStacksToDB24()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 2.1.0
|
||||
if m.currentDBVersion < 26 {
|
||||
err := m.updateEndpointSettingsToDB25()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 2.2.0
|
||||
if m.currentDBVersion < 27 {
|
||||
err := m.updateStackResourceControlToDB27()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 2.6.0
|
||||
if m.currentDBVersion < 30 {
|
||||
err := m.migrateDBVersionTo30()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return m.versionService.StoreDBVersion(portainer.DBVersion)
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
package registry
|
||||
|
||||
import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/bolt/internal"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
)
|
||||
|
||||
const (
|
||||
// BucketName represents the name of the bucket where this service stores data.
|
||||
BucketName = "registries"
|
||||
)
|
||||
|
||||
// Service represents a service for managing endpoint data.
|
||||
type Service struct {
|
||||
connection *internal.DbConnection
|
||||
}
|
||||
|
||||
// NewService creates a new instance of a service.
|
||||
func NewService(connection *internal.DbConnection) (*Service, error) {
|
||||
err := internal.CreateBucket(connection, BucketName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Service{
|
||||
connection: connection,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Registry returns an registry by ID.
|
||||
func (service *Service) Registry(ID portainer.RegistryID) (*portainer.Registry, error) {
|
||||
var registry portainer.Registry
|
||||
identifier := internal.Itob(int(ID))
|
||||
|
||||
err := internal.GetObject(service.connection, BucketName, identifier, ®istry)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ®istry, nil
|
||||
}
|
||||
|
||||
// Registries returns an array containing all the registries.
|
||||
func (service *Service) Registries() ([]portainer.Registry, error) {
|
||||
var registries = make([]portainer.Registry, 0)
|
||||
|
||||
err := service.connection.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 registry portainer.Registry
|
||||
err := internal.UnmarshalObject(v, ®istry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
registries = append(registries, registry)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return registries, err
|
||||
}
|
||||
|
||||
// CreateRegistry creates a new registry.
|
||||
func (service *Service) CreateRegistry(registry *portainer.Registry) error {
|
||||
return service.connection.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(BucketName))
|
||||
|
||||
id, _ := bucket.NextSequence()
|
||||
registry.ID = portainer.RegistryID(id)
|
||||
|
||||
data, err := internal.MarshalObject(registry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return bucket.Put(internal.Itob(int(registry.ID)), data)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateRegistry updates an registry.
|
||||
func (service *Service) UpdateRegistry(ID portainer.RegistryID, registry *portainer.Registry) error {
|
||||
identifier := internal.Itob(int(ID))
|
||||
return internal.UpdateObject(service.connection, BucketName, identifier, registry)
|
||||
}
|
||||
|
||||
// DeleteRegistry deletes an registry.
|
||||
func (service *Service) DeleteRegistry(ID portainer.RegistryID) error {
|
||||
identifier := internal.Itob(int(ID))
|
||||
return internal.DeleteObject(service.connection, BucketName, identifier)
|
||||
}
|
||||
114
api/bolt/registry_service.go
Normal file
114
api/bolt/registry_service.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package bolt
|
||||
|
||||
import (
|
||||
"github.com/portainer/portainer"
|
||||
"github.com/portainer/portainer/bolt/internal"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
)
|
||||
|
||||
// RegistryService represents a service for managing registries.
|
||||
type RegistryService struct {
|
||||
store *Store
|
||||
}
|
||||
|
||||
// Registry returns an registry by ID.
|
||||
func (service *RegistryService) Registry(ID portainer.RegistryID) (*portainer.Registry, error) {
|
||||
var data []byte
|
||||
err := service.store.db.View(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(registryBucketName))
|
||||
value := bucket.Get(internal.Itob(int(ID)))
|
||||
if value == nil {
|
||||
return portainer.ErrRegistryNotFound
|
||||
}
|
||||
|
||||
data = make([]byte, len(value))
|
||||
copy(data, value)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var registry portainer.Registry
|
||||
err = internal.UnmarshalRegistry(data, ®istry)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ®istry, nil
|
||||
}
|
||||
|
||||
// Registries returns an array containing all the registries.
|
||||
func (service *RegistryService) Registries() ([]portainer.Registry, error) {
|
||||
var registries = make([]portainer.Registry, 0)
|
||||
err := service.store.db.View(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(registryBucketName))
|
||||
|
||||
cursor := bucket.Cursor()
|
||||
for k, v := cursor.First(); k != nil; k, v = cursor.Next() {
|
||||
var registry portainer.Registry
|
||||
err := internal.UnmarshalRegistry(v, ®istry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
registries = append(registries, registry)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return registries, nil
|
||||
}
|
||||
|
||||
// CreateRegistry creates a new registry.
|
||||
func (service *RegistryService) CreateRegistry(registry *portainer.Registry) error {
|
||||
return service.store.db.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(registryBucketName))
|
||||
|
||||
id, _ := bucket.NextSequence()
|
||||
registry.ID = portainer.RegistryID(id)
|
||||
|
||||
data, err := internal.MarshalRegistry(registry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = bucket.Put(internal.Itob(int(registry.ID)), data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateRegistry updates an registry.
|
||||
func (service *RegistryService) UpdateRegistry(ID portainer.RegistryID, registry *portainer.Registry) error {
|
||||
data, err := internal.MarshalRegistry(registry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return service.store.db.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(registryBucketName))
|
||||
err = bucket.Put(internal.Itob(int(ID)), data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteRegistry deletes an registry.
|
||||
func (service *RegistryService) DeleteRegistry(ID portainer.RegistryID) error {
|
||||
return service.store.db.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(registryBucketName))
|
||||
err := bucket.Delete(internal.Itob(int(ID)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
148
api/bolt/resource_control_service.go
Normal file
148
api/bolt/resource_control_service.go
Normal file
@@ -0,0 +1,148 @@
|
||||
package bolt
|
||||
|
||||
import (
|
||||
"github.com/portainer/portainer"
|
||||
"github.com/portainer/portainer/bolt/internal"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
)
|
||||
|
||||
// ResourceControlService represents a service for managing resource controls.
|
||||
type ResourceControlService struct {
|
||||
store *Store
|
||||
}
|
||||
|
||||
// ResourceControl returns a ResourceControl object by ID
|
||||
func (service *ResourceControlService) ResourceControl(ID portainer.ResourceControlID) (*portainer.ResourceControl, error) {
|
||||
var data []byte
|
||||
err := service.store.db.View(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(resourceControlBucketName))
|
||||
value := bucket.Get(internal.Itob(int(ID)))
|
||||
if value == nil {
|
||||
return portainer.ErrResourceControlNotFound
|
||||
}
|
||||
|
||||
data = make([]byte, len(value))
|
||||
copy(data, value)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var resourceControl portainer.ResourceControl
|
||||
err = internal.UnmarshalResourceControl(data, &resourceControl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &resourceControl, nil
|
||||
}
|
||||
|
||||
// ResourceControlByResourceID returns a ResourceControl object by checking if the resourceID is equal
|
||||
// to the main ResourceID or in SubResourceIDs
|
||||
func (service *ResourceControlService) ResourceControlByResourceID(resourceID string) (*portainer.ResourceControl, error) {
|
||||
var resourceControl *portainer.ResourceControl
|
||||
|
||||
err := service.store.db.View(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(resourceControlBucketName))
|
||||
cursor := bucket.Cursor()
|
||||
for k, v := cursor.First(); k != nil; k, v = cursor.Next() {
|
||||
var rc portainer.ResourceControl
|
||||
err := internal.UnmarshalResourceControl(v, &rc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if rc.ResourceID == resourceID {
|
||||
resourceControl = &rc
|
||||
}
|
||||
for _, subResourceID := range rc.SubResourceIDs {
|
||||
if subResourceID == resourceID {
|
||||
resourceControl = &rc
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if resourceControl == nil {
|
||||
return portainer.ErrResourceControlNotFound
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resourceControl, nil
|
||||
}
|
||||
|
||||
// ResourceControls returns all the ResourceControl objects
|
||||
func (service *ResourceControlService) ResourceControls() ([]portainer.ResourceControl, error) {
|
||||
var rcs = make([]portainer.ResourceControl, 0)
|
||||
err := service.store.db.View(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(resourceControlBucketName))
|
||||
|
||||
cursor := bucket.Cursor()
|
||||
for k, v := cursor.First(); k != nil; k, v = cursor.Next() {
|
||||
var resourceControl portainer.ResourceControl
|
||||
err := internal.UnmarshalResourceControl(v, &resourceControl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rcs = append(rcs, resourceControl)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return rcs, nil
|
||||
}
|
||||
|
||||
// CreateResourceControl creates a new ResourceControl object
|
||||
func (service *ResourceControlService) CreateResourceControl(resourceControl *portainer.ResourceControl) error {
|
||||
return service.store.db.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(resourceControlBucketName))
|
||||
id, _ := bucket.NextSequence()
|
||||
resourceControl.ID = portainer.ResourceControlID(id)
|
||||
data, err := internal.MarshalResourceControl(resourceControl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = bucket.Put(internal.Itob(int(resourceControl.ID)), data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateResourceControl saves a ResourceControl object.
|
||||
func (service *ResourceControlService) UpdateResourceControl(ID portainer.ResourceControlID, resourceControl *portainer.ResourceControl) error {
|
||||
data, err := internal.MarshalResourceControl(resourceControl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return service.store.db.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(resourceControlBucketName))
|
||||
err = bucket.Put(internal.Itob(int(ID)), data)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteResourceControl deletes a ResourceControl object by ID
|
||||
func (service *ResourceControlService) DeleteResourceControl(ID portainer.ResourceControlID) error {
|
||||
return service.store.db.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(resourceControlBucketName))
|
||||
err := bucket.Delete(internal.Itob(int(ID)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -1,131 +0,0 @@
|
||||
package resourcecontrol
|
||||
|
||||
import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/bolt/internal"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
)
|
||||
|
||||
const (
|
||||
// BucketName represents the name of the bucket where this service stores data.
|
||||
BucketName = "resource_control"
|
||||
)
|
||||
|
||||
// Service represents a service for managing endpoint data.
|
||||
type Service struct {
|
||||
connection *internal.DbConnection
|
||||
}
|
||||
|
||||
// NewService creates a new instance of a service.
|
||||
func NewService(connection *internal.DbConnection) (*Service, error) {
|
||||
err := internal.CreateBucket(connection, BucketName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Service{
|
||||
connection: connection,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ResourceControl returns a ResourceControl object by ID
|
||||
func (service *Service) ResourceControl(ID portainer.ResourceControlID) (*portainer.ResourceControl, error) {
|
||||
var resourceControl portainer.ResourceControl
|
||||
identifier := internal.Itob(int(ID))
|
||||
|
||||
err := internal.GetObject(service.connection, BucketName, identifier, &resourceControl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &resourceControl, nil
|
||||
}
|
||||
|
||||
// ResourceControlByResourceIDAndType returns a ResourceControl object by checking if the resourceID is equal
|
||||
// to the main ResourceID or in SubResourceIDs. It also performs a check on the resource type. Return nil
|
||||
// if no ResourceControl was found.
|
||||
func (service *Service) ResourceControlByResourceIDAndType(resourceID string, resourceType portainer.ResourceControlType) (*portainer.ResourceControl, error) {
|
||||
var resourceControl *portainer.ResourceControl
|
||||
|
||||
err := service.connection.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 rc portainer.ResourceControl
|
||||
err := internal.UnmarshalObject(v, &rc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if rc.ResourceID == resourceID && rc.Type == resourceType {
|
||||
resourceControl = &rc
|
||||
break
|
||||
}
|
||||
|
||||
for _, subResourceID := range rc.SubResourceIDs {
|
||||
if subResourceID == resourceID {
|
||||
resourceControl = &rc
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return resourceControl, err
|
||||
}
|
||||
|
||||
// ResourceControls returns all the ResourceControl objects
|
||||
func (service *Service) ResourceControls() ([]portainer.ResourceControl, error) {
|
||||
var rcs = make([]portainer.ResourceControl, 0)
|
||||
|
||||
err := service.connection.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 resourceControl portainer.ResourceControl
|
||||
err := internal.UnmarshalObject(v, &resourceControl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rcs = append(rcs, resourceControl)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return rcs, err
|
||||
}
|
||||
|
||||
// CreateResourceControl creates a new ResourceControl object
|
||||
func (service *Service) CreateResourceControl(resourceControl *portainer.ResourceControl) error {
|
||||
return service.connection.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(BucketName))
|
||||
|
||||
id, _ := bucket.NextSequence()
|
||||
resourceControl.ID = portainer.ResourceControlID(id)
|
||||
|
||||
data, err := internal.MarshalObject(resourceControl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return bucket.Put(internal.Itob(int(resourceControl.ID)), data)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateResourceControl saves a ResourceControl object.
|
||||
func (service *Service) UpdateResourceControl(ID portainer.ResourceControlID, resourceControl *portainer.ResourceControl) error {
|
||||
identifier := internal.Itob(int(ID))
|
||||
return internal.UpdateObject(service.connection, BucketName, identifier, resourceControl)
|
||||
}
|
||||
|
||||
// DeleteResourceControl deletes a ResourceControl object by ID
|
||||
func (service *Service) DeleteResourceControl(ID portainer.ResourceControlID) error {
|
||||
identifier := internal.Itob(int(ID))
|
||||
return internal.DeleteObject(service.connection, BucketName, identifier)
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
package role
|
||||
|
||||
import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/bolt/internal"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
)
|
||||
|
||||
const (
|
||||
// BucketName represents the name of the bucket where this service stores data.
|
||||
BucketName = "roles"
|
||||
)
|
||||
|
||||
// Service represents a service for managing endpoint data.
|
||||
type Service struct {
|
||||
connection *internal.DbConnection
|
||||
}
|
||||
|
||||
// NewService creates a new instance of a service.
|
||||
func NewService(connection *internal.DbConnection) (*Service, error) {
|
||||
err := internal.CreateBucket(connection, BucketName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Service{
|
||||
connection: connection,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Role returns a Role by ID
|
||||
func (service *Service) Role(ID portainer.RoleID) (*portainer.Role, error) {
|
||||
var set portainer.Role
|
||||
identifier := internal.Itob(int(ID))
|
||||
|
||||
err := internal.GetObject(service.connection, BucketName, identifier, &set)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &set, nil
|
||||
}
|
||||
|
||||
// Roles return an array containing all the sets.
|
||||
func (service *Service) Roles() ([]portainer.Role, error) {
|
||||
var sets = make([]portainer.Role, 0)
|
||||
|
||||
err := service.connection.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 set portainer.Role
|
||||
err := internal.UnmarshalObject(v, &set)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sets = append(sets, set)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return sets, err
|
||||
}
|
||||
|
||||
// CreateRole creates a new Role.
|
||||
func (service *Service) CreateRole(role *portainer.Role) error {
|
||||
return service.connection.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(BucketName))
|
||||
|
||||
id, _ := bucket.NextSequence()
|
||||
role.ID = portainer.RoleID(id)
|
||||
|
||||
data, err := internal.MarshalObject(role)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return bucket.Put(internal.Itob(int(role.ID)), data)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateRole updates a role.
|
||||
func (service *Service) UpdateRole(ID portainer.RoleID, role *portainer.Role) error {
|
||||
identifier := internal.Itob(int(ID))
|
||||
return internal.UpdateObject(service.connection, BucketName, identifier, role)
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
package schedule
|
||||
|
||||
import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/bolt/internal"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
)
|
||||
|
||||
const (
|
||||
// BucketName represents the name of the bucket where this service stores data.
|
||||
BucketName = "schedules"
|
||||
)
|
||||
|
||||
// Service represents a service for managing schedule data.
|
||||
type Service struct {
|
||||
connection *internal.DbConnection
|
||||
}
|
||||
|
||||
// NewService creates a new instance of a service.
|
||||
func NewService(connection *internal.DbConnection) (*Service, error) {
|
||||
err := internal.CreateBucket(connection, BucketName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Service{
|
||||
connection: connection,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Schedule returns a schedule by ID.
|
||||
func (service *Service) Schedule(ID portainer.ScheduleID) (*portainer.Schedule, error) {
|
||||
var schedule portainer.Schedule
|
||||
identifier := internal.Itob(int(ID))
|
||||
|
||||
err := internal.GetObject(service.connection, BucketName, identifier, &schedule)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &schedule, nil
|
||||
}
|
||||
|
||||
// UpdateSchedule updates a schedule.
|
||||
func (service *Service) UpdateSchedule(ID portainer.ScheduleID, schedule *portainer.Schedule) error {
|
||||
identifier := internal.Itob(int(ID))
|
||||
return internal.UpdateObject(service.connection, BucketName, identifier, schedule)
|
||||
}
|
||||
|
||||
// DeleteSchedule deletes a schedule.
|
||||
func (service *Service) DeleteSchedule(ID portainer.ScheduleID) error {
|
||||
identifier := internal.Itob(int(ID))
|
||||
return internal.DeleteObject(service.connection, BucketName, identifier)
|
||||
}
|
||||
|
||||
// Schedules return a array containing all the schedules.
|
||||
func (service *Service) Schedules() ([]portainer.Schedule, error) {
|
||||
var schedules = make([]portainer.Schedule, 0)
|
||||
|
||||
err := service.connection.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 schedule portainer.Schedule
|
||||
err := internal.UnmarshalObject(v, &schedule)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
schedules = append(schedules, schedule)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return schedules, err
|
||||
}
|
||||
|
||||
// SchedulesByJobType return a array containing all the schedules
|
||||
// with the specified JobType.
|
||||
func (service *Service) SchedulesByJobType(jobType portainer.JobType) ([]portainer.Schedule, error) {
|
||||
var schedules = make([]portainer.Schedule, 0)
|
||||
|
||||
err := service.connection.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 schedule portainer.Schedule
|
||||
err := internal.UnmarshalObject(v, &schedule)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if schedule.JobType == jobType {
|
||||
schedules = append(schedules, schedule)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return schedules, err
|
||||
}
|
||||
|
||||
// CreateSchedule assign an ID to a new schedule and saves it.
|
||||
func (service *Service) CreateSchedule(schedule *portainer.Schedule) error {
|
||||
return service.connection.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(BucketName))
|
||||
|
||||
// We manually manage sequences for schedules
|
||||
err := bucket.SetSequence(uint64(schedule.ID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data, err := internal.MarshalObject(schedule)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return bucket.Put(internal.Itob(int(schedule.ID)), data)
|
||||
})
|
||||
}
|
||||
|
||||
// GetNextIdentifier returns the next identifier for a schedule.
|
||||
func (service *Service) GetNextIdentifier() int {
|
||||
return internal.GetNextIdentifier(service.connection, BucketName)
|
||||
}
|
||||
@@ -1,263 +0,0 @@
|
||||
package bolt
|
||||
|
||||
import (
|
||||
portainer "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"
|
||||
"github.com/portainer/portainer/api/bolt/edgejob"
|
||||
"github.com/portainer/portainer/api/bolt/edgestack"
|
||||
"github.com/portainer/portainer/api/bolt/endpoint"
|
||||
"github.com/portainer/portainer/api/bolt/endpointgroup"
|
||||
"github.com/portainer/portainer/api/bolt/endpointrelation"
|
||||
"github.com/portainer/portainer/api/bolt/extension"
|
||||
"github.com/portainer/portainer/api/bolt/registry"
|
||||
"github.com/portainer/portainer/api/bolt/resourcecontrol"
|
||||
"github.com/portainer/portainer/api/bolt/role"
|
||||
"github.com/portainer/portainer/api/bolt/schedule"
|
||||
"github.com/portainer/portainer/api/bolt/settings"
|
||||
"github.com/portainer/portainer/api/bolt/stack"
|
||||
"github.com/portainer/portainer/api/bolt/tag"
|
||||
"github.com/portainer/portainer/api/bolt/team"
|
||||
"github.com/portainer/portainer/api/bolt/teammembership"
|
||||
"github.com/portainer/portainer/api/bolt/tunnelserver"
|
||||
"github.com/portainer/portainer/api/bolt/user"
|
||||
"github.com/portainer/portainer/api/bolt/version"
|
||||
"github.com/portainer/portainer/api/bolt/webhook"
|
||||
)
|
||||
|
||||
func (store *Store) initServices() error {
|
||||
authorizationsetService, err := role.NewService(store.connection)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
store.RoleService = authorizationsetService
|
||||
|
||||
customTemplateService, err := customtemplate.NewService(store.connection)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
store.CustomTemplateService = customTemplateService
|
||||
|
||||
dockerhubService, err := dockerhub.NewService(store.connection)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
store.DockerHubService = dockerhubService
|
||||
|
||||
edgeStackService, err := edgestack.NewService(store.connection)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
store.EdgeStackService = edgeStackService
|
||||
|
||||
edgeGroupService, err := edgegroup.NewService(store.connection)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
store.EdgeGroupService = edgeGroupService
|
||||
|
||||
edgeJobService, err := edgejob.NewService(store.connection)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
store.EdgeJobService = edgeJobService
|
||||
|
||||
endpointgroupService, err := endpointgroup.NewService(store.connection)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
store.EndpointGroupService = endpointgroupService
|
||||
|
||||
endpointService, err := endpoint.NewService(store.connection)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
store.EndpointService = endpointService
|
||||
|
||||
endpointRelationService, err := endpointrelation.NewService(store.connection)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
store.EndpointRelationService = endpointRelationService
|
||||
|
||||
extensionService, err := extension.NewService(store.connection)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
store.ExtensionService = extensionService
|
||||
|
||||
registryService, err := registry.NewService(store.connection)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
store.RegistryService = registryService
|
||||
|
||||
resourcecontrolService, err := resourcecontrol.NewService(store.connection)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
store.ResourceControlService = resourcecontrolService
|
||||
|
||||
settingsService, err := settings.NewService(store.connection)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
store.SettingsService = settingsService
|
||||
|
||||
stackService, err := stack.NewService(store.connection)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
store.StackService = stackService
|
||||
|
||||
tagService, err := tag.NewService(store.connection)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
store.TagService = tagService
|
||||
|
||||
teammembershipService, err := teammembership.NewService(store.connection)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
store.TeamMembershipService = teammembershipService
|
||||
|
||||
teamService, err := team.NewService(store.connection)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
store.TeamService = teamService
|
||||
|
||||
tunnelServerService, err := tunnelserver.NewService(store.connection)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
store.TunnelServerService = tunnelServerService
|
||||
|
||||
userService, err := user.NewService(store.connection)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
store.UserService = userService
|
||||
|
||||
versionService, err := version.NewService(store.connection)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
store.VersionService = versionService
|
||||
|
||||
webhookService, err := webhook.NewService(store.connection)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
store.WebhookService = webhookService
|
||||
|
||||
scheduleService, err := schedule.NewService(store.connection)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
store.ScheduleService = scheduleService
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CustomTemplate gives access to the CustomTemplate data management layer
|
||||
func (store *Store) CustomTemplate() portainer.CustomTemplateService {
|
||||
return store.CustomTemplateService
|
||||
}
|
||||
|
||||
// DockerHub gives access to the DockerHub data management layer
|
||||
func (store *Store) DockerHub() portainer.DockerHubService {
|
||||
return store.DockerHubService
|
||||
}
|
||||
|
||||
// EdgeGroup gives access to the EdgeGroup data management layer
|
||||
func (store *Store) EdgeGroup() portainer.EdgeGroupService {
|
||||
return store.EdgeGroupService
|
||||
}
|
||||
|
||||
// EdgeJob gives access to the EdgeJob data management layer
|
||||
func (store *Store) EdgeJob() portainer.EdgeJobService {
|
||||
return store.EdgeJobService
|
||||
}
|
||||
|
||||
// EdgeStack gives access to the EdgeStack data management layer
|
||||
func (store *Store) EdgeStack() portainer.EdgeStackService {
|
||||
return store.EdgeStackService
|
||||
}
|
||||
|
||||
// Endpoint gives access to the Endpoint data management layer
|
||||
func (store *Store) Endpoint() portainer.EndpointService {
|
||||
return store.EndpointService
|
||||
}
|
||||
|
||||
// EndpointGroup gives access to the EndpointGroup data management layer
|
||||
func (store *Store) EndpointGroup() portainer.EndpointGroupService {
|
||||
return store.EndpointGroupService
|
||||
}
|
||||
|
||||
// EndpointRelation gives access to the EndpointRelation data management layer
|
||||
func (store *Store) EndpointRelation() portainer.EndpointRelationService {
|
||||
return store.EndpointRelationService
|
||||
}
|
||||
|
||||
// Registry gives access to the Registry data management layer
|
||||
func (store *Store) Registry() portainer.RegistryService {
|
||||
return store.RegistryService
|
||||
}
|
||||
|
||||
// ResourceControl gives access to the ResourceControl data management layer
|
||||
func (store *Store) ResourceControl() portainer.ResourceControlService {
|
||||
return store.ResourceControlService
|
||||
}
|
||||
|
||||
// Role gives access to the Role data management layer
|
||||
func (store *Store) Role() portainer.RoleService {
|
||||
return store.RoleService
|
||||
}
|
||||
|
||||
// Settings gives access to the Settings data management layer
|
||||
func (store *Store) Settings() portainer.SettingsService {
|
||||
return store.SettingsService
|
||||
}
|
||||
|
||||
// Stack gives access to the Stack data management layer
|
||||
func (store *Store) Stack() portainer.StackService {
|
||||
return store.StackService
|
||||
}
|
||||
|
||||
// Tag gives access to the Tag data management layer
|
||||
func (store *Store) Tag() portainer.TagService {
|
||||
return store.TagService
|
||||
}
|
||||
|
||||
// TeamMembership gives access to the TeamMembership data management layer
|
||||
func (store *Store) TeamMembership() portainer.TeamMembershipService {
|
||||
return store.TeamMembershipService
|
||||
}
|
||||
|
||||
// Team gives access to the Team data management layer
|
||||
func (store *Store) Team() portainer.TeamService {
|
||||
return store.TeamService
|
||||
}
|
||||
|
||||
// TunnelServer gives access to the TunnelServer data management layer
|
||||
func (store *Store) TunnelServer() portainer.TunnelServerService {
|
||||
return store.TunnelServerService
|
||||
}
|
||||
|
||||
// User gives access to the User data management layer
|
||||
func (store *Store) User() portainer.UserService {
|
||||
return store.UserService
|
||||
}
|
||||
|
||||
// Version gives access to the Version data management layer
|
||||
func (store *Store) Version() portainer.VersionService {
|
||||
return store.VersionService
|
||||
}
|
||||
|
||||
// Webhook gives access to the Webhook data management layer
|
||||
func (store *Store) Webhook() portainer.WebhookService {
|
||||
return store.WebhookService
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
package settings
|
||||
|
||||
import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/bolt/internal"
|
||||
)
|
||||
|
||||
const (
|
||||
// BucketName represents the name of the bucket where this service stores data.
|
||||
BucketName = "settings"
|
||||
settingsKey = "SETTINGS"
|
||||
)
|
||||
|
||||
// Service represents a service for managing endpoint data.
|
||||
type Service struct {
|
||||
connection *internal.DbConnection
|
||||
}
|
||||
|
||||
// NewService creates a new instance of a service.
|
||||
func NewService(connection *internal.DbConnection) (*Service, error) {
|
||||
err := internal.CreateBucket(connection, BucketName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Service{
|
||||
connection: connection,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Settings retrieve the settings object.
|
||||
func (service *Service) Settings() (*portainer.Settings, error) {
|
||||
var settings portainer.Settings
|
||||
|
||||
err := internal.GetObject(service.connection, BucketName, []byte(settingsKey), &settings)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &settings, nil
|
||||
}
|
||||
|
||||
// UpdateSettings persists a Settings object.
|
||||
func (service *Service) UpdateSettings(settings *portainer.Settings) error {
|
||||
return internal.UpdateObject(service.connection, BucketName, []byte(settingsKey), settings)
|
||||
}
|
||||
61
api/bolt/settings_service.go
Normal file
61
api/bolt/settings_service.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package bolt
|
||||
|
||||
import (
|
||||
"github.com/portainer/portainer"
|
||||
"github.com/portainer/portainer/bolt/internal"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
)
|
||||
|
||||
// SettingsService represents a service to manage application settings.
|
||||
type SettingsService struct {
|
||||
store *Store
|
||||
}
|
||||
|
||||
const (
|
||||
dbSettingsKey = "SETTINGS"
|
||||
)
|
||||
|
||||
// Settings retrieve the settings object.
|
||||
func (service *SettingsService) Settings() (*portainer.Settings, error) {
|
||||
var data []byte
|
||||
err := service.store.db.View(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(settingsBucketName))
|
||||
value := bucket.Get([]byte(dbSettingsKey))
|
||||
if value == nil {
|
||||
return portainer.ErrSettingsNotFound
|
||||
}
|
||||
|
||||
data = make([]byte, len(value))
|
||||
copy(data, value)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var settings portainer.Settings
|
||||
err = internal.UnmarshalSettings(data, &settings)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &settings, nil
|
||||
}
|
||||
|
||||
// StoreSettings persists a Settings object.
|
||||
func (service *SettingsService) StoreSettings(settings *portainer.Settings) error {
|
||||
return service.store.db.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(settingsBucketName))
|
||||
|
||||
data, err := internal.MarshalSettings(settings)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = bucket.Put([]byte(dbSettingsKey), data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
package stack
|
||||
|
||||
import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/bolt/errors"
|
||||
"github.com/portainer/portainer/api/bolt/internal"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
)
|
||||
|
||||
const (
|
||||
// BucketName represents the name of the bucket where this service stores data.
|
||||
BucketName = "stacks"
|
||||
)
|
||||
|
||||
// Service represents a service for managing endpoint data.
|
||||
type Service struct {
|
||||
connection *internal.DbConnection
|
||||
}
|
||||
|
||||
// NewService creates a new instance of a service.
|
||||
func NewService(connection *internal.DbConnection) (*Service, error) {
|
||||
err := internal.CreateBucket(connection, BucketName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Service{
|
||||
connection: connection,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Stack returns a stack object by ID.
|
||||
func (service *Service) Stack(ID portainer.StackID) (*portainer.Stack, error) {
|
||||
var stack portainer.Stack
|
||||
identifier := internal.Itob(int(ID))
|
||||
|
||||
err := internal.GetObject(service.connection, BucketName, identifier, &stack)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &stack, nil
|
||||
}
|
||||
|
||||
// StackByName returns a stack object by name.
|
||||
func (service *Service) StackByName(name string) (*portainer.Stack, error) {
|
||||
var stack *portainer.Stack
|
||||
|
||||
err := service.connection.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 t portainer.Stack
|
||||
err := internal.UnmarshalObject(v, &t)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if t.Name == name {
|
||||
stack = &t
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if stack == nil {
|
||||
return errors.ErrObjectNotFound
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return stack, err
|
||||
}
|
||||
|
||||
// Stacks returns an array containing all the stacks.
|
||||
func (service *Service) Stacks() ([]portainer.Stack, error) {
|
||||
var stacks = make([]portainer.Stack, 0)
|
||||
|
||||
err := service.connection.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 stack portainer.Stack
|
||||
err := internal.UnmarshalObject(v, &stack)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stacks = append(stacks, stack)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return stacks, err
|
||||
}
|
||||
|
||||
// GetNextIdentifier returns the next identifier for a stack.
|
||||
func (service *Service) GetNextIdentifier() int {
|
||||
return internal.GetNextIdentifier(service.connection, BucketName)
|
||||
}
|
||||
|
||||
// CreateStack creates a new stack.
|
||||
func (service *Service) CreateStack(stack *portainer.Stack) error {
|
||||
return service.connection.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(BucketName))
|
||||
|
||||
// We manually manage sequences for stacks
|
||||
err := bucket.SetSequence(uint64(stack.ID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data, err := internal.MarshalObject(stack)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return bucket.Put(internal.Itob(int(stack.ID)), data)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateStack updates a stack.
|
||||
func (service *Service) UpdateStack(ID portainer.StackID, stack *portainer.Stack) error {
|
||||
identifier := internal.Itob(int(ID))
|
||||
return internal.UpdateObject(service.connection, BucketName, identifier, stack)
|
||||
}
|
||||
|
||||
// DeleteStack deletes a stack.
|
||||
func (service *Service) DeleteStack(ID portainer.StackID) error {
|
||||
identifier := internal.Itob(int(ID))
|
||||
return internal.DeleteObject(service.connection, BucketName, identifier)
|
||||
}
|
||||
138
api/bolt/stack_service.go
Normal file
138
api/bolt/stack_service.go
Normal file
@@ -0,0 +1,138 @@
|
||||
package bolt
|
||||
|
||||
import (
|
||||
"github.com/portainer/portainer"
|
||||
"github.com/portainer/portainer/bolt/internal"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
)
|
||||
|
||||
// StackService represents a service for managing stacks.
|
||||
type StackService struct {
|
||||
store *Store
|
||||
}
|
||||
|
||||
// Stack returns a stack object by ID.
|
||||
func (service *StackService) Stack(ID portainer.StackID) (*portainer.Stack, error) {
|
||||
var data []byte
|
||||
err := service.store.db.View(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(stackBucketName))
|
||||
value := bucket.Get([]byte(ID))
|
||||
if value == nil {
|
||||
return portainer.ErrStackNotFound
|
||||
}
|
||||
|
||||
data = make([]byte, len(value))
|
||||
copy(data, value)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var stack portainer.Stack
|
||||
err = internal.UnmarshalStack(data, &stack)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &stack, nil
|
||||
}
|
||||
|
||||
// Stacks returns an array containing all the stacks.
|
||||
func (service *StackService) Stacks() ([]portainer.Stack, error) {
|
||||
var stacks = make([]portainer.Stack, 0)
|
||||
err := service.store.db.View(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(stackBucketName))
|
||||
|
||||
cursor := bucket.Cursor()
|
||||
for k, v := cursor.First(); k != nil; k, v = cursor.Next() {
|
||||
var stack portainer.Stack
|
||||
err := internal.UnmarshalStack(v, &stack)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stacks = append(stacks, stack)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return stacks, nil
|
||||
}
|
||||
|
||||
// StacksBySwarmID return an array containing all the stacks related to the specified Swarm ID.
|
||||
func (service *StackService) StacksBySwarmID(id string) ([]portainer.Stack, error) {
|
||||
var stacks = make([]portainer.Stack, 0)
|
||||
err := service.store.db.View(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(stackBucketName))
|
||||
|
||||
cursor := bucket.Cursor()
|
||||
for k, v := cursor.First(); k != nil; k, v = cursor.Next() {
|
||||
var stack portainer.Stack
|
||||
err := internal.UnmarshalStack(v, &stack)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if stack.SwarmID == id {
|
||||
stacks = append(stacks, stack)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return stacks, nil
|
||||
}
|
||||
|
||||
// CreateStack creates a new stack.
|
||||
func (service *StackService) CreateStack(stack *portainer.Stack) error {
|
||||
return service.store.db.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(stackBucketName))
|
||||
|
||||
data, err := internal.MarshalStack(stack)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = bucket.Put([]byte(stack.ID), data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateStack updates an stack.
|
||||
func (service *StackService) UpdateStack(ID portainer.StackID, stack *portainer.Stack) error {
|
||||
data, err := internal.MarshalStack(stack)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return service.store.db.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(stackBucketName))
|
||||
err = bucket.Put([]byte(ID), data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteStack deletes an stack.
|
||||
func (service *StackService) DeleteStack(ID portainer.StackID) error {
|
||||
return service.store.db.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(stackBucketName))
|
||||
err := bucket.Delete([]byte(ID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
package tag
|
||||
|
||||
import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/bolt/internal"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
)
|
||||
|
||||
const (
|
||||
// BucketName represents the name of the bucket where this service stores data.
|
||||
BucketName = "tags"
|
||||
)
|
||||
|
||||
// Service represents a service for managing endpoint data.
|
||||
type Service struct {
|
||||
connection *internal.DbConnection
|
||||
}
|
||||
|
||||
// NewService creates a new instance of a service.
|
||||
func NewService(connection *internal.DbConnection) (*Service, error) {
|
||||
err := internal.CreateBucket(connection, BucketName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Service{
|
||||
connection: connection,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Tags return an array containing all the tags.
|
||||
func (service *Service) Tags() ([]portainer.Tag, error) {
|
||||
var tags = make([]portainer.Tag, 0)
|
||||
|
||||
err := service.connection.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 tag portainer.Tag
|
||||
err := internal.UnmarshalObject(v, &tag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tags = append(tags, tag)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return tags, err
|
||||
}
|
||||
|
||||
// Tag returns a tag by ID.
|
||||
func (service *Service) Tag(ID portainer.TagID) (*portainer.Tag, error) {
|
||||
var tag portainer.Tag
|
||||
identifier := internal.Itob(int(ID))
|
||||
|
||||
err := internal.GetObject(service.connection, BucketName, identifier, &tag)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &tag, nil
|
||||
}
|
||||
|
||||
// CreateTag creates a new tag.
|
||||
func (service *Service) CreateTag(tag *portainer.Tag) error {
|
||||
return service.connection.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(BucketName))
|
||||
|
||||
id, _ := bucket.NextSequence()
|
||||
tag.ID = portainer.TagID(id)
|
||||
|
||||
data, err := internal.MarshalObject(tag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return bucket.Put(internal.Itob(int(tag.ID)), data)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateTag updates a tag.
|
||||
func (service *Service) UpdateTag(ID portainer.TagID, tag *portainer.Tag) error {
|
||||
identifier := internal.Itob(int(ID))
|
||||
return internal.UpdateObject(service.connection, BucketName, identifier, tag)
|
||||
}
|
||||
|
||||
// DeleteTag deletes a tag.
|
||||
func (service *Service) DeleteTag(ID portainer.TagID) error {
|
||||
identifier := internal.Itob(int(ID))
|
||||
return internal.DeleteObject(service.connection, BucketName, identifier)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user