Compare commits
119 Commits
release/2.
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c0ee5b67d8 | ||
|
|
029e41ee46 | ||
|
|
c8deeade84 | ||
|
|
3c356275b3 | ||
|
|
d0cdbc2d1e | ||
|
|
a79b89bcf3 | ||
|
|
41d2e7f502 | ||
|
|
774c268e8e | ||
|
|
26bbdfc536 | ||
|
|
08358ade26 | ||
|
|
d23b374d69 | ||
|
|
8a438e5ae6 | ||
|
|
a90cfb32f1 | ||
|
|
4c90b9f336 | ||
|
|
bb63b424a1 | ||
|
|
3ec6c6a49d | ||
|
|
6f9683b77a | ||
|
|
5aeaafd223 | ||
|
|
31d973dab4 | ||
|
|
51dbdaa848 | ||
|
|
0dbb6af646 | ||
|
|
53865e1d0f | ||
|
|
a74238bf33 | ||
|
|
234b6fb369 | ||
|
|
7650143f69 | ||
|
|
1f2817b174 | ||
|
|
9d7cf0a940 | ||
|
|
888a5e358f | ||
|
|
a6d3f63b7b | ||
|
|
578106df3e | ||
|
|
f54974ae6c | ||
|
|
36001bbe0a | ||
|
|
70d669922b | ||
|
|
893383e615 | ||
|
|
6db8001349 | ||
|
|
8a8b7db14c | ||
|
|
787807a51a | ||
|
|
9991c78d6a | ||
|
|
859ff2375e | ||
|
|
90049af6a7 | ||
|
|
e5d092fab3 | ||
|
|
d0f562a827 | ||
|
|
97cafc1f5e | ||
|
|
9a73d7802e | ||
|
|
d337fdf710 | ||
|
|
ce2d5726c1 | ||
|
|
64fe6fe10d | ||
|
|
817ecfc496 | ||
|
|
7f8545532e | ||
|
|
2a8b055285 | ||
|
|
4a1c40528e | ||
|
|
e6d9143a09 | ||
|
|
1f4ca2e255 | ||
|
|
3feac3af50 | ||
|
|
df14784a81 | ||
|
|
4cb6bb863e | ||
|
|
4d906e0d42 | ||
|
|
05041fe7fd | ||
|
|
1ea9b421e0 | ||
|
|
a5a7e2c868 | ||
|
|
bb832d285b | ||
|
|
caced72ec1 | ||
|
|
0d72896b6b | ||
|
|
48b69852eb | ||
|
|
273ef6c2ed | ||
|
|
bac7c89363 | ||
|
|
4bdf3ecf58 | ||
|
|
89dc83f24a | ||
|
|
4af6dcea0e | ||
|
|
d369a71ceb | ||
|
|
1fb5d31f7e | ||
|
|
9c616ffb07 | ||
|
|
dbae99ea87 | ||
|
|
3254051647 | ||
|
|
f0d128f212 | ||
|
|
a0b52fc3d7 | ||
|
|
31fdef1e60 | ||
|
|
be30e1c453 | ||
|
|
5b55b890e7 | ||
|
|
a5eac07b0c | ||
|
|
fa80a7b7e5 | ||
|
|
b14500a2d5 | ||
|
|
278667825a | ||
|
|
65ded647b6 | ||
|
|
084cdcd8dc | ||
|
|
5b68c4365e | ||
|
|
9cd64664cc | ||
|
|
e831fa4a03 | ||
|
|
2a3c807978 | ||
|
|
a8265a44d0 | ||
|
|
71ad21598b | ||
|
|
6e017ea64e | ||
|
|
d48980e85b | ||
|
|
80d3fcc40b | ||
|
|
2e92706ead | ||
|
|
d4fa9db432 | ||
|
|
a28559777f | ||
|
|
f6531627d4 | ||
|
|
535215833d | ||
|
|
666b09ad3b | ||
|
|
c4a1243af9 | ||
|
|
305d0d2da0 | ||
|
|
9af9b70f3e | ||
|
|
e4605d990d | ||
|
|
768697157c | ||
|
|
d3086da139 | ||
|
|
95894e8047 | ||
|
|
81de55fedd | ||
|
|
84827b8782 | ||
|
|
fa38af5d81 | ||
|
|
1b82b450d7 | ||
|
|
b78d804881 | ||
|
|
51b72c12f9 | ||
|
|
58c04bdbe3 | ||
|
|
a6320d5222 | ||
|
|
cb4b4a43e6 | ||
|
|
1e5a1d5bdd | ||
|
|
5ed0d21c39 | ||
|
|
2972dbeafb |
1
.env.defaults
Normal file
1
.env.defaults
Normal file
@@ -0,0 +1 @@
|
|||||||
|
PORTAINER_EDITION=CE
|
||||||
@@ -9,6 +9,7 @@ globals:
|
|||||||
|
|
||||||
extends:
|
extends:
|
||||||
- 'eslint:recommended'
|
- 'eslint:recommended'
|
||||||
|
- 'plugin:storybook/recommended'
|
||||||
- prettier
|
- prettier
|
||||||
|
|
||||||
plugins:
|
plugins:
|
||||||
@@ -60,6 +61,7 @@ overrides:
|
|||||||
- 'plugin:@typescript-eslint/recommended'
|
- 'plugin:@typescript-eslint/recommended'
|
||||||
- 'plugin:@typescript-eslint/eslint-recommended'
|
- 'plugin:@typescript-eslint/eslint-recommended'
|
||||||
- 'plugin:promise/recommended'
|
- 'plugin:promise/recommended'
|
||||||
|
- 'plugin:storybook/recommended'
|
||||||
- prettier # should be last
|
- prettier # should be last
|
||||||
settings:
|
settings:
|
||||||
react:
|
react:
|
||||||
@@ -77,12 +79,17 @@ overrides:
|
|||||||
react/forbid-prop-types: off
|
react/forbid-prop-types: off
|
||||||
react/require-default-props: off
|
react/require-default-props: off
|
||||||
react/no-array-index-key: off
|
react/no-array-index-key: off
|
||||||
|
no-underscore-dangle: off
|
||||||
react/jsx-filename-extension: [0]
|
react/jsx-filename-extension: [0]
|
||||||
import/no-extraneous-dependencies: ['error', { devDependencies: true }]
|
import/no-extraneous-dependencies: ['error', { devDependencies: true }]
|
||||||
'@typescript-eslint/explicit-module-boundary-types': off
|
'@typescript-eslint/explicit-module-boundary-types': off
|
||||||
'@typescript-eslint/no-unused-vars': 'error'
|
'@typescript-eslint/no-unused-vars': 'error'
|
||||||
'@typescript-eslint/no-explicit-any': 'error'
|
'@typescript-eslint/no-explicit-any': 'error'
|
||||||
'jsx-a11y/label-has-associated-control': ['error', { 'assert': 'either' }]
|
'jsx-a11y/label-has-associated-control': ['error', { 'assert': 'either' }]
|
||||||
|
'react/function-component-definition': ['error', { 'namedComponents': 'function-declaration' }]
|
||||||
|
'react/jsx-no-bind': off
|
||||||
|
'no-await-in-loop': 'off'
|
||||||
|
'react/jsx-no-useless-fragment': ['error', { allowExpressions: true }]
|
||||||
- files:
|
- files:
|
||||||
- app/**/*.test.*
|
- app/**/*.test.*
|
||||||
extends:
|
extends:
|
||||||
@@ -90,3 +97,9 @@ overrides:
|
|||||||
- 'plugin:jest/style'
|
- 'plugin:jest/style'
|
||||||
env:
|
env:
|
||||||
'jest/globals': true
|
'jest/globals': true
|
||||||
|
rules:
|
||||||
|
'react/jsx-no-constructed-context-values': off
|
||||||
|
- files:
|
||||||
|
- app/**/*.stories.*
|
||||||
|
rules:
|
||||||
|
'no-alert': off
|
||||||
|
|||||||
4
.git-blame-ignore-revs
Normal file
4
.git-blame-ignore-revs
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# prettier
|
||||||
|
cf5056d9c03b62d91a25c3b9127caac838695f98
|
||||||
|
|
||||||
|
# prettier v2 (put here after fix/EE-2344/fix-eslint-issues is merged)
|
||||||
41
.github/workflows/lint.yml
vendored
Normal file
41
.github/workflows/lint.yml
vendored
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
name: Lint
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- develop
|
||||||
|
- release/*
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- develop
|
||||||
|
- release/*
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
run-linters:
|
||||||
|
name: Run linters
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Check out Git repository
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Set up Node.js
|
||||||
|
uses: actions/setup-node@v1
|
||||||
|
with:
|
||||||
|
node-version: 12
|
||||||
|
|
||||||
|
# ESLint and Prettier must be in `package.json`
|
||||||
|
- name: Install Node.js dependencies
|
||||||
|
run: yarn
|
||||||
|
|
||||||
|
- name: Run linters
|
||||||
|
uses: wearerequired/lint-action@v1
|
||||||
|
with:
|
||||||
|
eslint: true
|
||||||
|
eslint_extensions: ts,tsx,js,jsx
|
||||||
|
prettier: true
|
||||||
|
prettier_dir: app/
|
||||||
|
gofmt: true
|
||||||
|
gofmt_dir: api/
|
||||||
11
.github/workflows/test-client.yaml
vendored
Normal file
11
.github/workflows/test-client.yaml
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
name: Test Frontend
|
||||||
|
on: push
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Install modules
|
||||||
|
run: yarn
|
||||||
|
- name: Run tests
|
||||||
|
run: yarn test:client
|
||||||
12
.prettierrc
12
.prettierrc
@@ -4,20 +4,16 @@
|
|||||||
"htmlWhitespaceSensitivity": "strict",
|
"htmlWhitespaceSensitivity": "strict",
|
||||||
"overrides": [
|
"overrides": [
|
||||||
{
|
{
|
||||||
"files": [
|
"files": ["*.html"],
|
||||||
"*.html"
|
|
||||||
],
|
|
||||||
"options": {
|
"options": {
|
||||||
"parser": "angular"
|
"parser": "angular"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"files": [
|
"files": ["*.{j,t}sx", "*.ts"],
|
||||||
"*.{j,t}sx"
|
|
||||||
],
|
|
||||||
"options": {
|
"options": {
|
||||||
"printWidth": 80,
|
"printWidth": 80
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,4 +28,8 @@ module.exports = {
|
|||||||
];
|
];
|
||||||
return config;
|
return config;
|
||||||
},
|
},
|
||||||
|
core: {
|
||||||
|
builder: 'webpack5',
|
||||||
|
},
|
||||||
|
staticDirs: ['./public'],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,23 @@
|
|||||||
import '../app/assets/css';
|
import '../app/assets/css';
|
||||||
|
|
||||||
|
import { pushStateLocationPlugin, UIRouter } from '@uirouter/react';
|
||||||
|
import { initialize as initMSW, mswDecorator } from 'msw-storybook-addon';
|
||||||
|
import { handlers } from '@/setup-tests/server-handlers';
|
||||||
|
|
||||||
|
// Initialize MSW
|
||||||
|
initMSW({
|
||||||
|
onUnhandledRequest: ({ method, url }) => {
|
||||||
|
if (url.pathname.startsWith('/api')) {
|
||||||
|
console.error(`Unhandled ${method} request to ${url}.
|
||||||
|
|
||||||
|
This exception has been only logged in the console, however, it's strongly recommended to resolve this error as you don't want unmocked data in Storybook stories.
|
||||||
|
|
||||||
|
If you wish to mock an error response, please refer to this guide: https://mswjs.io/docs/recipes/mocking-error-responses
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
export const parameters = {
|
export const parameters = {
|
||||||
actions: { argTypesRegex: '^on[A-Z].*' },
|
actions: { argTypesRegex: '^on[A-Z].*' },
|
||||||
controls: {
|
controls: {
|
||||||
@@ -8,4 +26,16 @@ export const parameters = {
|
|||||||
date: /Date$/,
|
date: /Date$/,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
msw: {
|
||||||
|
handlers,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const decorators = [
|
||||||
|
(Story) => (
|
||||||
|
<UIRouter plugins={[pushStateLocationPlugin]}>
|
||||||
|
<Story />
|
||||||
|
</UIRouter>
|
||||||
|
),
|
||||||
|
mswDecorator,
|
||||||
|
];
|
||||||
|
|||||||
328
.storybook/public/mockServiceWorker.js
Normal file
328
.storybook/public/mockServiceWorker.js
Normal file
@@ -0,0 +1,328 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
/* tslint:disable */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mock Service Worker (0.36.3).
|
||||||
|
* @see https://github.com/mswjs/msw
|
||||||
|
* - Please do NOT modify this file.
|
||||||
|
* - Please do NOT serve this file on production.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const INTEGRITY_CHECKSUM = '02f4ad4a2797f85668baf196e553d929';
|
||||||
|
const bypassHeaderName = 'x-msw-bypass';
|
||||||
|
const activeClientIds = new Set();
|
||||||
|
|
||||||
|
self.addEventListener('install', function () {
|
||||||
|
return self.skipWaiting();
|
||||||
|
});
|
||||||
|
|
||||||
|
self.addEventListener('activate', async function (event) {
|
||||||
|
return self.clients.claim();
|
||||||
|
});
|
||||||
|
|
||||||
|
self.addEventListener('message', async function (event) {
|
||||||
|
const clientId = event.source.id;
|
||||||
|
|
||||||
|
if (!clientId || !self.clients) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const client = await self.clients.get(clientId);
|
||||||
|
|
||||||
|
if (!client) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const allClients = await self.clients.matchAll();
|
||||||
|
|
||||||
|
switch (event.data) {
|
||||||
|
case 'KEEPALIVE_REQUEST': {
|
||||||
|
sendToClient(client, {
|
||||||
|
type: 'KEEPALIVE_RESPONSE',
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'INTEGRITY_CHECK_REQUEST': {
|
||||||
|
sendToClient(client, {
|
||||||
|
type: 'INTEGRITY_CHECK_RESPONSE',
|
||||||
|
payload: INTEGRITY_CHECKSUM,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'MOCK_ACTIVATE': {
|
||||||
|
activeClientIds.add(clientId);
|
||||||
|
|
||||||
|
sendToClient(client, {
|
||||||
|
type: 'MOCKING_ENABLED',
|
||||||
|
payload: true,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'MOCK_DEACTIVATE': {
|
||||||
|
activeClientIds.delete(clientId);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'CLIENT_CLOSED': {
|
||||||
|
activeClientIds.delete(clientId);
|
||||||
|
|
||||||
|
const remainingClients = allClients.filter((client) => {
|
||||||
|
return client.id !== clientId;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Unregister itself when there are no more clients
|
||||||
|
if (remainingClients.length === 0) {
|
||||||
|
self.registration.unregister();
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Resolve the "main" client for the given event.
|
||||||
|
// Client that issues a request doesn't necessarily equal the client
|
||||||
|
// that registered the worker. It's with the latter the worker should
|
||||||
|
// communicate with during the response resolving phase.
|
||||||
|
async function resolveMainClient(event) {
|
||||||
|
const client = await self.clients.get(event.clientId);
|
||||||
|
|
||||||
|
if (client.frameType === 'top-level') {
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
|
const allClients = await self.clients.matchAll();
|
||||||
|
|
||||||
|
return allClients
|
||||||
|
.filter((client) => {
|
||||||
|
// Get only those clients that are currently visible.
|
||||||
|
return client.visibilityState === 'visible';
|
||||||
|
})
|
||||||
|
.find((client) => {
|
||||||
|
// Find the client ID that's recorded in the
|
||||||
|
// set of clients that have registered the worker.
|
||||||
|
return activeClientIds.has(client.id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleRequest(event, requestId) {
|
||||||
|
const client = await resolveMainClient(event);
|
||||||
|
const response = await getResponse(event, client, requestId);
|
||||||
|
|
||||||
|
// Send back the response clone for the "response:*" life-cycle events.
|
||||||
|
// Ensure MSW is active and ready to handle the message, otherwise
|
||||||
|
// this message will pend indefinitely.
|
||||||
|
if (client && activeClientIds.has(client.id)) {
|
||||||
|
(async function () {
|
||||||
|
const clonedResponse = response.clone();
|
||||||
|
sendToClient(client, {
|
||||||
|
type: 'RESPONSE',
|
||||||
|
payload: {
|
||||||
|
requestId,
|
||||||
|
type: clonedResponse.type,
|
||||||
|
ok: clonedResponse.ok,
|
||||||
|
status: clonedResponse.status,
|
||||||
|
statusText: clonedResponse.statusText,
|
||||||
|
body: clonedResponse.body === null ? null : await clonedResponse.text(),
|
||||||
|
headers: serializeHeaders(clonedResponse.headers),
|
||||||
|
redirected: clonedResponse.redirected,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getResponse(event, client, requestId) {
|
||||||
|
const { request } = event;
|
||||||
|
const requestClone = request.clone();
|
||||||
|
const getOriginalResponse = () => fetch(requestClone);
|
||||||
|
|
||||||
|
// Bypass mocking when the request client is not active.
|
||||||
|
if (!client) {
|
||||||
|
return getOriginalResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bypass initial page load requests (i.e. static assets).
|
||||||
|
// The absence of the immediate/parent client in the map of the active clients
|
||||||
|
// means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet
|
||||||
|
// and is not ready to handle requests.
|
||||||
|
if (!activeClientIds.has(client.id)) {
|
||||||
|
return await getOriginalResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bypass requests with the explicit bypass header
|
||||||
|
if (requestClone.headers.get(bypassHeaderName) === 'true') {
|
||||||
|
const cleanRequestHeaders = serializeHeaders(requestClone.headers);
|
||||||
|
|
||||||
|
// Remove the bypass header to comply with the CORS preflight check.
|
||||||
|
delete cleanRequestHeaders[bypassHeaderName];
|
||||||
|
|
||||||
|
const originalRequest = new Request(requestClone, {
|
||||||
|
headers: new Headers(cleanRequestHeaders),
|
||||||
|
});
|
||||||
|
|
||||||
|
return fetch(originalRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the request to the client-side MSW.
|
||||||
|
const reqHeaders = serializeHeaders(request.headers);
|
||||||
|
const body = await request.text();
|
||||||
|
|
||||||
|
const clientMessage = await sendToClient(client, {
|
||||||
|
type: 'REQUEST',
|
||||||
|
payload: {
|
||||||
|
id: requestId,
|
||||||
|
url: request.url,
|
||||||
|
method: request.method,
|
||||||
|
headers: reqHeaders,
|
||||||
|
cache: request.cache,
|
||||||
|
mode: request.mode,
|
||||||
|
credentials: request.credentials,
|
||||||
|
destination: request.destination,
|
||||||
|
integrity: request.integrity,
|
||||||
|
redirect: request.redirect,
|
||||||
|
referrer: request.referrer,
|
||||||
|
referrerPolicy: request.referrerPolicy,
|
||||||
|
body,
|
||||||
|
bodyUsed: request.bodyUsed,
|
||||||
|
keepalive: request.keepalive,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
switch (clientMessage.type) {
|
||||||
|
case 'MOCK_SUCCESS': {
|
||||||
|
return delayPromise(() => respondWithMock(clientMessage), clientMessage.payload.delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'MOCK_NOT_FOUND': {
|
||||||
|
return getOriginalResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'NETWORK_ERROR': {
|
||||||
|
const { name, message } = clientMessage.payload;
|
||||||
|
const networkError = new Error(message);
|
||||||
|
networkError.name = name;
|
||||||
|
|
||||||
|
// Rejecting a request Promise emulates a network error.
|
||||||
|
throw networkError;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'INTERNAL_ERROR': {
|
||||||
|
const parsedBody = JSON.parse(clientMessage.payload.body);
|
||||||
|
|
||||||
|
console.error(
|
||||||
|
`\
|
||||||
|
[MSW] Uncaught exception in the request handler for "%s %s":
|
||||||
|
|
||||||
|
${parsedBody.location}
|
||||||
|
|
||||||
|
This exception has been gracefully handled as a 500 response, however, it's strongly recommended to resolve this error, as it indicates a mistake in your code. If you wish to mock an error response, please see this guide: https://mswjs.io/docs/recipes/mocking-error-responses\
|
||||||
|
`,
|
||||||
|
request.method,
|
||||||
|
request.url
|
||||||
|
);
|
||||||
|
|
||||||
|
return respondWithMock(clientMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return getOriginalResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.addEventListener('fetch', function (event) {
|
||||||
|
const { request } = event;
|
||||||
|
const accept = request.headers.get('accept') || '';
|
||||||
|
|
||||||
|
// Bypass server-sent events.
|
||||||
|
if (accept.includes('text/event-stream')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bypass navigation requests.
|
||||||
|
if (request.mode === 'navigate') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Opening the DevTools triggers the "only-if-cached" request
|
||||||
|
// that cannot be handled by the worker. Bypass such requests.
|
||||||
|
if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bypass all requests when there are no active clients.
|
||||||
|
// Prevents the self-unregistered worked from handling requests
|
||||||
|
// after it's been deleted (still remains active until the next reload).
|
||||||
|
if (activeClientIds.size === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const requestId = uuidv4();
|
||||||
|
|
||||||
|
return event.respondWith(
|
||||||
|
handleRequest(event, requestId).catch((error) => {
|
||||||
|
if (error.name === 'NetworkError') {
|
||||||
|
console.warn('[MSW] Successfully emulated a network error for the "%s %s" request.', request.method, request.url);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// At this point, any exception indicates an issue with the original request/response.
|
||||||
|
console.error(
|
||||||
|
`\
|
||||||
|
[MSW] Caught an exception from the "%s %s" request (%s). This is probably not a problem with Mock Service Worker. There is likely an additional logging output above.`,
|
||||||
|
request.method,
|
||||||
|
request.url,
|
||||||
|
`${error.name}: ${error.message}`
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
function serializeHeaders(headers) {
|
||||||
|
const reqHeaders = {};
|
||||||
|
headers.forEach((value, name) => {
|
||||||
|
reqHeaders[name] = reqHeaders[name] ? [].concat(reqHeaders[name]).concat(value) : value;
|
||||||
|
});
|
||||||
|
return reqHeaders;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendToClient(client, message) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const channel = new MessageChannel();
|
||||||
|
|
||||||
|
channel.port1.onmessage = (event) => {
|
||||||
|
if (event.data && event.data.error) {
|
||||||
|
return reject(event.data.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(event.data);
|
||||||
|
};
|
||||||
|
|
||||||
|
client.postMessage(JSON.stringify(message), [channel.port2]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function delayPromise(cb, duration) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setTimeout(() => resolve(cb()), duration);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function respondWithMock(clientMessage) {
|
||||||
|
return new Response(clientMessage.payload.body, {
|
||||||
|
...clientMessage.payload,
|
||||||
|
headers: clientMessage.payload.headers,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function uuidv4() {
|
||||||
|
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
||||||
|
const r = (Math.random() * 16) | 0;
|
||||||
|
const v = c == 'x' ? r : (r & 0x3) | 0x8;
|
||||||
|
return v.toString(16);
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -150,6 +150,7 @@
|
|||||||
"// @description ",
|
"// @description ",
|
||||||
"// @description **Access policy**: ",
|
"// @description **Access policy**: ",
|
||||||
"// @tags ",
|
"// @tags ",
|
||||||
|
"// @security ApiKeyAuth",
|
||||||
"// @security jwt",
|
"// @security jwt",
|
||||||
"// @accept json",
|
"// @accept json",
|
||||||
"// @produce json",
|
"// @produce json",
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
"go.lintTool": "golangci-lint",
|
"go.lintTool": "golangci-lint",
|
||||||
"go.lintFlags": ["--fast", "-E", "exportloopref"]
|
"go.lintFlags": ["--fast", "-E", "exportloopref"],
|
||||||
|
"gitlens.advanced.blame.customArguments": ["–ignore-revs-file", ".git-blame-ignore-revs"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -120,6 +120,7 @@ When adding a new route to an existing handler use the following as a template (
|
|||||||
// @description
|
// @description
|
||||||
// @description **Access policy**:
|
// @description **Access policy**:
|
||||||
// @tags
|
// @tags
|
||||||
|
// @security ApiKeyAuth
|
||||||
// @security jwt
|
// @security jwt
|
||||||
// @accept json
|
// @accept json
|
||||||
// @produce json
|
// @produce json
|
||||||
|
|||||||
@@ -6,19 +6,20 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
portainer "github.com/portainer/portainer/api"
|
portainer "github.com/portainer/portainer/api"
|
||||||
|
"github.com/portainer/portainer/api/dataservices"
|
||||||
)
|
)
|
||||||
|
|
||||||
var logFatalf = log.Fatalf
|
var logFatalf = log.Fatalf
|
||||||
|
|
||||||
type Monitor struct {
|
type Monitor struct {
|
||||||
timeout time.Duration
|
timeout time.Duration
|
||||||
datastore portainer.DataStore
|
datastore dataservices.DataStore
|
||||||
shutdownCtx context.Context
|
shutdownCtx context.Context
|
||||||
cancellationFunc context.CancelFunc
|
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.
|
// 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 {
|
func New(timeout time.Duration, datastore dataservices.DataStore, shutdownCtx context.Context) *Monitor {
|
||||||
return &Monitor{
|
return &Monitor{
|
||||||
timeout: timeout,
|
timeout: timeout,
|
||||||
datastore: datastore,
|
datastore: datastore,
|
||||||
|
|||||||
30
api/apikey/apikey.go
Normal file
30
api/apikey/apikey.go
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package apikey
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
portainer "github.com/portainer/portainer/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
// APIKeyService represents a service for managing API keys.
|
||||||
|
type APIKeyService interface {
|
||||||
|
HashRaw(rawKey string) []byte
|
||||||
|
GenerateApiKey(user portainer.User, description string) (string, *portainer.APIKey, error)
|
||||||
|
GetAPIKey(apiKeyID portainer.APIKeyID) (*portainer.APIKey, error)
|
||||||
|
GetAPIKeys(userID portainer.UserID) ([]portainer.APIKey, error)
|
||||||
|
GetDigestUserAndKey(digest []byte) (portainer.User, portainer.APIKey, error)
|
||||||
|
UpdateAPIKey(apiKey *portainer.APIKey) error
|
||||||
|
DeleteAPIKey(apiKeyID portainer.APIKeyID) error
|
||||||
|
InvalidateUserKeyCache(userId portainer.UserID) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateRandomKey generates a random key of specified length
|
||||||
|
// source: https://github.com/gorilla/securecookie/blob/master/securecookie.go#L515
|
||||||
|
func generateRandomKey(length int) []byte {
|
||||||
|
k := make([]byte, length)
|
||||||
|
if _, err := io.ReadFull(rand.Reader, k); err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return k
|
||||||
|
}
|
||||||
50
api/apikey/apikey_test.go
Normal file
50
api/apikey/apikey_test.go
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
package apikey
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_generateRandomKey(t *testing.T) {
|
||||||
|
is := assert.New(t)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
wantLenth int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Generate a random key of length 16",
|
||||||
|
wantLenth: 16,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Generate a random key of length 32",
|
||||||
|
wantLenth: 32,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Generate a random key of length 64",
|
||||||
|
wantLenth: 64,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Generate a random key of length 128",
|
||||||
|
wantLenth: 128,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := generateRandomKey(tt.wantLenth)
|
||||||
|
is.Equal(tt.wantLenth, len(got))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("Generated keys are unique", func(t *testing.T) {
|
||||||
|
keys := make(map[string]bool)
|
||||||
|
for i := 0; i < 100; i++ {
|
||||||
|
key := generateRandomKey(8)
|
||||||
|
_, ok := keys[string(key)]
|
||||||
|
is.False(ok)
|
||||||
|
keys[string(key)] = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
69
api/apikey/cache.go
Normal file
69
api/apikey/cache.go
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
package apikey
|
||||||
|
|
||||||
|
import (
|
||||||
|
lru "github.com/hashicorp/golang-lru"
|
||||||
|
portainer "github.com/portainer/portainer/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
const defaultAPIKeyCacheSize = 1024
|
||||||
|
|
||||||
|
// entry is a tuple containing the user and API key associated to an API key digest
|
||||||
|
type entry struct {
|
||||||
|
user portainer.User
|
||||||
|
apiKey portainer.APIKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// apiKeyCache is a concurrency-safe, in-memory cache which primarily exists for to reduce database roundtrips.
|
||||||
|
// We store the api-key digest (keys) and the associated user and key-data (values) in the cache.
|
||||||
|
// This is required because HTTP requests will contain only the api-key digest in the x-api-key request header;
|
||||||
|
// digest value must be mapped to a portainer user (and respective key data) for validation.
|
||||||
|
// This cache is used to avoid multiple database queries to retrieve these user/key associated to the digest.
|
||||||
|
type apiKeyCache struct {
|
||||||
|
// cache type [string]entry cache (key: string(digest), value: user/key entry)
|
||||||
|
// note: []byte keys are not supported by golang-lru Cache
|
||||||
|
cache *lru.Cache
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAPIKeyCache creates a new cache for API keys
|
||||||
|
func NewAPIKeyCache(cacheSize int) *apiKeyCache {
|
||||||
|
cache, _ := lru.New(cacheSize)
|
||||||
|
return &apiKeyCache{cache: cache}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns the user/key associated to an api-key's digest
|
||||||
|
// This is required because HTTP requests will contain the digest of the API key in header,
|
||||||
|
// the digest value must be mapped to a portainer user.
|
||||||
|
func (c *apiKeyCache) Get(digest []byte) (portainer.User, portainer.APIKey, bool) {
|
||||||
|
val, ok := c.cache.Get(string(digest))
|
||||||
|
if !ok {
|
||||||
|
return portainer.User{}, portainer.APIKey{}, false
|
||||||
|
}
|
||||||
|
tuple := val.(entry)
|
||||||
|
|
||||||
|
return tuple.user, tuple.apiKey, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set persists a user/key entry to the cache
|
||||||
|
func (c *apiKeyCache) Set(digest []byte, user portainer.User, apiKey portainer.APIKey) {
|
||||||
|
c.cache.Add(string(digest), entry{
|
||||||
|
user: user,
|
||||||
|
apiKey: apiKey,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete evicts a digest's user/key entry key from the cache
|
||||||
|
func (c *apiKeyCache) Delete(digest []byte) {
|
||||||
|
c.cache.Remove(string(digest))
|
||||||
|
}
|
||||||
|
|
||||||
|
// InvalidateUserKeyCache loops through all the api-keys associated to a user and removes them from the cache
|
||||||
|
func (c *apiKeyCache) InvalidateUserKeyCache(userId portainer.UserID) bool {
|
||||||
|
present := false
|
||||||
|
for _, k := range c.cache.Keys() {
|
||||||
|
user, _, _ := c.Get([]byte(k.(string)))
|
||||||
|
if user.ID == userId {
|
||||||
|
present = c.cache.Remove(k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return present
|
||||||
|
}
|
||||||
181
api/apikey/cache_test.go
Normal file
181
api/apikey/cache_test.go
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
package apikey
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
portainer "github.com/portainer/portainer/api"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_apiKeyCacheGet(t *testing.T) {
|
||||||
|
is := assert.New(t)
|
||||||
|
|
||||||
|
keyCache := NewAPIKeyCache(10)
|
||||||
|
|
||||||
|
// pre-populate cache
|
||||||
|
keyCache.cache.Add(string("foo"), entry{user: portainer.User{}, apiKey: portainer.APIKey{}})
|
||||||
|
keyCache.cache.Add(string(""), entry{user: portainer.User{}, apiKey: portainer.APIKey{}})
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
digest []byte
|
||||||
|
found bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
digest: []byte("foo"),
|
||||||
|
found: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
digest: []byte(""),
|
||||||
|
found: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
digest: []byte("bar"),
|
||||||
|
found: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(string(test.digest), func(t *testing.T) {
|
||||||
|
_, _, found := keyCache.Get(test.digest)
|
||||||
|
is.Equal(test.found, found)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_apiKeyCacheSet(t *testing.T) {
|
||||||
|
is := assert.New(t)
|
||||||
|
|
||||||
|
keyCache := NewAPIKeyCache(10)
|
||||||
|
|
||||||
|
// pre-populate cache
|
||||||
|
keyCache.Set([]byte("bar"), portainer.User{ID: 2}, portainer.APIKey{})
|
||||||
|
keyCache.Set([]byte("foo"), portainer.User{ID: 1}, portainer.APIKey{})
|
||||||
|
|
||||||
|
// overwrite existing entry
|
||||||
|
keyCache.Set([]byte("foo"), portainer.User{ID: 3}, portainer.APIKey{})
|
||||||
|
|
||||||
|
val, ok := keyCache.cache.Get(string("bar"))
|
||||||
|
is.True(ok)
|
||||||
|
|
||||||
|
tuple := val.(entry)
|
||||||
|
is.Equal(portainer.User{ID: 2}, tuple.user)
|
||||||
|
|
||||||
|
val, ok = keyCache.cache.Get(string("foo"))
|
||||||
|
is.True(ok)
|
||||||
|
|
||||||
|
tuple = val.(entry)
|
||||||
|
is.Equal(portainer.User{ID: 3}, tuple.user)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_apiKeyCacheDelete(t *testing.T) {
|
||||||
|
is := assert.New(t)
|
||||||
|
|
||||||
|
keyCache := NewAPIKeyCache(10)
|
||||||
|
|
||||||
|
t.Run("Delete an existing entry", func(t *testing.T) {
|
||||||
|
keyCache.cache.Add(string("foo"), entry{user: portainer.User{ID: 1}, apiKey: portainer.APIKey{}})
|
||||||
|
keyCache.Delete([]byte("foo"))
|
||||||
|
|
||||||
|
_, ok := keyCache.cache.Get(string("foo"))
|
||||||
|
is.False(ok)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Delete a non-existing entry", func(t *testing.T) {
|
||||||
|
nonPanicFunc := func() { keyCache.Delete([]byte("non-existent-key")) }
|
||||||
|
is.NotPanics(nonPanicFunc)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_apiKeyCacheLRU(t *testing.T) {
|
||||||
|
is := assert.New(t)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
cacheLen int
|
||||||
|
key []string
|
||||||
|
foundKeys []string
|
||||||
|
evictedKeys []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Cache length is 1, add 2 keys",
|
||||||
|
cacheLen: 1,
|
||||||
|
key: []string{"foo", "bar"},
|
||||||
|
foundKeys: []string{"bar"},
|
||||||
|
evictedKeys: []string{"foo"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Cache length is 1, add 3 keys",
|
||||||
|
cacheLen: 1,
|
||||||
|
key: []string{"foo", "bar", "baz"},
|
||||||
|
foundKeys: []string{"baz"},
|
||||||
|
evictedKeys: []string{"foo", "bar"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Cache length is 2, add 3 keys",
|
||||||
|
cacheLen: 2,
|
||||||
|
key: []string{"foo", "bar", "baz"},
|
||||||
|
foundKeys: []string{"bar", "baz"},
|
||||||
|
evictedKeys: []string{"foo"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Cache length is 2, add 4 keys",
|
||||||
|
cacheLen: 2,
|
||||||
|
key: []string{"foo", "bar", "baz", "qux"},
|
||||||
|
foundKeys: []string{"baz", "qux"},
|
||||||
|
evictedKeys: []string{"foo", "bar"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
keyCache := NewAPIKeyCache(test.cacheLen)
|
||||||
|
|
||||||
|
for _, key := range test.key {
|
||||||
|
keyCache.Set([]byte(key), portainer.User{ID: 1}, portainer.APIKey{})
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, key := range test.foundKeys {
|
||||||
|
_, _, found := keyCache.Get([]byte(key))
|
||||||
|
is.True(found, "Key %s not found", key)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, key := range test.evictedKeys {
|
||||||
|
_, _, found := keyCache.Get([]byte(key))
|
||||||
|
is.False(found, "key %s should have been evicted", key)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_apiKeyCacheInvalidateUserKeyCache(t *testing.T) {
|
||||||
|
is := assert.New(t)
|
||||||
|
|
||||||
|
keyCache := NewAPIKeyCache(10)
|
||||||
|
|
||||||
|
t.Run("Removes users keys from cache", func(t *testing.T) {
|
||||||
|
keyCache.cache.Add(string("foo"), entry{user: portainer.User{ID: 1}, apiKey: portainer.APIKey{}})
|
||||||
|
|
||||||
|
ok := keyCache.InvalidateUserKeyCache(1)
|
||||||
|
is.True(ok)
|
||||||
|
|
||||||
|
_, ok = keyCache.cache.Get(string("foo"))
|
||||||
|
is.False(ok)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Does not affect other keys", func(t *testing.T) {
|
||||||
|
keyCache.cache.Add(string("foo"), entry{user: portainer.User{ID: 1}, apiKey: portainer.APIKey{}})
|
||||||
|
keyCache.cache.Add(string("bar"), entry{user: portainer.User{ID: 2}, apiKey: portainer.APIKey{}})
|
||||||
|
|
||||||
|
ok := keyCache.InvalidateUserKeyCache(1)
|
||||||
|
is.True(ok)
|
||||||
|
|
||||||
|
ok = keyCache.InvalidateUserKeyCache(1)
|
||||||
|
is.False(ok)
|
||||||
|
|
||||||
|
_, ok = keyCache.cache.Get(string("foo"))
|
||||||
|
is.False(ok)
|
||||||
|
|
||||||
|
_, ok = keyCache.cache.Get(string("bar"))
|
||||||
|
is.True(ok)
|
||||||
|
})
|
||||||
|
}
|
||||||
127
api/apikey/service.go
Normal file
127
api/apikey/service.go
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
package apikey
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
|
portainer "github.com/portainer/portainer/api"
|
||||||
|
"github.com/portainer/portainer/api/dataservices"
|
||||||
|
)
|
||||||
|
|
||||||
|
const portainerAPIKeyPrefix = "ptr_"
|
||||||
|
|
||||||
|
var ErrInvalidAPIKey = errors.New("Invalid API key")
|
||||||
|
|
||||||
|
type apiKeyService struct {
|
||||||
|
apiKeyRepository dataservices.APIKeyRepository
|
||||||
|
userRepository dataservices.UserService
|
||||||
|
cache *apiKeyCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAPIKeyService(apiKeyRepository dataservices.APIKeyRepository, userRepository dataservices.UserService) *apiKeyService {
|
||||||
|
return &apiKeyService{
|
||||||
|
apiKeyRepository: apiKeyRepository,
|
||||||
|
userRepository: userRepository,
|
||||||
|
cache: NewAPIKeyCache(defaultAPIKeyCacheSize),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HashRaw computes a hash digest of provided raw API key.
|
||||||
|
func (a *apiKeyService) HashRaw(rawKey string) []byte {
|
||||||
|
hashDigest := sha256.Sum256([]byte(rawKey))
|
||||||
|
return hashDigest[:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateApiKey generates a raw API key for a user (for one-time display).
|
||||||
|
// The generated API key is stored in the cache and database.
|
||||||
|
func (a *apiKeyService) GenerateApiKey(user portainer.User, description string) (string, *portainer.APIKey, error) {
|
||||||
|
randKey := generateRandomKey(32)
|
||||||
|
encodedRawAPIKey := base64.StdEncoding.EncodeToString(randKey)
|
||||||
|
prefixedAPIKey := portainerAPIKeyPrefix + encodedRawAPIKey
|
||||||
|
|
||||||
|
hashDigest := a.HashRaw(prefixedAPIKey)
|
||||||
|
|
||||||
|
apiKey := &portainer.APIKey{
|
||||||
|
UserID: user.ID,
|
||||||
|
Description: description,
|
||||||
|
Prefix: prefixedAPIKey[:7],
|
||||||
|
DateCreated: time.Now().Unix(),
|
||||||
|
Digest: hashDigest,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := a.apiKeyRepository.CreateAPIKey(apiKey)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, errors.Wrap(err, "Unable to create API key")
|
||||||
|
}
|
||||||
|
|
||||||
|
// persist api-key to cache
|
||||||
|
a.cache.Set(apiKey.Digest, user, *apiKey)
|
||||||
|
|
||||||
|
return prefixedAPIKey, apiKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAPIKey returns an API key by its ID.
|
||||||
|
func (a *apiKeyService) GetAPIKey(apiKeyID portainer.APIKeyID) (*portainer.APIKey, error) {
|
||||||
|
return a.apiKeyRepository.GetAPIKey(apiKeyID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAPIKeys returns all the API keys associated to a user.
|
||||||
|
func (a *apiKeyService) GetAPIKeys(userID portainer.UserID) ([]portainer.APIKey, error) {
|
||||||
|
return a.apiKeyRepository.GetAPIKeysByUserID(userID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDigestUserAndKey returns the user and api-key associated to a specified hash digest.
|
||||||
|
// A cache lookup is performed first; if the user/api-key is not found in the cache, respective database lookups are performed.
|
||||||
|
func (a *apiKeyService) GetDigestUserAndKey(digest []byte) (portainer.User, portainer.APIKey, error) {
|
||||||
|
// get api key from cache if possible
|
||||||
|
cachedUser, cachedKey, ok := a.cache.Get(digest)
|
||||||
|
if ok {
|
||||||
|
return cachedUser, cachedKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
apiKey, err := a.apiKeyRepository.GetAPIKeyByDigest(digest)
|
||||||
|
if err != nil {
|
||||||
|
return portainer.User{}, portainer.APIKey{}, errors.Wrap(err, "Unable to retrieve API key")
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := a.userRepository.User(apiKey.UserID)
|
||||||
|
if err != nil {
|
||||||
|
return portainer.User{}, portainer.APIKey{}, errors.Wrap(err, "Unable to retrieve digest user")
|
||||||
|
}
|
||||||
|
|
||||||
|
// persist api-key to cache - for quicker future lookups
|
||||||
|
a.cache.Set(apiKey.Digest, *user, *apiKey)
|
||||||
|
|
||||||
|
return *user, *apiKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateAPIKey updates an API key and in cache and database.
|
||||||
|
func (a *apiKeyService) UpdateAPIKey(apiKey *portainer.APIKey) error {
|
||||||
|
user, _, err := a.GetDigestUserAndKey(apiKey.Digest)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "Unable to retrieve API key")
|
||||||
|
}
|
||||||
|
a.cache.Set(apiKey.Digest, user, *apiKey)
|
||||||
|
return a.apiKeyRepository.UpdateAPIKey(apiKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteAPIKey deletes an API key and removes the digest/api-key entry from the cache.
|
||||||
|
func (a *apiKeyService) DeleteAPIKey(apiKeyID portainer.APIKeyID) error {
|
||||||
|
// get api-key digest to remove from cache
|
||||||
|
apiKey, err := a.apiKeyRepository.GetAPIKey(apiKeyID)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, fmt.Sprintf("Unable to retrieve API key: %d", apiKeyID))
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete the user/api-key from cache
|
||||||
|
a.cache.Delete(apiKey.Digest)
|
||||||
|
return a.apiKeyRepository.DeleteAPIKey(apiKeyID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *apiKeyService) InvalidateUserKeyCache(userId portainer.UserID) bool {
|
||||||
|
return a.cache.InvalidateUserKeyCache(userId)
|
||||||
|
}
|
||||||
309
api/apikey/service_test.go
Normal file
309
api/apikey/service_test.go
Normal file
@@ -0,0 +1,309 @@
|
|||||||
|
package apikey
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
portainer "github.com/portainer/portainer/api"
|
||||||
|
"github.com/portainer/portainer/api/datastore"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_SatisfiesAPIKeyServiceInterface(t *testing.T) {
|
||||||
|
is := assert.New(t)
|
||||||
|
is.Implements((*APIKeyService)(nil), NewAPIKeyService(nil, nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_GenerateApiKey(t *testing.T) {
|
||||||
|
is := assert.New(t)
|
||||||
|
|
||||||
|
_, store, teardown := datastore.MustNewTestStore(true)
|
||||||
|
defer teardown()
|
||||||
|
|
||||||
|
service := NewAPIKeyService(store.APIKeyRepository(), store.User())
|
||||||
|
|
||||||
|
t.Run("Successfully generates API key", func(t *testing.T) {
|
||||||
|
desc := "test-1"
|
||||||
|
rawKey, apiKey, err := service.GenerateApiKey(portainer.User{ID: 1}, desc)
|
||||||
|
is.NoError(err)
|
||||||
|
is.NotEmpty(rawKey)
|
||||||
|
is.NotEmpty(apiKey)
|
||||||
|
is.Equal(desc, apiKey.Description)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Api key prefix is 7 chars", func(t *testing.T) {
|
||||||
|
rawKey, apiKey, err := service.GenerateApiKey(portainer.User{ID: 1}, "test-2")
|
||||||
|
is.NoError(err)
|
||||||
|
|
||||||
|
is.Equal(rawKey[:7], apiKey.Prefix)
|
||||||
|
is.Len(apiKey.Prefix, 7)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Api key has 'ptr_' as prefix", func(t *testing.T) {
|
||||||
|
rawKey, _, err := service.GenerateApiKey(portainer.User{ID: 1}, "test-x")
|
||||||
|
is.NoError(err)
|
||||||
|
|
||||||
|
is.Equal(portainerAPIKeyPrefix, "ptr_")
|
||||||
|
is.True(strings.HasPrefix(rawKey, "ptr_"))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Successfully caches API key", func(t *testing.T) {
|
||||||
|
user := portainer.User{ID: 1}
|
||||||
|
_, apiKey, err := service.GenerateApiKey(user, "test-3")
|
||||||
|
is.NoError(err)
|
||||||
|
|
||||||
|
userFromCache, apiKeyFromCache, ok := service.cache.Get(apiKey.Digest)
|
||||||
|
is.True(ok)
|
||||||
|
is.Equal(user, userFromCache)
|
||||||
|
is.Equal(apiKey, &apiKeyFromCache)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Decoded raw api-key digest matches generated digest", func(t *testing.T) {
|
||||||
|
rawKey, apiKey, err := service.GenerateApiKey(portainer.User{ID: 1}, "test-4")
|
||||||
|
is.NoError(err)
|
||||||
|
|
||||||
|
generatedDigest := sha256.Sum256([]byte(rawKey))
|
||||||
|
|
||||||
|
is.Equal(apiKey.Digest, generatedDigest[:])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_GetAPIKey(t *testing.T) {
|
||||||
|
is := assert.New(t)
|
||||||
|
|
||||||
|
_, store, teardown := datastore.MustNewTestStore(true)
|
||||||
|
defer teardown()
|
||||||
|
|
||||||
|
service := NewAPIKeyService(store.APIKeyRepository(), store.User())
|
||||||
|
|
||||||
|
t.Run("Successfully returns all API keys", func(t *testing.T) {
|
||||||
|
user := portainer.User{ID: 1}
|
||||||
|
_, apiKey, err := service.GenerateApiKey(user, "test-1")
|
||||||
|
is.NoError(err)
|
||||||
|
|
||||||
|
apiKeyGot, err := service.GetAPIKey(apiKey.ID)
|
||||||
|
is.NoError(err)
|
||||||
|
|
||||||
|
is.Equal(apiKey, apiKeyGot)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_GetAPIKeys(t *testing.T) {
|
||||||
|
is := assert.New(t)
|
||||||
|
|
||||||
|
_, store, teardown := datastore.MustNewTestStore(true)
|
||||||
|
defer teardown()
|
||||||
|
|
||||||
|
service := NewAPIKeyService(store.APIKeyRepository(), store.User())
|
||||||
|
|
||||||
|
t.Run("Successfully returns all API keys", func(t *testing.T) {
|
||||||
|
user := portainer.User{ID: 1}
|
||||||
|
_, _, err := service.GenerateApiKey(user, "test-1")
|
||||||
|
is.NoError(err)
|
||||||
|
_, _, err = service.GenerateApiKey(user, "test-2")
|
||||||
|
is.NoError(err)
|
||||||
|
|
||||||
|
keys, err := service.GetAPIKeys(user.ID)
|
||||||
|
is.NoError(err)
|
||||||
|
is.Len(keys, 2)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_GetDigestUserAndKey(t *testing.T) {
|
||||||
|
is := assert.New(t)
|
||||||
|
|
||||||
|
_, store, teardown := datastore.MustNewTestStore(true)
|
||||||
|
defer teardown()
|
||||||
|
|
||||||
|
service := NewAPIKeyService(store.APIKeyRepository(), store.User())
|
||||||
|
|
||||||
|
t.Run("Successfully returns user and api key associated to digest", func(t *testing.T) {
|
||||||
|
user := portainer.User{ID: 1}
|
||||||
|
_, apiKey, err := service.GenerateApiKey(user, "test-1")
|
||||||
|
is.NoError(err)
|
||||||
|
|
||||||
|
userGot, apiKeyGot, err := service.GetDigestUserAndKey(apiKey.Digest)
|
||||||
|
is.NoError(err)
|
||||||
|
is.Equal(user, userGot)
|
||||||
|
is.Equal(*apiKey, apiKeyGot)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Successfully caches user and api key associated to digest", func(t *testing.T) {
|
||||||
|
user := portainer.User{ID: 1}
|
||||||
|
_, apiKey, err := service.GenerateApiKey(user, "test-1")
|
||||||
|
is.NoError(err)
|
||||||
|
|
||||||
|
userGot, apiKeyGot, err := service.GetDigestUserAndKey(apiKey.Digest)
|
||||||
|
is.NoError(err)
|
||||||
|
is.Equal(user, userGot)
|
||||||
|
is.Equal(*apiKey, apiKeyGot)
|
||||||
|
|
||||||
|
userFromCache, apiKeyFromCache, ok := service.cache.Get(apiKey.Digest)
|
||||||
|
is.True(ok)
|
||||||
|
is.Equal(userGot, userFromCache)
|
||||||
|
is.Equal(apiKeyGot, apiKeyFromCache)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_UpdateAPIKey(t *testing.T) {
|
||||||
|
is := assert.New(t)
|
||||||
|
|
||||||
|
_, store, teardown := datastore.MustNewTestStore(true)
|
||||||
|
defer teardown()
|
||||||
|
|
||||||
|
service := NewAPIKeyService(store.APIKeyRepository(), store.User())
|
||||||
|
|
||||||
|
t.Run("Successfully updates the api-key LastUsed time", func(t *testing.T) {
|
||||||
|
user := portainer.User{ID: 1}
|
||||||
|
store.User().Create(&user)
|
||||||
|
_, apiKey, err := service.GenerateApiKey(user, "test-x")
|
||||||
|
is.NoError(err)
|
||||||
|
|
||||||
|
apiKey.LastUsed = time.Now().UTC().Unix()
|
||||||
|
err = service.UpdateAPIKey(apiKey)
|
||||||
|
is.NoError(err)
|
||||||
|
|
||||||
|
_, apiKeyGot, err := service.GetDigestUserAndKey(apiKey.Digest)
|
||||||
|
is.NoError(err)
|
||||||
|
|
||||||
|
log.Println(apiKey)
|
||||||
|
log.Println(apiKeyGot)
|
||||||
|
|
||||||
|
is.Equal(apiKey.LastUsed, apiKeyGot.LastUsed)
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Successfully updates api-key in cache upon api-key update", func(t *testing.T) {
|
||||||
|
_, apiKey, err := service.GenerateApiKey(portainer.User{ID: 1}, "test-x2")
|
||||||
|
is.NoError(err)
|
||||||
|
|
||||||
|
_, apiKeyFromCache, ok := service.cache.Get(apiKey.Digest)
|
||||||
|
is.True(ok)
|
||||||
|
is.Equal(*apiKey, apiKeyFromCache)
|
||||||
|
|
||||||
|
apiKey.LastUsed = time.Now().UTC().Unix()
|
||||||
|
is.NotEqual(*apiKey, apiKeyFromCache)
|
||||||
|
|
||||||
|
err = service.UpdateAPIKey(apiKey)
|
||||||
|
is.NoError(err)
|
||||||
|
|
||||||
|
_, updatedAPIKeyFromCache, ok := service.cache.Get(apiKey.Digest)
|
||||||
|
is.True(ok)
|
||||||
|
is.Equal(*apiKey, updatedAPIKeyFromCache)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_DeleteAPIKey(t *testing.T) {
|
||||||
|
is := assert.New(t)
|
||||||
|
|
||||||
|
_, store, teardown := datastore.MustNewTestStore(true)
|
||||||
|
defer teardown()
|
||||||
|
|
||||||
|
service := NewAPIKeyService(store.APIKeyRepository(), store.User())
|
||||||
|
|
||||||
|
t.Run("Successfully updates the api-key", func(t *testing.T) {
|
||||||
|
user := portainer.User{ID: 1}
|
||||||
|
_, apiKey, err := service.GenerateApiKey(user, "test-1")
|
||||||
|
is.NoError(err)
|
||||||
|
|
||||||
|
_, apiKeyGot, err := service.GetDigestUserAndKey(apiKey.Digest)
|
||||||
|
is.NoError(err)
|
||||||
|
is.Equal(*apiKey, apiKeyGot)
|
||||||
|
|
||||||
|
err = service.DeleteAPIKey(apiKey.ID)
|
||||||
|
is.NoError(err)
|
||||||
|
|
||||||
|
_, _, err = service.GetDigestUserAndKey(apiKey.Digest)
|
||||||
|
is.Error(err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Successfully removes api-key from cache upon deletion", func(t *testing.T) {
|
||||||
|
user := portainer.User{ID: 1}
|
||||||
|
_, apiKey, err := service.GenerateApiKey(user, "test-1")
|
||||||
|
is.NoError(err)
|
||||||
|
|
||||||
|
_, apiKeyFromCache, ok := service.cache.Get(apiKey.Digest)
|
||||||
|
is.True(ok)
|
||||||
|
is.Equal(*apiKey, apiKeyFromCache)
|
||||||
|
|
||||||
|
err = service.DeleteAPIKey(apiKey.ID)
|
||||||
|
is.NoError(err)
|
||||||
|
|
||||||
|
_, _, ok = service.cache.Get(apiKey.Digest)
|
||||||
|
is.False(ok)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_InvalidateUserKeyCache(t *testing.T) {
|
||||||
|
is := assert.New(t)
|
||||||
|
|
||||||
|
_, store, teardown := datastore.MustNewTestStore(true)
|
||||||
|
defer teardown()
|
||||||
|
|
||||||
|
service := NewAPIKeyService(store.APIKeyRepository(), store.User())
|
||||||
|
|
||||||
|
t.Run("Successfully updates evicts keys from cache", func(t *testing.T) {
|
||||||
|
// generate api keys
|
||||||
|
user := portainer.User{ID: 1}
|
||||||
|
_, apiKey1, err := service.GenerateApiKey(user, "test-1")
|
||||||
|
is.NoError(err)
|
||||||
|
|
||||||
|
_, apiKey2, err := service.GenerateApiKey(user, "test-2")
|
||||||
|
is.NoError(err)
|
||||||
|
|
||||||
|
// verify api keys are present in cache
|
||||||
|
_, apiKeyFromCache, ok := service.cache.Get(apiKey1.Digest)
|
||||||
|
is.True(ok)
|
||||||
|
is.Equal(*apiKey1, apiKeyFromCache)
|
||||||
|
|
||||||
|
_, apiKeyFromCache, ok = service.cache.Get(apiKey2.Digest)
|
||||||
|
is.True(ok)
|
||||||
|
is.Equal(*apiKey2, apiKeyFromCache)
|
||||||
|
|
||||||
|
// evict cache
|
||||||
|
ok = service.InvalidateUserKeyCache(user.ID)
|
||||||
|
is.True(ok)
|
||||||
|
|
||||||
|
// verify users keys have been flushed from cache
|
||||||
|
_, _, ok = service.cache.Get(apiKey1.Digest)
|
||||||
|
is.False(ok)
|
||||||
|
|
||||||
|
_, _, ok = service.cache.Get(apiKey2.Digest)
|
||||||
|
is.False(ok)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("User key eviction does not affect other users keys", func(t *testing.T) {
|
||||||
|
// generate keys for 2 users
|
||||||
|
user1 := portainer.User{ID: 1}
|
||||||
|
_, apiKey1, err := service.GenerateApiKey(user1, "test-1")
|
||||||
|
is.NoError(err)
|
||||||
|
|
||||||
|
user2 := portainer.User{ID: 2}
|
||||||
|
_, apiKey2, err := service.GenerateApiKey(user2, "test-2")
|
||||||
|
is.NoError(err)
|
||||||
|
|
||||||
|
// verify keys in cache
|
||||||
|
_, apiKeyFromCache, ok := service.cache.Get(apiKey1.Digest)
|
||||||
|
is.True(ok)
|
||||||
|
is.Equal(*apiKey1, apiKeyFromCache)
|
||||||
|
|
||||||
|
_, apiKeyFromCache, ok = service.cache.Get(apiKey2.Digest)
|
||||||
|
is.True(ok)
|
||||||
|
is.Equal(*apiKey2, apiKeyFromCache)
|
||||||
|
|
||||||
|
// evict key of single user from cache
|
||||||
|
ok = service.cache.InvalidateUserKeyCache(user1.ID)
|
||||||
|
is.True(ok)
|
||||||
|
|
||||||
|
// verify user1 key has been flushed from cache
|
||||||
|
_, _, ok = service.cache.Get(apiKey1.Digest)
|
||||||
|
is.False(ok)
|
||||||
|
|
||||||
|
// verify user2 key is still in cache
|
||||||
|
_, _, ok = service.cache.Get(apiKey2.Digest)
|
||||||
|
is.True(ok)
|
||||||
|
})
|
||||||
|
}
|
||||||
61
api/aws/ecr/authorization_token.go
Normal file
61
api/aws/ecr/authorization_token.go
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
package ecr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Service) GetEncodedAuthorizationToken() (token *string, expiry *time.Time, err error) {
|
||||||
|
getAuthorizationTokenOutput, err := s.client.GetAuthorizationToken(context.TODO(), nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(getAuthorizationTokenOutput.AuthorizationData) == 0 {
|
||||||
|
err = fmt.Errorf("AuthorizationData is empty")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
authData := getAuthorizationTokenOutput.AuthorizationData[0]
|
||||||
|
|
||||||
|
token = authData.AuthorizationToken
|
||||||
|
expiry = authData.ExpiresAt
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetAuthorizationToken() (token *string, expiry *time.Time, err error) {
|
||||||
|
tokenEncodedStr, expiry, err := s.GetEncodedAuthorizationToken()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenByte, err := base64.StdEncoding.DecodeString(*tokenEncodedStr)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tokenStr := string(tokenByte)
|
||||||
|
token = &tokenStr
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) ParseAuthorizationToken(token string) (username string, password string, err error) {
|
||||||
|
if len(token) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
splitToken := strings.Split(token, ":")
|
||||||
|
if len(splitToken) < 2 {
|
||||||
|
err = fmt.Errorf("invalid ECR authorization token")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
username = splitToken[0]
|
||||||
|
password = splitToken[1]
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
32
api/aws/ecr/ecr.go
Normal file
32
api/aws/ecr/ecr.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package ecr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/aws/aws-sdk-go-v2/aws"
|
||||||
|
"github.com/aws/aws-sdk-go-v2/credentials"
|
||||||
|
"github.com/aws/aws-sdk-go-v2/service/ecr"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
Service struct {
|
||||||
|
accessKey string
|
||||||
|
secretKey string
|
||||||
|
region string
|
||||||
|
client *ecr.Client
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewService(accessKey, secretKey, region string) *Service {
|
||||||
|
options := ecr.Options{
|
||||||
|
Region: region,
|
||||||
|
Credentials: aws.NewCredentialsCache(credentials.NewStaticCredentialsProvider(accessKey, secretKey, "")),
|
||||||
|
}
|
||||||
|
|
||||||
|
client := ecr.New(options)
|
||||||
|
|
||||||
|
return &Service{
|
||||||
|
accessKey: accessKey,
|
||||||
|
secretKey: secretKey,
|
||||||
|
region: region,
|
||||||
|
client: client,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,15 +3,17 @@ package backup
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
portainer "github.com/portainer/portainer/api"
|
|
||||||
"github.com/portainer/portainer/api/archive"
|
"github.com/portainer/portainer/api/archive"
|
||||||
"github.com/portainer/portainer/api/crypto"
|
"github.com/portainer/portainer/api/crypto"
|
||||||
|
"github.com/portainer/portainer/api/dataservices"
|
||||||
"github.com/portainer/portainer/api/filesystem"
|
"github.com/portainer/portainer/api/filesystem"
|
||||||
"github.com/portainer/portainer/api/http/offlinegate"
|
"github.com/portainer/portainer/api/http/offlinegate"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
const rwxr__r__ os.FileMode = 0744
|
const rwxr__r__ os.FileMode = 0744
|
||||||
@@ -30,7 +32,7 @@ var filesToBackup = []string{
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Creates a tar.gz system archive and encrypts it if password is not empty. Returns a path to the archive file.
|
// 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) {
|
func CreateBackupArchive(password string, gate *offlinegate.OfflineGate, datastore dataservices.DataStore, filestorePath string) (string, error) {
|
||||||
unlock := gate.Lock()
|
unlock := gate.Lock()
|
||||||
defer unlock()
|
defer unlock()
|
||||||
|
|
||||||
@@ -39,6 +41,18 @@ func CreateBackupArchive(password string, gate *offlinegate.OfflineGate, datasto
|
|||||||
return "", errors.Wrap(err, "Failed to create backup dir")
|
return "", errors.Wrap(err, "Failed to create backup dir")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// new export
|
||||||
|
exportFilename := path.Join(backupDirPath, fmt.Sprintf("export-%d.json", time.Now().Unix()))
|
||||||
|
|
||||||
|
err := datastore.Export(exportFilename)
|
||||||
|
if err != nil {
|
||||||
|
logrus.WithError(err).Debugf("failed to export to %s", exportFilename)
|
||||||
|
} else {
|
||||||
|
logrus.Debugf("exported to %s", exportFilename)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err := backupDb(backupDirPath, datastore); err != nil {
|
if err := backupDb(backupDirPath, datastore); err != nil {
|
||||||
return "", errors.Wrap(err, "Failed to backup database")
|
return "", errors.Wrap(err, "Failed to backup database")
|
||||||
}
|
}
|
||||||
@@ -65,8 +79,9 @@ func CreateBackupArchive(password string, gate *offlinegate.OfflineGate, datasto
|
|||||||
return archivePath, nil
|
return archivePath, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func backupDb(backupDirPath string, datastore portainer.DataStore) error {
|
func backupDb(backupDirPath string, datastore dataservices.DataStore) error {
|
||||||
backupWriter, err := os.Create(filepath.Join(backupDirPath, "portainer.db"))
|
dbFileName := datastore.Connection().GetDatabaseFileName()
|
||||||
|
backupWriter, err := os.Create(filepath.Join(backupDirPath, dbFileName))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,17 +8,18 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
portainer "github.com/portainer/portainer/api"
|
|
||||||
"github.com/portainer/portainer/api/archive"
|
"github.com/portainer/portainer/api/archive"
|
||||||
"github.com/portainer/portainer/api/crypto"
|
"github.com/portainer/portainer/api/crypto"
|
||||||
|
"github.com/portainer/portainer/api/database/boltdb"
|
||||||
|
"github.com/portainer/portainer/api/dataservices"
|
||||||
"github.com/portainer/portainer/api/filesystem"
|
"github.com/portainer/portainer/api/filesystem"
|
||||||
"github.com/portainer/portainer/api/http/offlinegate"
|
"github.com/portainer/portainer/api/http/offlinegate"
|
||||||
)
|
)
|
||||||
|
|
||||||
var filesToRestore = append(filesToBackup, "portainer.db")
|
var filesToRestore = filesToBackup
|
||||||
|
|
||||||
// Restores system state from backup archive, will trigger system shutdown, when finished.
|
// 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 {
|
func RestoreArchive(archive io.Reader, password string, filestorePath string, gate *offlinegate.OfflineGate, datastore dataservices.DataStore, shutdownTrigger context.CancelFunc) error {
|
||||||
var err error
|
var err error
|
||||||
if password != "" {
|
if password != "" {
|
||||||
archive, err = decrypt(archive, password)
|
archive, err = decrypt(archive, password)
|
||||||
@@ -65,5 +66,20 @@ func restoreFiles(srcDir string, destinationDir string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
|
// TODO: This is very boltdb module specific once again due to the filename. Move to bolt module? Refactor for another day
|
||||||
|
|
||||||
|
// Prevent the possibility of having both databases. Remove any default new instance
|
||||||
|
os.Remove(filepath.Join(destinationDir, boltdb.DatabaseFileName))
|
||||||
|
os.Remove(filepath.Join(destinationDir, boltdb.EncryptedDatabaseFileName))
|
||||||
|
|
||||||
|
// Now copy the database. It'll be either portainer.db or portainer.edb
|
||||||
|
|
||||||
|
// Note: CopyPath does not return an error if the source file doesn't exist
|
||||||
|
err := filesystem.CopyPath(filepath.Join(srcDir, boltdb.EncryptedDatabaseFileName), destinationDir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return filesystem.CopyPath(filepath.Join(srcDir, boltdb.DatabaseFileName), destinationDir)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,154 +0,0 @@
|
|||||||
package bolt
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"path"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/portainer/portainer/api/bolt/helmuserrepository"
|
|
||||||
|
|
||||||
"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/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/ssl"
|
|
||||||
"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"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
databaseFileName = "portainer.db"
|
|
||||||
)
|
|
||||||
|
|
||||||
// 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
|
|
||||||
HelmUserRepositoryService *helmuserrepository.Service
|
|
||||||
RegistryService *registry.Service
|
|
||||||
ResourceControlService *resourcecontrol.Service
|
|
||||||
RoleService *role.Service
|
|
||||||
ScheduleService *schedule.Service
|
|
||||||
SettingsService *settings.Service
|
|
||||||
SSLSettingsService *ssl.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
|
|
||||||
}
|
|
||||||
|
|
||||||
func (store *Store) version() (int, error) {
|
|
||||||
version, err := store.VersionService.DBVersion()
|
|
||||||
if err == errors.ErrObjectNotFound {
|
|
||||||
version = 0
|
|
||||||
}
|
|
||||||
return version, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (store *Store) edition() portainer.SoftwareEdition {
|
|
||||||
edition, err := store.VersionService.Edition()
|
|
||||||
if err == errors.ErrObjectNotFound {
|
|
||||||
edition = portainer.PortainerCE
|
|
||||||
}
|
|
||||||
return edition
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewStore initializes a new Store and the associated services
|
|
||||||
func NewStore(storePath string, fileService portainer.FileService) *Store {
|
|
||||||
return &Store{
|
|
||||||
path: storePath,
|
|
||||||
fileService: fileService,
|
|
||||||
isNew: true,
|
|
||||||
connection: &internal.DbConnection{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
store.connection.DB = db
|
|
||||||
|
|
||||||
err = store.initServices()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// if we have DBVersion in the database then ensure we flag this as NOT a new store
|
|
||||||
if _, err := store.VersionService.DBVersion(); err == nil {
|
|
||||||
store.isNew = false
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close closes the BoltDB database.
|
|
||||||
// Safe to being called multiple times.
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// CheckCurrentEdition checks if current edition is community edition
|
|
||||||
func (store *Store) CheckCurrentEdition() error {
|
|
||||||
if store.edition() != portainer.PortainerCE {
|
|
||||||
return errors.ErrWrongDBEdition
|
|
||||||
}
|
|
||||||
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 environment(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 environment(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 environment(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 environment(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 environment(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 environment(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 environments(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 environment(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 environments(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 environment(endpoint).
|
|
||||||
func (service *Service) GetNextIdentifier() int {
|
|
||||||
return internal.GetNextIdentifier(service.connection, BucketName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Synchronize creates, updates and deletes environments(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
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -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 environment(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 environment(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 environment(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 environment(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 environment(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 environment(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 environment(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 Environment(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 Environment(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 Environment(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,73 +0,0 @@
|
|||||||
package helmuserrepository
|
|
||||||
|
|
||||||
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 = "helm_user_repository"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Service represents a service for managing environment(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
|
|
||||||
}
|
|
||||||
|
|
||||||
// HelmUserRepositoryByUserID return an array containing all the HelmUserRepository objects where the specified userID is present.
|
|
||||||
func (service *Service) HelmUserRepositoryByUserID(userID portainer.UserID) ([]portainer.HelmUserRepository, error) {
|
|
||||||
var result = make([]portainer.HelmUserRepository, 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 record portainer.HelmUserRepository
|
|
||||||
err := internal.UnmarshalObject(v, &record)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if record.UserID == userID {
|
|
||||||
result = append(result, record)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
return result, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateHelmUserRepository creates a new HelmUserRepository object.
|
|
||||||
func (service *Service) CreateHelmUserRepository(record *portainer.HelmUserRepository) error {
|
|
||||||
return service.connection.Update(func(tx *bolt.Tx) error {
|
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
|
||||||
|
|
||||||
id, _ := bucket.NextSequence()
|
|
||||||
record.ID = portainer.HelmUserRepositoryID(id)
|
|
||||||
|
|
||||||
data, err := internal.MarshalObject(record)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return bucket.Put(internal.Itob(int(record.ID)), data)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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 environment(endpoint)
|
|
||||||
// decoding at the moment.
|
|
||||||
func UnmarshalObjectWithJsoniter(data []byte, object interface{}) error {
|
|
||||||
var jsoni = jsoniter.ConfigCompatibleWithStandardLibrary
|
|
||||||
return jsoni.Unmarshal(data, &object)
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
package log
|
|
||||||
@@ -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,103 +0,0 @@
|
|||||||
package migrator
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/boltdb/bolt"
|
|
||||||
"github.com/portainer/portainer/api"
|
|
||||||
"github.com/portainer/portainer/api/bolt/internal"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (m *Migrator) updateResourceControlsToDBVersion2() error {
|
|
||||||
legacyResourceControls, err := m.retrieveLegacyResourceControls()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, resourceControl := range legacyResourceControls {
|
|
||||||
resourceControl.SubResourceIDs = []string{}
|
|
||||||
resourceControl.TeamAccesses = []portainer.TeamResourceAccess{}
|
|
||||||
|
|
||||||
owner, err := m.userService.User(resourceControl.OwnerID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if owner.Role == portainer.AdministratorRole {
|
|
||||||
resourceControl.AdministratorsOnly = true
|
|
||||||
resourceControl.UserAccesses = []portainer.UserResourceAccess{}
|
|
||||||
} else {
|
|
||||||
resourceControl.AdministratorsOnly = false
|
|
||||||
userAccess := portainer.UserResourceAccess{
|
|
||||||
UserID: resourceControl.OwnerID,
|
|
||||||
AccessLevel: portainer.ReadWriteAccessLevel,
|
|
||||||
}
|
|
||||||
resourceControl.UserAccesses = []portainer.UserResourceAccess{userAccess}
|
|
||||||
}
|
|
||||||
|
|
||||||
err = m.resourceControlService.CreateResourceControl(&resourceControl)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Migrator) updateEndpointsToDBVersion2() error {
|
|
||||||
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)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Migrator) retrieveLegacyResourceControls() ([]portainer.ResourceControl, error) {
|
|
||||||
legacyResourceControls := make([]portainer.ResourceControl, 0)
|
|
||||||
err := m.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)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
resourceControl.Type = portainer.ContainerResourceControl
|
|
||||||
legacyResourceControls = append(legacyResourceControls, resourceControl)
|
|
||||||
}
|
|
||||||
|
|
||||||
bucket = tx.Bucket([]byte("serviceResourceControl"))
|
|
||||||
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
|
|
||||||
}
|
|
||||||
resourceControl.Type = portainer.ServiceResourceControl
|
|
||||||
legacyResourceControls = append(legacyResourceControls, resourceControl)
|
|
||||||
}
|
|
||||||
|
|
||||||
bucket = tx.Bucket([]byte("volumeResourceControl"))
|
|
||||||
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
|
|
||||||
}
|
|
||||||
resourceControl.Type = portainer.VolumeResourceControl
|
|
||||||
legacyResourceControls = append(legacyResourceControls, resourceControl)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
return legacyResourceControls, err
|
|
||||||
}
|
|
||||||
@@ -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,20 +0,0 @@
|
|||||||
package migrator
|
|
||||||
|
|
||||||
import "github.com/portainer/portainer/api"
|
|
||||||
|
|
||||||
func (m *Migrator) updateSettingsToDBVersion3() error {
|
|
||||||
legacySettings, err := m.settingsService.Settings()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
legacySettings.AuthenticationMethod = portainer.AuthenticationInternal
|
|
||||||
legacySettings.LDAPSettings = portainer.LDAPSettings{
|
|
||||||
TLSConfig: portainer.TLSConfiguration{},
|
|
||||||
SearchSettings: []portainer.LDAPSearchSettings{
|
|
||||||
portainer.LDAPSearchSettings{},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
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.migrateSettingsToDB30(); 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,28 +0,0 @@
|
|||||||
package migrator
|
|
||||||
|
|
||||||
import "github.com/portainer/portainer/api"
|
|
||||||
|
|
||||||
func (m *Migrator) updateEndpointsToDBVersion4() error {
|
|
||||||
legacyEndpoints, err := m.endpointService.Endpoints()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, endpoint := range legacyEndpoints {
|
|
||||||
endpoint.TLSConfig = portainer.TLSConfiguration{}
|
|
||||||
if endpoint.TLS {
|
|
||||||
endpoint.TLSConfig.TLS = true
|
|
||||||
endpoint.TLSConfig.TLSSkipVerify = false
|
|
||||||
endpoint.TLSConfig.TLSCACertPath = endpoint.TLSCACertPath
|
|
||||||
endpoint.TLSConfig.TLSCertPath = endpoint.TLSCertPath
|
|
||||||
endpoint.TLSConfig.TLSKeyPath = endpoint.TLSKeyPath
|
|
||||||
}
|
|
||||||
|
|
||||||
err = m.endpointService.UpdateEndpoint(endpoint.ID, &endpoint)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
package migrator
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/boltdb/bolt"
|
|
||||||
portainer "github.com/portainer/portainer/api"
|
|
||||||
"github.com/portainer/portainer/api/bolt/dockerhub"
|
|
||||||
"github.com/portainer/portainer/api/bolt/endpoint"
|
|
||||||
"github.com/portainer/portainer/api/bolt/internal"
|
|
||||||
"github.com/portainer/portainer/api/bolt/registry"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
db35TestFile = "portainer-mig-35.db"
|
|
||||||
username = "portainer"
|
|
||||||
password = "password"
|
|
||||||
)
|
|
||||||
|
|
||||||
func setupDB35Test(t *testing.T) *Migrator {
|
|
||||||
is := assert.New(t)
|
|
||||||
dbConn, err := bolt.Open(path.Join(t.TempDir(), db35TestFile), 0600, &bolt.Options{Timeout: 1 * time.Second})
|
|
||||||
is.NoError(err, "failed to init testing DB connection")
|
|
||||||
|
|
||||||
// Create an old style dockerhub authenticated account
|
|
||||||
dockerhubService, err := dockerhub.NewService(&internal.DbConnection{DB: dbConn})
|
|
||||||
is.NoError(err, "failed to init testing registry service")
|
|
||||||
err = dockerhubService.UpdateDockerHub(&portainer.DockerHub{true, username, password})
|
|
||||||
is.NoError(err, "failed to create dockerhub account")
|
|
||||||
|
|
||||||
registryService, err := registry.NewService(&internal.DbConnection{DB: dbConn})
|
|
||||||
is.NoError(err, "failed to init testing registry service")
|
|
||||||
|
|
||||||
endpointService, err := endpoint.NewService(&internal.DbConnection{DB: dbConn})
|
|
||||||
is.NoError(err, "failed to init endpoint service")
|
|
||||||
|
|
||||||
m := &Migrator{
|
|
||||||
db: dbConn,
|
|
||||||
dockerhubService: dockerhubService,
|
|
||||||
registryService: registryService,
|
|
||||||
endpointService: endpointService,
|
|
||||||
}
|
|
||||||
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestUpdateDockerhubToDB32 tests a normal upgrade
|
|
||||||
func TestUpdateDockerhubToDB32(t *testing.T) {
|
|
||||||
is := assert.New(t)
|
|
||||||
m := setupDB35Test(t)
|
|
||||||
defer m.db.Close()
|
|
||||||
defer os.Remove(db35TestFile)
|
|
||||||
|
|
||||||
if err := m.updateDockerhubToDB32(); err != nil {
|
|
||||||
t.Errorf("failed to update settings: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify we have a single registry were created
|
|
||||||
registries, err := m.registryService.Registries()
|
|
||||||
is.NoError(err, "failed to read registries from the RegistryService")
|
|
||||||
is.Equal(len(registries), 1, "only one migrated registry expected")
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestUpdateDockerhubToDB32_with_duplicate_migrations tests an upgrade where in earlier versions a broken migration
|
|
||||||
// created a large number of duplicate "dockerhub migrated" registry entries.
|
|
||||||
func TestUpdateDockerhubToDB32_with_duplicate_migrations(t *testing.T) {
|
|
||||||
is := assert.New(t)
|
|
||||||
m := setupDB35Test(t)
|
|
||||||
defer m.db.Close()
|
|
||||||
defer os.Remove(db35TestFile)
|
|
||||||
|
|
||||||
// Create lots of duplicate entries...
|
|
||||||
registry := &portainer.Registry{
|
|
||||||
Type: portainer.DockerHubRegistry,
|
|
||||||
Name: "Dockerhub (authenticated - migrated)",
|
|
||||||
URL: "docker.io",
|
|
||||||
Authentication: true,
|
|
||||||
Username: "portainer",
|
|
||||||
Password: "password",
|
|
||||||
RegistryAccesses: portainer.RegistryAccesses{},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 1; i < 150; i++ {
|
|
||||||
err := m.registryService.CreateRegistry(registry)
|
|
||||||
assert.NoError(t, err, "create registry failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify they were created
|
|
||||||
registries, err := m.registryService.Registries()
|
|
||||||
is.NoError(err, "failed to read registries from the RegistryService")
|
|
||||||
is.Condition(func() bool {
|
|
||||||
return len(registries) > 1
|
|
||||||
}, "expected multiple duplicate registry entries")
|
|
||||||
|
|
||||||
// Now run the migrator
|
|
||||||
if err := m.updateDockerhubToDB32(); err != nil {
|
|
||||||
t.Errorf("failed to update settings: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify we have a single registry were created
|
|
||||||
registries, err = m.registryService.Registries()
|
|
||||||
is.NoError(err, "failed to read registries from the RegistryService")
|
|
||||||
is.Equal(len(registries), 1, "only one migrated registry expected")
|
|
||||||
}
|
|
||||||
@@ -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,20 +0,0 @@
|
|||||||
package migrator
|
|
||||||
|
|
||||||
import "github.com/portainer/portainer/api"
|
|
||||||
|
|
||||||
func (m *Migrator) updateEndpointsToVersion8() error {
|
|
||||||
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)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
package migrator
|
|
||||||
|
|
||||||
import "github.com/portainer/portainer/api"
|
|
||||||
|
|
||||||
func (m *Migrator) updateEndpointsToVersion9() error {
|
|
||||||
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)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
package migrator
|
|
||||||
|
|
||||||
import "github.com/portainer/portainer/api"
|
|
||||||
|
|
||||||
func (m *Migrator) updateEndpointsToVersion10() error {
|
|
||||||
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)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -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 environment(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)
|
|
||||||
}
|
|
||||||
@@ -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 environment(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,282 +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/helmuserrepository"
|
|
||||||
"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/ssl"
|
|
||||||
"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
|
|
||||||
|
|
||||||
helmUserRepositoryService, err := helmuserrepository.NewService(store.connection)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
store.HelmUserRepositoryService = helmUserRepositoryService
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
sslSettingsService, err := ssl.NewService(store.connection)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
store.SSLSettingsService = sslSettingsService
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
// Environment(Endpoint) gives access to the Environment(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
|
|
||||||
}
|
|
||||||
|
|
||||||
// HelmUserRepository access the helm user repository settings
|
|
||||||
func (store *Store) HelmUserRepository() portainer.HelmUserRepositoryService {
|
|
||||||
return store.HelmUserRepositoryService
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
// SSLSettings gives access to the SSL Settings data management layer
|
|
||||||
func (store *Store) SSLSettings() portainer.SSLSettingsService {
|
|
||||||
return store.SSLSettingsService
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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,236 +0,0 @@
|
|||||||
package stack
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
portainer "github.com/portainer/portainer/api"
|
|
||||||
"github.com/portainer/portainer/api/bolt/errors"
|
|
||||||
"github.com/portainer/portainer/api/bolt/internal"
|
|
||||||
|
|
||||||
"github.com/boltdb/bolt"
|
|
||||||
pkgerrors "github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// BucketName represents the name of the bucket where this service stores data.
|
|
||||||
BucketName = "stacks"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Service represents a service for managing environment(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 with same name
|
|
||||||
func (service *Service) StacksByName(name string) ([]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 t portainer.Stack
|
|
||||||
err := internal.UnmarshalObject(v, &t)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if t.Name == name {
|
|
||||||
stacks = append(stacks, t)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
return stacks, 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// StackByWebhookID returns a pointer to a stack object by webhook ID.
|
|
||||||
// It returns nil, errors.ErrObjectNotFound if there's no stack associated with the webhook ID.
|
|
||||||
func (service *Service) StackByWebhookID(id string) (*portainer.Stack, error) {
|
|
||||||
if id == "" {
|
|
||||||
return nil, pkgerrors.New("webhook ID can't be empty string")
|
|
||||||
}
|
|
||||||
var stack portainer.Stack
|
|
||||||
found := false
|
|
||||||
|
|
||||||
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 struct {
|
|
||||||
AutoUpdate *struct {
|
|
||||||
WebhookID string `json:"Webhook"`
|
|
||||||
} `json:"AutoUpdate"`
|
|
||||||
}
|
|
||||||
|
|
||||||
err := internal.UnmarshalObject(v, &t)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if t.AutoUpdate != nil && strings.EqualFold(t.AutoUpdate.WebhookID, id) {
|
|
||||||
found = true
|
|
||||||
err := internal.UnmarshalObject(v, &stack)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
return nil, errors.ErrObjectNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
return &stack, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RefreshableStacks returns stacks that are configured for a periodic update
|
|
||||||
func (service *Service) RefreshableStacks() ([]portainer.Stack, error) {
|
|
||||||
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() {
|
|
||||||
stack := portainer.Stack{}
|
|
||||||
err := internal.UnmarshalObject(v, &stack)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if stack.AutoUpdate != nil && stack.AutoUpdate.Interval != "" {
|
|
||||||
stacks = append(stacks, stack)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
return stacks, err
|
|
||||||
}
|
|
||||||
@@ -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 environment(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)
|
|
||||||
}
|
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
package team
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
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 = "teams"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Service represents a service for managing environment(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
|
|
||||||
}
|
|
||||||
|
|
||||||
// Team returns a Team by ID
|
|
||||||
func (service *Service) Team(ID portainer.TeamID) (*portainer.Team, error) {
|
|
||||||
var team portainer.Team
|
|
||||||
identifier := internal.Itob(int(ID))
|
|
||||||
|
|
||||||
err := internal.GetObject(service.connection, BucketName, identifier, &team)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &team, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// TeamByName returns a team by name.
|
|
||||||
func (service *Service) TeamByName(name string) (*portainer.Team, error) {
|
|
||||||
var team *portainer.Team
|
|
||||||
|
|
||||||
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.Team
|
|
||||||
err := internal.UnmarshalObject(v, &t)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.EqualFold(t.Name, name) {
|
|
||||||
team = &t
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if team == nil {
|
|
||||||
return errors.ErrObjectNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
return team, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Teams return an array containing all the teams.
|
|
||||||
func (service *Service) Teams() ([]portainer.Team, error) {
|
|
||||||
var teams = make([]portainer.Team, 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 team portainer.Team
|
|
||||||
err := internal.UnmarshalObject(v, &team)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
teams = append(teams, team)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
return teams, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateTeam saves a Team.
|
|
||||||
func (service *Service) UpdateTeam(ID portainer.TeamID, team *portainer.Team) error {
|
|
||||||
identifier := internal.Itob(int(ID))
|
|
||||||
return internal.UpdateObject(service.connection, BucketName, identifier, team)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateTeam creates a new Team.
|
|
||||||
func (service *Service) CreateTeam(team *portainer.Team) error {
|
|
||||||
return service.connection.Update(func(tx *bolt.Tx) error {
|
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
|
||||||
|
|
||||||
id, _ := bucket.NextSequence()
|
|
||||||
team.ID = portainer.TeamID(id)
|
|
||||||
|
|
||||||
data, err := internal.MarshalObject(team)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return bucket.Put(internal.Itob(int(team.ID)), data)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteTeam deletes a Team.
|
|
||||||
func (service *Service) DeleteTeam(ID portainer.TeamID) error {
|
|
||||||
identifier := internal.Itob(int(ID))
|
|
||||||
return internal.DeleteObject(service.connection, BucketName, identifier)
|
|
||||||
}
|
|
||||||
@@ -1,197 +0,0 @@
|
|||||||
package teammembership
|
|
||||||
|
|
||||||
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 = "team_membership"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Service represents a service for managing environment(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
|
|
||||||
}
|
|
||||||
|
|
||||||
// TeamMembership returns a TeamMembership object by ID
|
|
||||||
func (service *Service) TeamMembership(ID portainer.TeamMembershipID) (*portainer.TeamMembership, error) {
|
|
||||||
var membership portainer.TeamMembership
|
|
||||||
identifier := internal.Itob(int(ID))
|
|
||||||
|
|
||||||
err := internal.GetObject(service.connection, BucketName, identifier, &membership)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &membership, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// TeamMemberships return an array containing all the TeamMembership objects.
|
|
||||||
func (service *Service) TeamMemberships() ([]portainer.TeamMembership, error) {
|
|
||||||
var memberships = make([]portainer.TeamMembership, 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 membership portainer.TeamMembership
|
|
||||||
err := internal.UnmarshalObject(v, &membership)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
memberships = append(memberships, membership)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
return memberships, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// TeamMembershipsByUserID return an array containing all the TeamMembership objects where the specified userID is present.
|
|
||||||
func (service *Service) TeamMembershipsByUserID(userID portainer.UserID) ([]portainer.TeamMembership, error) {
|
|
||||||
var memberships = make([]portainer.TeamMembership, 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 membership portainer.TeamMembership
|
|
||||||
err := internal.UnmarshalObject(v, &membership)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if membership.UserID == userID {
|
|
||||||
memberships = append(memberships, membership)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
return memberships, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// TeamMembershipsByTeamID return an array containing all the TeamMembership objects where the specified teamID is present.
|
|
||||||
func (service *Service) TeamMembershipsByTeamID(teamID portainer.TeamID) ([]portainer.TeamMembership, error) {
|
|
||||||
var memberships = make([]portainer.TeamMembership, 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 membership portainer.TeamMembership
|
|
||||||
err := internal.UnmarshalObject(v, &membership)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if membership.TeamID == teamID {
|
|
||||||
memberships = append(memberships, membership)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
return memberships, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateTeamMembership saves a TeamMembership object.
|
|
||||||
func (service *Service) UpdateTeamMembership(ID portainer.TeamMembershipID, membership *portainer.TeamMembership) error {
|
|
||||||
identifier := internal.Itob(int(ID))
|
|
||||||
return internal.UpdateObject(service.connection, BucketName, identifier, membership)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateTeamMembership creates a new TeamMembership object.
|
|
||||||
func (service *Service) CreateTeamMembership(membership *portainer.TeamMembership) error {
|
|
||||||
return service.connection.Update(func(tx *bolt.Tx) error {
|
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
|
||||||
|
|
||||||
id, _ := bucket.NextSequence()
|
|
||||||
membership.ID = portainer.TeamMembershipID(id)
|
|
||||||
|
|
||||||
data, err := internal.MarshalObject(membership)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return bucket.Put(internal.Itob(int(membership.ID)), data)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteTeamMembership deletes a TeamMembership object.
|
|
||||||
func (service *Service) DeleteTeamMembership(ID portainer.TeamMembershipID) error {
|
|
||||||
identifier := internal.Itob(int(ID))
|
|
||||||
return internal.DeleteObject(service.connection, BucketName, identifier)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteTeamMembershipByUserID deletes all the TeamMembership object associated to a UserID.
|
|
||||||
func (service *Service) DeleteTeamMembershipByUserID(userID portainer.UserID) error {
|
|
||||||
return service.connection.Update(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 membership portainer.TeamMembership
|
|
||||||
err := internal.UnmarshalObject(v, &membership)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if membership.UserID == userID {
|
|
||||||
err := bucket.Delete(internal.Itob(int(membership.ID)))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteTeamMembershipByTeamID deletes all the TeamMembership object associated to a TeamID.
|
|
||||||
func (service *Service) DeleteTeamMembershipByTeamID(teamID portainer.TeamID) error {
|
|
||||||
return service.connection.Update(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 membership portainer.TeamMembership
|
|
||||||
err := internal.UnmarshalObject(v, &membership)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if membership.TeamID == teamID {
|
|
||||||
err := bucket.Delete(internal.Itob(int(membership.ID)))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
package bolt
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/portainer/portainer/api/filesystem"
|
|
||||||
)
|
|
||||||
|
|
||||||
var errTempDir = errors.New("can't create a temp dir")
|
|
||||||
|
|
||||||
func MustNewTestStore(init bool) (*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) (*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 := NewStore(dataStorePath, fileService)
|
|
||||||
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 *Store, dataStorePath string) {
|
|
||||||
err := store.Close()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = os.RemoveAll(dataStorePath)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,156 +0,0 @@
|
|||||||
package user
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
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 = "users"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Service represents a service for managing environment(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
|
|
||||||
}
|
|
||||||
|
|
||||||
// User returns a user by ID
|
|
||||||
func (service *Service) User(ID portainer.UserID) (*portainer.User, error) {
|
|
||||||
var user portainer.User
|
|
||||||
identifier := internal.Itob(int(ID))
|
|
||||||
|
|
||||||
err := internal.GetObject(service.connection, BucketName, identifier, &user)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &user, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UserByUsername returns a user by username.
|
|
||||||
func (service *Service) UserByUsername(username string) (*portainer.User, error) {
|
|
||||||
var user *portainer.User
|
|
||||||
|
|
||||||
username = strings.ToLower(username)
|
|
||||||
|
|
||||||
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 u portainer.User
|
|
||||||
err := internal.UnmarshalObject(v, &u)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.EqualFold(u.Username, username) {
|
|
||||||
user = &u
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if user == nil {
|
|
||||||
return errors.ErrObjectNotFound
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
return user, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Users return an array containing all the users.
|
|
||||||
func (service *Service) Users() ([]portainer.User, error) {
|
|
||||||
var users = make([]portainer.User, 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 user portainer.User
|
|
||||||
err := internal.UnmarshalObject(v, &user)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
users = append(users, user)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
return users, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// UsersByRole return an array containing all the users with the specified role.
|
|
||||||
func (service *Service) UsersByRole(role portainer.UserRole) ([]portainer.User, error) {
|
|
||||||
var users = make([]portainer.User, 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 user portainer.User
|
|
||||||
err := internal.UnmarshalObject(v, &user)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if user.Role == role {
|
|
||||||
users = append(users, user)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
return users, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateUser saves a user.
|
|
||||||
func (service *Service) UpdateUser(ID portainer.UserID, user *portainer.User) error {
|
|
||||||
identifier := internal.Itob(int(ID))
|
|
||||||
user.Username = strings.ToLower(user.Username)
|
|
||||||
return internal.UpdateObject(service.connection, BucketName, identifier, user)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateUser creates a new user.
|
|
||||||
func (service *Service) CreateUser(user *portainer.User) error {
|
|
||||||
return service.connection.Update(func(tx *bolt.Tx) error {
|
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
|
||||||
|
|
||||||
id, _ := bucket.NextSequence()
|
|
||||||
user.ID = portainer.UserID(id)
|
|
||||||
user.Username = strings.ToLower(user.Username)
|
|
||||||
|
|
||||||
data, err := internal.MarshalObject(user)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return bucket.Put(internal.Itob(int(user.ID)), data)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteUser deletes a user.
|
|
||||||
func (service *Service) DeleteUser(ID portainer.UserID) error {
|
|
||||||
identifier := internal.Itob(int(ID))
|
|
||||||
return internal.DeleteObject(service.connection, BucketName, identifier)
|
|
||||||
}
|
|
||||||
@@ -1,167 +0,0 @@
|
|||||||
package version
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/boltdb/bolt"
|
|
||||||
portainer "github.com/portainer/portainer/api"
|
|
||||||
"github.com/portainer/portainer/api/bolt/errors"
|
|
||||||
"github.com/portainer/portainer/api/bolt/internal"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// BucketName represents the name of the bucket where this service stores data.
|
|
||||||
BucketName = "version"
|
|
||||||
versionKey = "DB_VERSION"
|
|
||||||
instanceKey = "INSTANCE_ID"
|
|
||||||
editionKey = "EDITION"
|
|
||||||
updatingKey = "DB_UPDATING"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Service represents a service to manage stored versions.
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// DBVersion retrieves the stored database version.
|
|
||||||
func (service *Service) DBVersion() (int, error) {
|
|
||||||
var data []byte
|
|
||||||
|
|
||||||
err := service.connection.View(func(tx *bolt.Tx) error {
|
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
|
||||||
|
|
||||||
value := bucket.Get([]byte(versionKey))
|
|
||||||
if value == nil {
|
|
||||||
return errors.ErrObjectNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
data = make([]byte, len(value))
|
|
||||||
copy(data, value)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return strconv.Atoi(string(data))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Edition retrieves the stored portainer edition.
|
|
||||||
func (service *Service) Edition() (portainer.SoftwareEdition, error) {
|
|
||||||
editionData, err := service.getKey(editionKey)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
edition, err := strconv.Atoi(string(editionData))
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return portainer.SoftwareEdition(edition), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// StoreDBVersion store the database version.
|
|
||||||
func (service *Service) StoreDBVersion(version int) error {
|
|
||||||
return service.connection.Update(func(tx *bolt.Tx) error {
|
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
|
||||||
|
|
||||||
data := []byte(strconv.Itoa(version))
|
|
||||||
return bucket.Put([]byte(versionKey), data)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsUpdating retrieves the database updating status.
|
|
||||||
func (service *Service) IsUpdating() (bool, error) {
|
|
||||||
isUpdating, err := service.getKey(updatingKey)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return strconv.ParseBool(string(isUpdating))
|
|
||||||
}
|
|
||||||
|
|
||||||
// StoreIsUpdating store the database updating status.
|
|
||||||
func (service *Service) StoreIsUpdating(isUpdating bool) error {
|
|
||||||
return service.setKey(updatingKey, strconv.FormatBool(isUpdating))
|
|
||||||
}
|
|
||||||
|
|
||||||
// InstanceID retrieves the stored instance ID.
|
|
||||||
func (service *Service) InstanceID() (string, error) {
|
|
||||||
var data []byte
|
|
||||||
|
|
||||||
err := service.connection.View(func(tx *bolt.Tx) error {
|
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
|
||||||
|
|
||||||
value := bucket.Get([]byte(instanceKey))
|
|
||||||
if value == nil {
|
|
||||||
return errors.ErrObjectNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
data = make([]byte, len(value))
|
|
||||||
copy(data, value)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return string(data), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// StoreInstanceID store the instance ID.
|
|
||||||
func (service *Service) StoreInstanceID(ID string) error {
|
|
||||||
return service.connection.Update(func(tx *bolt.Tx) error {
|
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
|
||||||
|
|
||||||
data := []byte(ID)
|
|
||||||
return bucket.Put([]byte(instanceKey), data)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (service *Service) getKey(key string) ([]byte, error) {
|
|
||||||
var data []byte
|
|
||||||
|
|
||||||
err := service.connection.View(func(tx *bolt.Tx) error {
|
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
|
||||||
|
|
||||||
value := bucket.Get([]byte(key))
|
|
||||||
if value == nil {
|
|
||||||
return errors.ErrObjectNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
data = make([]byte, len(value))
|
|
||||||
copy(data, value)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (service *Service) setKey(key string, value string) error {
|
|
||||||
return service.connection.Update(func(tx *bolt.Tx) error {
|
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
|
||||||
|
|
||||||
data := []byte(value)
|
|
||||||
return bucket.Put([]byte(key), data)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,152 +0,0 @@
|
|||||||
package webhook
|
|
||||||
|
|
||||||
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 = "webhooks"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Service represents a service for managing webhook 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
|
|
||||||
}
|
|
||||||
|
|
||||||
//Webhooks returns an array of all webhooks
|
|
||||||
func (service *Service) Webhooks() ([]portainer.Webhook, error) {
|
|
||||||
var webhooks = make([]portainer.Webhook, 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 webhook portainer.Webhook
|
|
||||||
err := internal.UnmarshalObject(v, &webhook)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
webhooks = append(webhooks, webhook)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
return webhooks, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Webhook returns a webhook by ID.
|
|
||||||
func (service *Service) Webhook(ID portainer.WebhookID) (*portainer.Webhook, error) {
|
|
||||||
var webhook portainer.Webhook
|
|
||||||
identifier := internal.Itob(int(ID))
|
|
||||||
|
|
||||||
err := internal.GetObject(service.connection, BucketName, identifier, &webhook)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &webhook, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// WebhookByResourceID returns a webhook by the ResourceID it is associated with.
|
|
||||||
func (service *Service) WebhookByResourceID(ID string) (*portainer.Webhook, error) {
|
|
||||||
var webhook *portainer.Webhook
|
|
||||||
|
|
||||||
err := service.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 w portainer.Webhook
|
|
||||||
err := internal.UnmarshalObject(v, &w)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if w.ResourceID == ID {
|
|
||||||
webhook = &w
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if webhook == nil {
|
|
||||||
return errors.ErrObjectNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
return webhook, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// WebhookByToken returns a webhook by the random token it is associated with.
|
|
||||||
func (service *Service) WebhookByToken(token string) (*portainer.Webhook, error) {
|
|
||||||
var webhook *portainer.Webhook
|
|
||||||
|
|
||||||
err := service.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 w portainer.Webhook
|
|
||||||
err := internal.UnmarshalObject(v, &w)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if w.Token == token {
|
|
||||||
webhook = &w
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if webhook == nil {
|
|
||||||
return errors.ErrObjectNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
return webhook, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteWebhook deletes a webhook.
|
|
||||||
func (service *Service) DeleteWebhook(ID portainer.WebhookID) error {
|
|
||||||
identifier := internal.Itob(int(ID))
|
|
||||||
return internal.DeleteObject(service.connection, BucketName, identifier)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateWebhook assign an ID to a new webhook and saves it.
|
|
||||||
func (service *Service) CreateWebhook(webhook *portainer.Webhook) error {
|
|
||||||
return service.connection.Update(func(tx *bolt.Tx) error {
|
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
|
||||||
|
|
||||||
id, _ := bucket.NextSequence()
|
|
||||||
webhook.ID = portainer.WebhookID(id)
|
|
||||||
|
|
||||||
data, err := internal.MarshalObject(webhook)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return bucket.Put(internal.Itob(int(webhook.ID)), data)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -13,7 +13,7 @@ import (
|
|||||||
chserver "github.com/jpillora/chisel/server"
|
chserver "github.com/jpillora/chisel/server"
|
||||||
cmap "github.com/orcaman/concurrent-map"
|
cmap "github.com/orcaman/concurrent-map"
|
||||||
portainer "github.com/portainer/portainer/api"
|
portainer "github.com/portainer/portainer/api"
|
||||||
"github.com/portainer/portainer/api/bolt/errors"
|
"github.com/portainer/portainer/api/dataservices"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -29,7 +29,7 @@ type Service struct {
|
|||||||
serverFingerprint string
|
serverFingerprint string
|
||||||
serverPort string
|
serverPort string
|
||||||
tunnelDetailsMap cmap.ConcurrentMap
|
tunnelDetailsMap cmap.ConcurrentMap
|
||||||
dataStore portainer.DataStore
|
dataStore dataservices.DataStore
|
||||||
snapshotService portainer.SnapshotService
|
snapshotService portainer.SnapshotService
|
||||||
chiselServer *chserver.Server
|
chiselServer *chserver.Server
|
||||||
shutdownCtx context.Context
|
shutdownCtx context.Context
|
||||||
@@ -37,7 +37,7 @@ type Service struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewService returns a pointer to a new instance of Service
|
// NewService returns a pointer to a new instance of Service
|
||||||
func NewService(dataStore portainer.DataStore, shutdownCtx context.Context) *Service {
|
func NewService(dataStore dataservices.DataStore, shutdownCtx context.Context) *Service {
|
||||||
return &Service{
|
return &Service{
|
||||||
tunnelDetailsMap: cmap.New(),
|
tunnelDetailsMap: cmap.New(),
|
||||||
dataStore: dataStore,
|
dataStore: dataStore,
|
||||||
@@ -46,7 +46,7 @@ func NewService(dataStore portainer.DataStore, shutdownCtx context.Context) *Ser
|
|||||||
}
|
}
|
||||||
|
|
||||||
// pingAgent ping the given agent so that the agent can keep the tunnel alive
|
// pingAgent ping the given agent so that the agent can keep the tunnel alive
|
||||||
func (service *Service) pingAgent(endpointID portainer.EndpointID) error{
|
func (service *Service) pingAgent(endpointID portainer.EndpointID) error {
|
||||||
tunnel := service.GetTunnelDetails(endpointID)
|
tunnel := service.GetTunnelDetails(endpointID)
|
||||||
requestURL := fmt.Sprintf("http://127.0.0.1:%d/ping", tunnel.Port)
|
requestURL := fmt.Sprintf("http://127.0.0.1:%d/ping", tunnel.Port)
|
||||||
req, err := http.NewRequest(http.MethodHead, requestURL, nil)
|
req, err := http.NewRequest(http.MethodHead, requestURL, nil)
|
||||||
@@ -147,7 +147,7 @@ func (service *Service) retrievePrivateKeySeed() (string, error) {
|
|||||||
var serverInfo *portainer.TunnelServerInfo
|
var serverInfo *portainer.TunnelServerInfo
|
||||||
|
|
||||||
serverInfo, err := service.dataStore.TunnelServer().Info()
|
serverInfo, err := service.dataStore.TunnelServer().Info()
|
||||||
if err == errors.ErrObjectNotFound {
|
if service.dataStore.IsErrObjectNotFound(err) {
|
||||||
keySeed := uniuri.NewLen(16)
|
keySeed := uniuri.NewLen(16)
|
||||||
|
|
||||||
serverInfo = &portainer.TunnelServerInfo{
|
serverInfo = &portainer.TunnelServerInfo{
|
||||||
|
|||||||
@@ -45,16 +45,22 @@ func (*Service) ParseFlags(version string) (*portainer.CLIFlags, error) {
|
|||||||
TLSCert: kingpin.Flag("tlscert", "Path to the TLS certificate file").Default(defaultTLSCertPath).String(),
|
TLSCert: kingpin.Flag("tlscert", "Path to the TLS certificate file").Default(defaultTLSCertPath).String(),
|
||||||
TLSKey: kingpin.Flag("tlskey", "Path to the TLS key").Default(defaultTLSKeyPath).String(),
|
TLSKey: kingpin.Flag("tlskey", "Path to the TLS key").Default(defaultTLSKeyPath).String(),
|
||||||
HTTPDisabled: kingpin.Flag("http-disabled", "Serve portainer only on https").Default(defaultHTTPDisabled).Bool(),
|
HTTPDisabled: kingpin.Flag("http-disabled", "Serve portainer only on https").Default(defaultHTTPDisabled).Bool(),
|
||||||
|
HTTPEnabled: kingpin.Flag("http-enabled", "Serve portainer on http").Default(defaultHTTPEnabled).Bool(),
|
||||||
SSL: kingpin.Flag("ssl", "Secure Portainer instance using SSL (deprecated)").Default(defaultSSL).Bool(),
|
SSL: kingpin.Flag("ssl", "Secure Portainer instance using SSL (deprecated)").Default(defaultSSL).Bool(),
|
||||||
SSLCert: kingpin.Flag("sslcert", "Path to the SSL certificate used to secure the Portainer instance").String(),
|
SSLCert: kingpin.Flag("sslcert", "Path to the SSL certificate used to secure the Portainer instance").String(),
|
||||||
SSLKey: kingpin.Flag("sslkey", "Path to the SSL key used to secure the Portainer instance").String(),
|
SSLKey: kingpin.Flag("sslkey", "Path to the SSL key used to secure the Portainer instance").String(),
|
||||||
Rollback: kingpin.Flag("rollback", "Rollback the database store to the previous version").Bool(),
|
Rollback: kingpin.Flag("rollback", "Rollback the database store to the previous version").Bool(),
|
||||||
SnapshotInterval: kingpin.Flag("snapshot-interval", "Duration between each environment snapshot job").Default(defaultSnapshotInterval).String(),
|
SnapshotInterval: kingpin.Flag("snapshot-interval", "Duration between each environment snapshot job").String(),
|
||||||
AdminPassword: kingpin.Flag("admin-password", "Hashed admin password").String(),
|
AdminPassword: kingpin.Flag("admin-password", "Hashed admin password").String(),
|
||||||
AdminPasswordFile: kingpin.Flag("admin-password-file", "Path to the file containing the password for the admin user").String(),
|
AdminPasswordFile: kingpin.Flag("admin-password-file", "Path to the file containing the password for the admin user").String(),
|
||||||
Labels: pairs(kingpin.Flag("hide-label", "Hide containers with a specific label in the UI").Short('l')),
|
Labels: pairs(kingpin.Flag("hide-label", "Hide containers with a specific label in the UI").Short('l')),
|
||||||
Logo: kingpin.Flag("logo", "URL for the logo displayed in the UI").String(),
|
Logo: kingpin.Flag("logo", "URL for the logo displayed in the UI").String(),
|
||||||
Templates: kingpin.Flag("templates", "URL to the templates definitions.").Short('t').String(),
|
Templates: kingpin.Flag("templates", "URL to the templates definitions.").Short('t').String(),
|
||||||
|
BaseURL: kingpin.Flag("base-url", "Base URL parameter such as portainer if running portainer as http://yourdomain.com/portainer/.").Short('b').Default(defaultBaseURL).String(),
|
||||||
|
InitialMmapSize: kingpin.Flag("initial-mmap-size", "Initial mmap size of the database in bytes").Int(),
|
||||||
|
MaxBatchSize: kingpin.Flag("max-batch-size", "Maximum size of a batch").Int(),
|
||||||
|
MaxBatchDelay: kingpin.Flag("max-batch-delay", "Maximum delay before a batch starts").Duration(),
|
||||||
|
SecretKeyName: kingpin.Flag("secret-key-name", "Secret key name for encryption and will be used as /run/secrets/<secret-key-name>.").Default(defaultSecretKeyName).String(),
|
||||||
}
|
}
|
||||||
|
|
||||||
kingpin.Parse()
|
kingpin.Parse()
|
||||||
@@ -123,7 +129,7 @@ func validateEndpointURL(endpointURL string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func validateSnapshotInterval(snapshotInterval string) error {
|
func validateSnapshotInterval(snapshotInterval string) error {
|
||||||
if snapshotInterval != defaultSnapshotInterval {
|
if snapshotInterval != "" {
|
||||||
_, err := time.ParseDuration(snapshotInterval)
|
_, err := time.ParseDuration(snapshotInterval)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errInvalidSnapshotInterval
|
return errInvalidSnapshotInterval
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//go:build !windows
|
||||||
// +build !windows
|
// +build !windows
|
||||||
|
|
||||||
package cli
|
package cli
|
||||||
@@ -15,8 +16,10 @@ const (
|
|||||||
defaultTLSCertPath = "/certs/cert.pem"
|
defaultTLSCertPath = "/certs/cert.pem"
|
||||||
defaultTLSKeyPath = "/certs/key.pem"
|
defaultTLSKeyPath = "/certs/key.pem"
|
||||||
defaultHTTPDisabled = "false"
|
defaultHTTPDisabled = "false"
|
||||||
|
defaultHTTPEnabled = "false"
|
||||||
defaultSSL = "false"
|
defaultSSL = "false"
|
||||||
defaultSSLCertPath = "/certs/portainer.crt"
|
defaultSSLCertPath = "/certs/portainer.crt"
|
||||||
defaultSSLKeyPath = "/certs/portainer.key"
|
defaultSSLKeyPath = "/certs/portainer.key"
|
||||||
defaultSnapshotInterval = "5m"
|
defaultBaseURL = "/"
|
||||||
|
defaultSecretKeyName = "portainer"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -13,8 +13,11 @@ const (
|
|||||||
defaultTLSCertPath = "C:\\certs\\cert.pem"
|
defaultTLSCertPath = "C:\\certs\\cert.pem"
|
||||||
defaultTLSKeyPath = "C:\\certs\\key.pem"
|
defaultTLSKeyPath = "C:\\certs\\key.pem"
|
||||||
defaultHTTPDisabled = "false"
|
defaultHTTPDisabled = "false"
|
||||||
|
defaultHTTPEnabled = "false"
|
||||||
defaultSSL = "false"
|
defaultSSL = "false"
|
||||||
defaultSSLCertPath = "C:\\certs\\portainer.crt"
|
defaultSSLCertPath = "C:\\certs\\portainer.crt"
|
||||||
defaultSSLKeyPath = "C:\\certs\\portainer.key"
|
defaultSSLKeyPath = "C:\\certs\\portainer.key"
|
||||||
defaultSnapshotInterval = "5m"
|
defaultSnapshotInterval = "5m"
|
||||||
|
defaultBaseURL = "/"
|
||||||
|
defaultSecretKeyName = "portainer"
|
||||||
)
|
)
|
||||||
|
|||||||
29
api/cmd/portainer/import.go
Normal file
29
api/cmd/portainer/import.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
portainer "github.com/portainer/portainer/api"
|
||||||
|
"github.com/portainer/portainer/api/datastore"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func importFromJson(fileService portainer.FileService, store *datastore.Store) {
|
||||||
|
// EXPERIMENTAL - if used with an incomplete json file, it will fail, as we don't have a way to default the model values
|
||||||
|
importFile := "/data/import.json"
|
||||||
|
if exists, _ := fileService.FileExists(importFile); exists {
|
||||||
|
if err := store.Import(importFile); err != nil {
|
||||||
|
logrus.WithError(err).Debugf("Import %s failed", importFile)
|
||||||
|
|
||||||
|
// TODO: should really rollback on failure, but then we have nothing.
|
||||||
|
} else {
|
||||||
|
logrus.Printf("Successfully imported %s to new portainer database", importFile)
|
||||||
|
}
|
||||||
|
// TODO: this is bad - its to ensure that any defaults that were broken in import, or migrations get set back to what we want
|
||||||
|
// I also suspect that everything from "Init to Init" is potentially a migration
|
||||||
|
err := store.Init()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed initializing data store: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,18 +1,41 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type portainerFormatter struct {
|
||||||
|
logrus.TextFormatter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *portainerFormatter) Format(entry *logrus.Entry) ([]byte, error) {
|
||||||
|
var levelColor int
|
||||||
|
switch entry.Level {
|
||||||
|
case logrus.DebugLevel, logrus.TraceLevel:
|
||||||
|
levelColor = 31 // gray
|
||||||
|
case logrus.WarnLevel:
|
||||||
|
levelColor = 33 // yellow
|
||||||
|
case logrus.ErrorLevel, logrus.FatalLevel, logrus.PanicLevel:
|
||||||
|
levelColor = 31 // red
|
||||||
|
default:
|
||||||
|
levelColor = 36 // blue
|
||||||
|
}
|
||||||
|
return []byte(fmt.Sprintf("\x1b[%dm%s\x1b[0m %s %s\n", levelColor, strings.ToUpper(entry.Level.String()), entry.Time.Format(f.TimestampFormat), entry.Message)), nil
|
||||||
|
}
|
||||||
|
|
||||||
func configureLogger() {
|
func configureLogger() {
|
||||||
logger := logrus.New() // logger is to implicitly substitute stdlib's log
|
logger := logrus.New() // logger is to implicitly substitute stdlib's log
|
||||||
log.SetOutput(logger.Writer())
|
log.SetOutput(logger.Writer())
|
||||||
|
|
||||||
formatter := &logrus.TextFormatter{DisableTimestamp: true, DisableLevelTruncation: true}
|
formatter := &logrus.TextFormatter{DisableTimestamp: true, DisableLevelTruncation: true}
|
||||||
|
formatterLogrus := &portainerFormatter{logrus.TextFormatter{DisableTimestamp: false, DisableLevelTruncation: true, TimestampFormat: "2006/01/02 15:04:05", FullTimestamp: true}}
|
||||||
|
|
||||||
logger.SetFormatter(formatter)
|
logger.SetFormatter(formatter)
|
||||||
logrus.SetFormatter(formatter)
|
logrus.SetFormatter(formatterLogrus)
|
||||||
|
|
||||||
logger.SetLevel(logrus.DebugLevel)
|
logger.SetLevel(logrus.DebugLevel)
|
||||||
logrus.SetLevel(logrus.DebugLevel)
|
logrus.SetLevel(logrus.DebugLevel)
|
||||||
|
|||||||
@@ -2,23 +2,30 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/sha256"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"github.com/portainer/libhelm"
|
||||||
portainer "github.com/portainer/portainer/api"
|
portainer "github.com/portainer/portainer/api"
|
||||||
"github.com/portainer/portainer/api/bolt"
|
"github.com/portainer/portainer/api/apikey"
|
||||||
"github.com/portainer/portainer/api/chisel"
|
"github.com/portainer/portainer/api/chisel"
|
||||||
"github.com/portainer/portainer/api/cli"
|
"github.com/portainer/portainer/api/cli"
|
||||||
"github.com/portainer/portainer/api/crypto"
|
"github.com/portainer/portainer/api/crypto"
|
||||||
|
"github.com/portainer/portainer/api/database"
|
||||||
|
"github.com/portainer/portainer/api/database/boltdb"
|
||||||
|
"github.com/portainer/portainer/api/dataservices"
|
||||||
|
"github.com/portainer/portainer/api/datastore"
|
||||||
"github.com/portainer/portainer/api/docker"
|
"github.com/portainer/portainer/api/docker"
|
||||||
|
|
||||||
"github.com/portainer/libhelm"
|
|
||||||
"github.com/portainer/portainer/api/exec"
|
"github.com/portainer/portainer/api/exec"
|
||||||
"github.com/portainer/portainer/api/filesystem"
|
"github.com/portainer/portainer/api/filesystem"
|
||||||
"github.com/portainer/portainer/api/git"
|
"github.com/portainer/portainer/api/git"
|
||||||
|
"github.com/portainer/portainer/api/hostmanagement/openamt"
|
||||||
"github.com/portainer/portainer/api/http"
|
"github.com/portainer/portainer/api/http"
|
||||||
"github.com/portainer/portainer/api/http/client"
|
"github.com/portainer/portainer/api/http/client"
|
||||||
"github.com/portainer/portainer/api/http/proxy"
|
"github.com/portainer/portainer/api/http/proxy"
|
||||||
@@ -40,12 +47,12 @@ func initCLI() *portainer.CLIFlags {
|
|||||||
var cliService portainer.CLIService = &cli.Service{}
|
var cliService portainer.CLIService = &cli.Service{}
|
||||||
flags, err := cliService.ParseFlags(portainer.APIVersion)
|
flags, err := cliService.ParseFlags(portainer.APIVersion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed parsing flags: %v", err)
|
logrus.Fatalf("Failed parsing flags: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = cliService.ValidateFlags(flags)
|
err = cliService.ValidateFlags(flags)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed validating flags:%v", err)
|
logrus.Fatalf("Failed validating flags:%v", err)
|
||||||
}
|
}
|
||||||
return flags
|
return flags
|
||||||
}
|
}
|
||||||
@@ -53,62 +60,92 @@ func initCLI() *portainer.CLIFlags {
|
|||||||
func initFileService(dataStorePath string) portainer.FileService {
|
func initFileService(dataStorePath string) portainer.FileService {
|
||||||
fileService, err := filesystem.NewService(dataStorePath, "")
|
fileService, err := filesystem.NewService(dataStorePath, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed creating file service: %v", err)
|
logrus.Fatalf("Failed creating file service: %v", err)
|
||||||
}
|
}
|
||||||
return fileService
|
return fileService
|
||||||
}
|
}
|
||||||
|
|
||||||
func initDataStore(dataStorePath string, rollback bool, fileService portainer.FileService, shutdownCtx context.Context) portainer.DataStore {
|
func initDataStore(flags *portainer.CLIFlags, secretKey []byte, fileService portainer.FileService, shutdownCtx context.Context) dataservices.DataStore {
|
||||||
store := bolt.NewStore(dataStorePath, fileService)
|
connection, err := database.NewDatabase("boltdb", *flags.Data, secretKey)
|
||||||
err := store.Open()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed opening store: %v", err)
|
logrus.Fatalf("failed creating database connection: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if rollback {
|
if bconn, ok := connection.(*boltdb.DbConnection); ok {
|
||||||
|
bconn.MaxBatchSize = *flags.MaxBatchSize
|
||||||
|
bconn.MaxBatchDelay = *flags.MaxBatchDelay
|
||||||
|
bconn.InitialMmapSize = *flags.InitialMmapSize
|
||||||
|
} else {
|
||||||
|
logrus.Fatalf("failed creating database connection: expecting a boltdb database type but a different one was received")
|
||||||
|
}
|
||||||
|
|
||||||
|
store := datastore.NewStore(*flags.Data, fileService, connection)
|
||||||
|
isNew, err := store.Open()
|
||||||
|
if err != nil {
|
||||||
|
logrus.Fatalf("Failed opening store: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if *flags.Rollback {
|
||||||
err := store.Rollback(false)
|
err := store.Rollback(false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed rolling back: %s", err)
|
logrus.Fatalf("Failed rolling back: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("Exiting rollback")
|
logrus.Println("Exiting rollback")
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Init sets some defaults - it's basically a migration
|
||||||
err = store.Init()
|
err = store.Init()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed initializing data store: %v", err)
|
logrus.Fatalf("Failed initializing data store: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = store.MigrateData(false)
|
if isNew {
|
||||||
|
// from MigrateData
|
||||||
|
store.VersionService.StoreDBVersion(portainer.DBVersion)
|
||||||
|
} else {
|
||||||
|
storedVersion, err := store.VersionService.DBVersion()
|
||||||
|
if err != nil {
|
||||||
|
logrus.Fatalf("Something Failed during creation of new database: %v", err)
|
||||||
|
}
|
||||||
|
if storedVersion != portainer.DBVersion {
|
||||||
|
err = store.MigrateData()
|
||||||
|
if err != nil {
|
||||||
|
logrus.Fatalf("Failed migration: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = updateSettingsFromFlags(store, flags)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed migration: %v", err)
|
logrus.Fatalf("Failed updating settings from flags: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
go shutdownDatastore(shutdownCtx, store)
|
// this is for the db restore functionality - needs more tests.
|
||||||
return store
|
go func() {
|
||||||
}
|
<-shutdownCtx.Done()
|
||||||
|
defer connection.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
func shutdownDatastore(shutdownCtx context.Context, datastore portainer.DataStore) {
|
return store
|
||||||
<-shutdownCtx.Done()
|
|
||||||
datastore.Close()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func initComposeStackManager(assetsPath string, configPath string, reverseTunnelService portainer.ReverseTunnelService, proxyManager *proxy.Manager) portainer.ComposeStackManager {
|
func initComposeStackManager(assetsPath string, configPath string, reverseTunnelService portainer.ReverseTunnelService, proxyManager *proxy.Manager) portainer.ComposeStackManager {
|
||||||
composeWrapper, err := exec.NewComposeStackManager(assetsPath, configPath, proxyManager)
|
composeWrapper, err := exec.NewComposeStackManager(assetsPath, configPath, proxyManager)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed creating compose manager: %s", err)
|
logrus.Fatalf("Failed creating compose manager: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return composeWrapper
|
return composeWrapper
|
||||||
}
|
}
|
||||||
|
|
||||||
func initSwarmStackManager(assetsPath string, configPath string, signatureService portainer.DigitalSignatureService, fileService portainer.FileService, reverseTunnelService portainer.ReverseTunnelService) (portainer.SwarmStackManager, error) {
|
func initSwarmStackManager(assetsPath string, configPath string, signatureService portainer.DigitalSignatureService, fileService portainer.FileService, reverseTunnelService portainer.ReverseTunnelService, dataStore dataservices.DataStore) (portainer.SwarmStackManager, error) {
|
||||||
return exec.NewSwarmStackManager(assetsPath, configPath, signatureService, fileService, reverseTunnelService)
|
return exec.NewSwarmStackManager(assetsPath, configPath, signatureService, fileService, reverseTunnelService, dataStore)
|
||||||
}
|
}
|
||||||
|
|
||||||
func initKubernetesDeployer(kubernetesTokenCacheManager *kubeproxy.TokenCacheManager, kubernetesClientFactory *kubecli.ClientFactory, dataStore portainer.DataStore, reverseTunnelService portainer.ReverseTunnelService, signatureService portainer.DigitalSignatureService, proxyManager *proxy.Manager, assetsPath string) portainer.KubernetesDeployer {
|
func initKubernetesDeployer(kubernetesTokenCacheManager *kubeproxy.TokenCacheManager, kubernetesClientFactory *kubecli.ClientFactory, dataStore dataservices.DataStore, reverseTunnelService portainer.ReverseTunnelService, signatureService portainer.DigitalSignatureService, proxyManager *proxy.Manager, assetsPath string) portainer.KubernetesDeployer {
|
||||||
return exec.NewKubernetesDeployer(kubernetesTokenCacheManager, kubernetesClientFactory, dataStore, reverseTunnelService, signatureService, proxyManager, assetsPath)
|
return exec.NewKubernetesDeployer(kubernetesTokenCacheManager, kubernetesClientFactory, dataStore, reverseTunnelService, signatureService, proxyManager, assetsPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,17 +153,12 @@ func initHelmPackageManager(assetsPath string) (libhelm.HelmPackageManager, erro
|
|||||||
return libhelm.NewHelmPackageManager(libhelm.HelmConfig{BinaryPath: assetsPath})
|
return libhelm.NewHelmPackageManager(libhelm.HelmConfig{BinaryPath: assetsPath})
|
||||||
}
|
}
|
||||||
|
|
||||||
func initJWTService(dataStore portainer.DataStore) (portainer.JWTService, error) {
|
func initAPIKeyService(datastore dataservices.DataStore) apikey.APIKeyService {
|
||||||
settings, err := dataStore.Settings().Settings()
|
return apikey.NewAPIKeyService(datastore.APIKeyRepository(), datastore.User())
|
||||||
if err != nil {
|
}
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if settings.UserSessionTimeout == "" {
|
func initJWTService(userSessionTimeout string, dataStore dataservices.DataStore) (dataservices.JWTService, error) {
|
||||||
settings.UserSessionTimeout = portainer.DefaultUserSessionTimeout
|
jwtService, err := jwt.NewService(userSessionTimeout, dataStore)
|
||||||
dataStore.Settings().UpdateSettings(settings)
|
|
||||||
}
|
|
||||||
jwtService, err := jwt.NewService(settings.UserSessionTimeout, dataStore)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -153,7 +185,7 @@ func initGitService() portainer.GitService {
|
|||||||
return git.NewService()
|
return git.NewService()
|
||||||
}
|
}
|
||||||
|
|
||||||
func initSSLService(addr, dataPath, certPath, keyPath string, fileService portainer.FileService, dataStore portainer.DataStore, shutdownTrigger context.CancelFunc) (*ssl.Service, error) {
|
func initSSLService(addr, dataPath, certPath, keyPath string, fileService portainer.FileService, dataStore dataservices.DataStore, shutdownTrigger context.CancelFunc) (*ssl.Service, error) {
|
||||||
slices := strings.Split(addr, ":")
|
slices := strings.Split(addr, ":")
|
||||||
host := slices[0]
|
host := slices[0]
|
||||||
if host == "" {
|
if host == "" {
|
||||||
@@ -174,15 +206,15 @@ func initDockerClientFactory(signatureService portainer.DigitalSignatureService,
|
|||||||
return docker.NewClientFactory(signatureService, reverseTunnelService)
|
return docker.NewClientFactory(signatureService, reverseTunnelService)
|
||||||
}
|
}
|
||||||
|
|
||||||
func initKubernetesClientFactory(signatureService portainer.DigitalSignatureService, reverseTunnelService portainer.ReverseTunnelService, instanceID string, dataStore portainer.DataStore) *kubecli.ClientFactory {
|
func initKubernetesClientFactory(signatureService portainer.DigitalSignatureService, reverseTunnelService portainer.ReverseTunnelService, instanceID string, dataStore dataservices.DataStore) *kubecli.ClientFactory {
|
||||||
return kubecli.NewClientFactory(signatureService, reverseTunnelService, instanceID, dataStore)
|
return kubecli.NewClientFactory(signatureService, reverseTunnelService, instanceID, dataStore)
|
||||||
}
|
}
|
||||||
|
|
||||||
func initSnapshotService(snapshotInterval string, dataStore portainer.DataStore, dockerClientFactory *docker.ClientFactory, kubernetesClientFactory *kubecli.ClientFactory, shutdownCtx context.Context) (portainer.SnapshotService, error) {
|
func initSnapshotService(snapshotIntervalFromFlag string, dataStore dataservices.DataStore, dockerClientFactory *docker.ClientFactory, kubernetesClientFactory *kubecli.ClientFactory, shutdownCtx context.Context) (portainer.SnapshotService, error) {
|
||||||
dockerSnapshotter := docker.NewSnapshotter(dockerClientFactory)
|
dockerSnapshotter := docker.NewSnapshotter(dockerClientFactory)
|
||||||
kubernetesSnapshotter := kubernetes.NewSnapshotter(kubernetesClientFactory)
|
kubernetesSnapshotter := kubernetes.NewSnapshotter(kubernetesClientFactory)
|
||||||
|
|
||||||
snapshotService, err := snapshot.NewService(snapshotInterval, dataStore, dockerSnapshotter, kubernetesSnapshotter, shutdownCtx)
|
snapshotService, err := snapshot.NewService(snapshotIntervalFromFlag, dataStore, dockerSnapshotter, kubernetesSnapshotter, shutdownCtx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -197,17 +229,23 @@ func initStatus(instanceID string) *portainer.Status {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateSettingsFromFlags(dataStore portainer.DataStore, flags *portainer.CLIFlags) error {
|
func updateSettingsFromFlags(dataStore dataservices.DataStore, flags *portainer.CLIFlags) error {
|
||||||
settings, err := dataStore.Settings().Settings()
|
settings, err := dataStore.Settings().Settings()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
settings.LogoURL = *flags.Logo
|
if *flags.SnapshotInterval != "" {
|
||||||
settings.SnapshotInterval = *flags.SnapshotInterval
|
settings.SnapshotInterval = *flags.SnapshotInterval
|
||||||
settings.EnableEdgeComputeFeatures = *flags.EnableEdgeComputeFeatures
|
}
|
||||||
settings.EnableTelemetry = true
|
|
||||||
settings.OAuthSettings.SSO = true
|
if *flags.Logo != "" {
|
||||||
|
settings.LogoURL = *flags.Logo
|
||||||
|
}
|
||||||
|
|
||||||
|
if *flags.EnableEdgeComputeFeatures {
|
||||||
|
settings.EnableEdgeComputeFeatures = *flags.EnableEdgeComputeFeatures
|
||||||
|
}
|
||||||
|
|
||||||
if *flags.Templates != "" {
|
if *flags.Templates != "" {
|
||||||
settings.TemplatesURL = *flags.Templates
|
settings.TemplatesURL = *flags.Templates
|
||||||
@@ -222,14 +260,16 @@ func updateSettingsFromFlags(dataStore portainer.DataStore, flags *portainer.CLI
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
httpEnabled := !*flags.HTTPDisabled
|
|
||||||
|
|
||||||
sslSettings, err := dataStore.SSLSettings().Settings()
|
sslSettings, err := dataStore.SSLSettings().Settings()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
sslSettings.HTTPEnabled = httpEnabled
|
if *flags.HTTPDisabled {
|
||||||
|
sslSettings.HTTPEnabled = false
|
||||||
|
} else if *flags.HTTPEnabled {
|
||||||
|
sslSettings.HTTPEnabled = true
|
||||||
|
}
|
||||||
|
|
||||||
err = dataStore.SSLSettings().UpdateSettings(sslSettings)
|
err = dataStore.SSLSettings().UpdateSettings(sslSettings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -242,7 +282,7 @@ func updateSettingsFromFlags(dataStore portainer.DataStore, flags *portainer.CLI
|
|||||||
// enableFeaturesFromFlags turns on or off feature flags
|
// enableFeaturesFromFlags turns on or off feature flags
|
||||||
// e.g. portainer --feat open-amt --feat fdo=true ... (defaults to true)
|
// e.g. portainer --feat open-amt --feat fdo=true ... (defaults to true)
|
||||||
// note, settings are persisted to the DB. To turn off `--feat open-amt=false`
|
// note, settings are persisted to the DB. To turn off `--feat open-amt=false`
|
||||||
func enableFeaturesFromFlags(dataStore portainer.DataStore, flags *portainer.CLIFlags) error {
|
func enableFeaturesFromFlags(dataStore dataservices.DataStore, flags *portainer.CLIFlags) error {
|
||||||
settings, err := dataStore.Settings().Settings()
|
settings, err := dataStore.Settings().Settings()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -271,9 +311,9 @@ func enableFeaturesFromFlags(dataStore portainer.DataStore, flags *portainer.CLI
|
|||||||
}
|
}
|
||||||
|
|
||||||
if featureState {
|
if featureState {
|
||||||
log.Printf("Feature %v : on", *correspondingFeature)
|
logrus.Printf("Feature %v : on", *correspondingFeature)
|
||||||
} else {
|
} else {
|
||||||
log.Printf("Feature %v : off", *correspondingFeature)
|
logrus.Printf("Feature %v : off", *correspondingFeature)
|
||||||
}
|
}
|
||||||
|
|
||||||
settings.FeatureFlagSettings[*correspondingFeature] = featureState
|
settings.FeatureFlagSettings[*correspondingFeature] = featureState
|
||||||
@@ -302,7 +342,7 @@ func generateAndStoreKeyPair(fileService portainer.FileService, signatureService
|
|||||||
func initKeyPair(fileService portainer.FileService, signatureService portainer.DigitalSignatureService) error {
|
func initKeyPair(fileService portainer.FileService, signatureService portainer.DigitalSignatureService) error {
|
||||||
existingKeyPair, err := fileService.KeyPairFilesExist()
|
existingKeyPair, err := fileService.KeyPairFilesExist()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed checking for existing key pair: %v", err)
|
logrus.Fatalf("Failed checking for existing key pair: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if existingKeyPair {
|
if existingKeyPair {
|
||||||
@@ -311,7 +351,7 @@ func initKeyPair(fileService portainer.FileService, signatureService portainer.D
|
|||||||
return generateAndStoreKeyPair(fileService, signatureService)
|
return generateAndStoreKeyPair(fileService, signatureService)
|
||||||
}
|
}
|
||||||
|
|
||||||
func createTLSSecuredEndpoint(flags *portainer.CLIFlags, dataStore portainer.DataStore, snapshotService portainer.SnapshotService) error {
|
func createTLSSecuredEndpoint(flags *portainer.CLIFlags, dataStore dataservices.DataStore, snapshotService portainer.SnapshotService) error {
|
||||||
tlsConfiguration := portainer.TLSConfiguration{
|
tlsConfiguration := portainer.TLSConfiguration{
|
||||||
TLS: *flags.TLS,
|
TLS: *flags.TLS,
|
||||||
TLSSkipVerify: *flags.TLSSkipVerify,
|
TLSSkipVerify: *flags.TLSSkipVerify,
|
||||||
@@ -373,13 +413,13 @@ func createTLSSecuredEndpoint(flags *portainer.CLIFlags, dataStore portainer.Dat
|
|||||||
|
|
||||||
err := snapshotService.SnapshotEndpoint(endpoint)
|
err := snapshotService.SnapshotEndpoint(endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("http error: environment snapshot error (environment=%s, URL=%s) (err=%s)\n", endpoint.Name, endpoint.URL, err)
|
logrus.Printf("http error: environment snapshot error (environment=%s, URL=%s) (err=%s)\n", endpoint.Name, endpoint.URL, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return dataStore.Endpoint().CreateEndpoint(endpoint)
|
return dataStore.Endpoint().Create(endpoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
func createUnsecuredEndpoint(endpointURL string, dataStore portainer.DataStore, snapshotService portainer.SnapshotService) error {
|
func createUnsecuredEndpoint(endpointURL string, dataStore dataservices.DataStore, snapshotService portainer.SnapshotService) error {
|
||||||
if strings.HasPrefix(endpointURL, "tcp://") {
|
if strings.HasPrefix(endpointURL, "tcp://") {
|
||||||
_, err := client.ExecutePingOperation(endpointURL, nil)
|
_, err := client.ExecutePingOperation(endpointURL, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -419,13 +459,13 @@ func createUnsecuredEndpoint(endpointURL string, dataStore portainer.DataStore,
|
|||||||
|
|
||||||
err := snapshotService.SnapshotEndpoint(endpoint)
|
err := snapshotService.SnapshotEndpoint(endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("http error: environment snapshot error (environment=%s, URL=%s) (err=%s)\n", endpoint.Name, endpoint.URL, err)
|
logrus.Printf("http error: environment snapshot error (environment=%s, URL=%s) (err=%s)\n", endpoint.Name, endpoint.URL, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return dataStore.Endpoint().CreateEndpoint(endpoint)
|
return dataStore.Endpoint().Create(endpoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
func initEndpoint(flags *portainer.CLIFlags, dataStore portainer.DataStore, snapshotService portainer.SnapshotService) error {
|
func initEndpoint(flags *portainer.CLIFlags, dataStore dataservices.DataStore, snapshotService portainer.SnapshotService) error {
|
||||||
if *flags.EndpointURL == "" {
|
if *flags.EndpointURL == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -436,7 +476,7 @@ func initEndpoint(flags *portainer.CLIFlags, dataStore portainer.DataStore, snap
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(endpoints) > 0 {
|
if len(endpoints) > 0 {
|
||||||
log.Println("Instance already has defined environments. Skipping the environment defined via CLI.")
|
logrus.Println("Instance already has defined environments. Skipping the environment defined via CLI.")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -446,60 +486,90 @@ func initEndpoint(flags *portainer.CLIFlags, dataStore portainer.DataStore, snap
|
|||||||
return createUnsecuredEndpoint(*flags.EndpointURL, dataStore, snapshotService)
|
return createUnsecuredEndpoint(*flags.EndpointURL, dataStore, snapshotService)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func loadEncryptionSecretKey(keyfilename string) []byte {
|
||||||
|
content, err := os.ReadFile(path.Join("/run/secrets", keyfilename))
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
logrus.Printf("Encryption key file `%s` not present", keyfilename)
|
||||||
|
} else {
|
||||||
|
logrus.Printf("Error reading encryption key file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// return a 32 byte hash of the secret (required for AES)
|
||||||
|
hash := sha256.Sum256(content)
|
||||||
|
return hash[:]
|
||||||
|
}
|
||||||
|
|
||||||
func buildServer(flags *portainer.CLIFlags) portainer.Server {
|
func buildServer(flags *portainer.CLIFlags) portainer.Server {
|
||||||
shutdownCtx, shutdownTrigger := context.WithCancel(context.Background())
|
shutdownCtx, shutdownTrigger := context.WithCancel(context.Background())
|
||||||
|
|
||||||
fileService := initFileService(*flags.Data)
|
fileService := initFileService(*flags.Data)
|
||||||
|
encryptionKey := loadEncryptionSecretKey(*flags.SecretKeyName)
|
||||||
dataStore := initDataStore(*flags.Data, *flags.Rollback, fileService, shutdownCtx)
|
if encryptionKey == nil {
|
||||||
|
logrus.Println("Proceeding without encryption key")
|
||||||
if err := dataStore.CheckCurrentEdition(); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
jwtService, err := initJWTService(dataStore)
|
dataStore := initDataStore(flags, encryptionKey, fileService, shutdownCtx)
|
||||||
|
|
||||||
|
if err := dataStore.CheckCurrentEdition(); err != nil {
|
||||||
|
logrus.Fatal(err)
|
||||||
|
}
|
||||||
|
instanceID, err := dataStore.Version().InstanceID()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed initializing JWT service: %v", err)
|
logrus.Fatalf("Failed getting instance id: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
apiKeyService := initAPIKeyService(dataStore)
|
||||||
|
|
||||||
|
settings, err := dataStore.Settings().Settings()
|
||||||
|
if err != nil {
|
||||||
|
logrus.Fatal(err)
|
||||||
|
}
|
||||||
|
jwtService, err := initJWTService(settings.UserSessionTimeout, dataStore)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Fatalf("Failed initializing JWT service: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = enableFeaturesFromFlags(dataStore, flags)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Fatalf("Failed enabling feature flag: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ldapService := initLDAPService()
|
ldapService := initLDAPService()
|
||||||
|
|
||||||
oauthService := initOAuthService()
|
oauthService := initOAuthService()
|
||||||
|
|
||||||
gitService := initGitService()
|
gitService := initGitService()
|
||||||
|
|
||||||
cryptoService := initCryptoService()
|
openAMTService := openamt.NewService()
|
||||||
|
|
||||||
|
cryptoService := initCryptoService()
|
||||||
digitalSignatureService := initDigitalSignatureService()
|
digitalSignatureService := initDigitalSignatureService()
|
||||||
|
|
||||||
sslService, err := initSSLService(*flags.AddrHTTPS, *flags.Data, *flags.SSLCert, *flags.SSLKey, fileService, dataStore, shutdownTrigger)
|
sslService, err := initSSLService(*flags.AddrHTTPS, *flags.Data, *flags.SSLCert, *flags.SSLKey, fileService, dataStore, shutdownTrigger)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
logrus.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
sslSettings, err := sslService.GetSSLSettings()
|
sslSettings, err := sslService.GetSSLSettings()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed to get ssl settings: %s", err)
|
logrus.Fatalf("Failed to get ssl settings: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = initKeyPair(fileService, digitalSignatureService)
|
err = initKeyPair(fileService, digitalSignatureService)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed initializing key pai: %v", err)
|
logrus.Fatalf("Failed initializing key pair: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
reverseTunnelService := chisel.NewService(dataStore, shutdownCtx)
|
reverseTunnelService := chisel.NewService(dataStore, shutdownCtx)
|
||||||
|
|
||||||
instanceID, err := dataStore.Version().InstanceID()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("failed getting instance id: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
dockerClientFactory := initDockerClientFactory(digitalSignatureService, reverseTunnelService)
|
dockerClientFactory := initDockerClientFactory(digitalSignatureService, reverseTunnelService)
|
||||||
kubernetesClientFactory := initKubernetesClientFactory(digitalSignatureService, reverseTunnelService, instanceID, dataStore)
|
kubernetesClientFactory := initKubernetesClientFactory(digitalSignatureService, reverseTunnelService, instanceID, dataStore)
|
||||||
|
|
||||||
snapshotService, err := initSnapshotService(*flags.SnapshotInterval, dataStore, dockerClientFactory, kubernetesClientFactory, shutdownCtx)
|
snapshotService, err := initSnapshotService(*flags.SnapshotInterval, dataStore, dockerClientFactory, kubernetesClientFactory, shutdownCtx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed initializing snapshot service: %v", err)
|
logrus.Fatalf("Failed initializing snapshot service: %v", err)
|
||||||
}
|
}
|
||||||
snapshotService.Start()
|
snapshotService.Start()
|
||||||
|
|
||||||
@@ -518,51 +588,39 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
|
|||||||
|
|
||||||
composeStackManager := initComposeStackManager(*flags.Assets, dockerConfigPath, reverseTunnelService, proxyManager)
|
composeStackManager := initComposeStackManager(*flags.Assets, dockerConfigPath, reverseTunnelService, proxyManager)
|
||||||
|
|
||||||
swarmStackManager, err := initSwarmStackManager(*flags.Assets, dockerConfigPath, digitalSignatureService, fileService, reverseTunnelService)
|
swarmStackManager, err := initSwarmStackManager(*flags.Assets, dockerConfigPath, digitalSignatureService, fileService, reverseTunnelService, dataStore)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed initializing swarm stack manager: %s", err)
|
logrus.Fatalf("Failed initializing swarm stack manager: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
kubernetesDeployer := initKubernetesDeployer(kubernetesTokenCacheManager, kubernetesClientFactory, dataStore, reverseTunnelService, digitalSignatureService, proxyManager, *flags.Assets)
|
kubernetesDeployer := initKubernetesDeployer(kubernetesTokenCacheManager, kubernetesClientFactory, dataStore, reverseTunnelService, digitalSignatureService, proxyManager, *flags.Assets)
|
||||||
|
|
||||||
helmPackageManager, err := initHelmPackageManager(*flags.Assets)
|
helmPackageManager, err := initHelmPackageManager(*flags.Assets)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed initializing helm package manager: %s", err)
|
logrus.Fatalf("Failed initializing helm package manager: %v", err)
|
||||||
}
|
|
||||||
|
|
||||||
if dataStore.IsNew() {
|
|
||||||
err = updateSettingsFromFlags(dataStore, flags)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("failed updating settings from flags: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err = enableFeaturesFromFlags(dataStore, flags)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("failed enabling feature flag: %v", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = edge.LoadEdgeJobs(dataStore, reverseTunnelService)
|
err = edge.LoadEdgeJobs(dataStore, reverseTunnelService)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed loading edge jobs from database: %v", err)
|
logrus.Fatalf("Failed loading edge jobs from database: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
applicationStatus := initStatus(instanceID)
|
applicationStatus := initStatus(instanceID)
|
||||||
|
|
||||||
err = initEndpoint(flags, dataStore, snapshotService)
|
err = initEndpoint(flags, dataStore, snapshotService)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed initializing environment: %v", err)
|
logrus.Fatalf("Failed initializing environment: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
adminPasswordHash := ""
|
adminPasswordHash := ""
|
||||||
if *flags.AdminPasswordFile != "" {
|
if *flags.AdminPasswordFile != "" {
|
||||||
content, err := fileService.GetFileContent(*flags.AdminPasswordFile, "")
|
content, err := fileService.GetFileContent(*flags.AdminPasswordFile, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed getting admin password file: %v", err)
|
logrus.Fatalf("Failed getting admin password file: %v", err)
|
||||||
}
|
}
|
||||||
adminPasswordHash, err = cryptoService.Hash(strings.TrimSuffix(string(content), "\n"))
|
adminPasswordHash, err = cryptoService.Hash(strings.TrimSuffix(string(content), "\n"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed hashing admin password: %v", err)
|
logrus.Fatalf("Failed hashing admin password: %v", err)
|
||||||
}
|
}
|
||||||
} else if *flags.AdminPassword != "" {
|
} else if *flags.AdminPassword != "" {
|
||||||
adminPasswordHash = *flags.AdminPassword
|
adminPasswordHash = *flags.AdminPassword
|
||||||
@@ -571,33 +629,33 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
|
|||||||
if adminPasswordHash != "" {
|
if adminPasswordHash != "" {
|
||||||
users, err := dataStore.User().UsersByRole(portainer.AdministratorRole)
|
users, err := dataStore.User().UsersByRole(portainer.AdministratorRole)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed getting admin user: %v", err)
|
logrus.Fatalf("Failed getting admin user: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(users) == 0 {
|
if len(users) == 0 {
|
||||||
log.Println("Created admin user with the given password.")
|
logrus.Println("Created admin user with the given password.")
|
||||||
user := &portainer.User{
|
user := &portainer.User{
|
||||||
Username: "admin",
|
Username: "admin",
|
||||||
Role: portainer.AdministratorRole,
|
Role: portainer.AdministratorRole,
|
||||||
Password: adminPasswordHash,
|
Password: adminPasswordHash,
|
||||||
}
|
}
|
||||||
err := dataStore.User().CreateUser(user)
|
err := dataStore.User().Create(user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed creating admin user: %v", err)
|
logrus.Fatalf("Failed creating admin user: %v", err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Println("Instance already has an administrator user defined. Skipping admin password related flags.")
|
logrus.Println("Instance already has an administrator user defined. Skipping admin password related flags.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = reverseTunnelService.StartTunnelServer(*flags.TunnelAddr, *flags.TunnelPort, snapshotService)
|
err = reverseTunnelService.StartTunnelServer(*flags.TunnelAddr, *flags.TunnelPort, snapshotService)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed starting tunnel server: %s", err)
|
logrus.Fatalf("Failed starting tunnel server: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
sslDBSettings, err := dataStore.SSLSettings().Settings()
|
sslDBSettings, err := dataStore.SSLSettings().Settings()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed to fetch ssl settings from DB")
|
logrus.Fatalf("Failed to fetch ssl settings from DB")
|
||||||
}
|
}
|
||||||
|
|
||||||
scheduler := scheduler.NewScheduler(shutdownCtx)
|
scheduler := scheduler.NewScheduler(shutdownCtx)
|
||||||
@@ -618,11 +676,13 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
|
|||||||
KubernetesDeployer: kubernetesDeployer,
|
KubernetesDeployer: kubernetesDeployer,
|
||||||
HelmPackageManager: helmPackageManager,
|
HelmPackageManager: helmPackageManager,
|
||||||
CryptoService: cryptoService,
|
CryptoService: cryptoService,
|
||||||
|
APIKeyService: apiKeyService,
|
||||||
JWTService: jwtService,
|
JWTService: jwtService,
|
||||||
FileService: fileService,
|
FileService: fileService,
|
||||||
LDAPService: ldapService,
|
LDAPService: ldapService,
|
||||||
OAuthService: oauthService,
|
OAuthService: oauthService,
|
||||||
GitService: gitService,
|
GitService: gitService,
|
||||||
|
OpenAMTService: openAMTService,
|
||||||
ProxyManager: proxyManager,
|
ProxyManager: proxyManager,
|
||||||
KubernetesTokenCacheManager: kubernetesTokenCacheManager,
|
KubernetesTokenCacheManager: kubernetesTokenCacheManager,
|
||||||
KubeConfigService: kubeConfigService,
|
KubeConfigService: kubeConfigService,
|
||||||
@@ -635,6 +695,7 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
|
|||||||
ShutdownCtx: shutdownCtx,
|
ShutdownCtx: shutdownCtx,
|
||||||
ShutdownTrigger: shutdownTrigger,
|
ShutdownTrigger: shutdownTrigger,
|
||||||
StackDeployer: stackDeployer,
|
StackDeployer: stackDeployer,
|
||||||
|
BaseURL: *flags.BaseURL,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -645,8 +706,8 @@ func main() {
|
|||||||
|
|
||||||
for {
|
for {
|
||||||
server := buildServer(flags)
|
server := buildServer(flags)
|
||||||
log.Printf("[INFO] [cmd,main] Starting Portainer version %s\n", portainer.APIVersion)
|
logrus.Printf("[INFO] [cmd,main] Starting Portainer version %s\n", portainer.APIVersion)
|
||||||
err := server.Start()
|
err := server.Start()
|
||||||
log.Printf("[INFO] [cmd,main] Http server exited: %s\n", err)
|
logrus.Printf("[INFO] [cmd,main] Http server exited: %v\n", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,9 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
portainer "github.com/portainer/portainer/api"
|
portainer "github.com/portainer/portainer/api"
|
||||||
"github.com/portainer/portainer/api/bolt"
|
|
||||||
"github.com/portainer/portainer/api/cli"
|
"github.com/portainer/portainer/api/cli"
|
||||||
|
"github.com/portainer/portainer/api/dataservices"
|
||||||
|
"github.com/portainer/portainer/api/datastore"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"gopkg.in/alecthomas/kingpin.v2"
|
"gopkg.in/alecthomas/kingpin.v2"
|
||||||
)
|
)
|
||||||
@@ -20,7 +21,7 @@ func (m mockKingpinSetting) SetValue(value kingpin.Value) {
|
|||||||
func Test_enableFeaturesFromFlags(t *testing.T) {
|
func Test_enableFeaturesFromFlags(t *testing.T) {
|
||||||
is := assert.New(t)
|
is := assert.New(t)
|
||||||
|
|
||||||
store, teardown := bolt.MustNewTestStore(true)
|
_, store, teardown := datastore.MustNewTestStore(true)
|
||||||
defer teardown()
|
defer teardown()
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
@@ -28,11 +29,6 @@ func Test_enableFeaturesFromFlags(t *testing.T) {
|
|||||||
isSupported bool
|
isSupported bool
|
||||||
}{
|
}{
|
||||||
{"test", false},
|
{"test", false},
|
||||||
{"openamt", false},
|
|
||||||
{"open-amt", true},
|
|
||||||
{"oPeN-amT", true},
|
|
||||||
{"fdo", true},
|
|
||||||
{"FDO", true},
|
|
||||||
}
|
}
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
t.Run(fmt.Sprintf("%s succeeds:%v", test.featureFlag, test.isSupported), func(t *testing.T) {
|
t.Run(fmt.Sprintf("%s succeeds:%v", test.featureFlag, test.isSupported), func(t *testing.T) {
|
||||||
@@ -59,7 +55,7 @@ func Test_enableFeaturesFromFlags(t *testing.T) {
|
|||||||
|
|
||||||
const FeatTest portainer.Feature = "optional-test"
|
const FeatTest portainer.Feature = "optional-test"
|
||||||
|
|
||||||
func optionalFunc(dataStore portainer.DataStore) string {
|
func optionalFunc(dataStore dataservices.DataStore) string {
|
||||||
|
|
||||||
// TODO: this is a code smell - finding out if a feature flag is enabled should not require having access to the store, and asking for a settings obj.
|
// TODO: this is a code smell - finding out if a feature flag is enabled should not require having access to the store, and asking for a settings obj.
|
||||||
// ideally, the `if` should look more like:
|
// ideally, the `if` should look more like:
|
||||||
@@ -80,7 +76,7 @@ func Test_optionalFeature(t *testing.T) {
|
|||||||
|
|
||||||
is := assert.New(t)
|
is := assert.New(t)
|
||||||
|
|
||||||
store, teardown := bolt.MustNewTestStore(true)
|
_, store, teardown := datastore.MustNewTestStore(true)
|
||||||
defer teardown()
|
defer teardown()
|
||||||
|
|
||||||
// Enable the test feature
|
// Enable the test feature
|
||||||
|
|||||||
36
api/connection.go
Normal file
36
api/connection.go
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
package portainer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Connection interface {
|
||||||
|
Open() error
|
||||||
|
Close() error
|
||||||
|
|
||||||
|
// write the db contents to filename as json (the schema needs defining)
|
||||||
|
ExportRaw(filename string) error
|
||||||
|
|
||||||
|
// TODO: this one is very database specific atm
|
||||||
|
BackupTo(w io.Writer) error
|
||||||
|
GetDatabaseFileName() string
|
||||||
|
GetDatabaseFilePath() string
|
||||||
|
GetStorePath() string
|
||||||
|
|
||||||
|
IsEncryptedStore() bool
|
||||||
|
NeedsEncryptionMigration() (bool, error)
|
||||||
|
SetEncrypted(encrypted bool)
|
||||||
|
|
||||||
|
SetServiceName(bucketName string) error
|
||||||
|
GetObject(bucketName string, key []byte, object interface{}) error
|
||||||
|
UpdateObject(bucketName string, key []byte, object interface{}) error
|
||||||
|
DeleteObject(bucketName string, key []byte) error
|
||||||
|
DeleteAllObjects(bucketName string, matching func(o interface{}) (id int, ok bool)) error
|
||||||
|
GetNextIdentifier(bucketName string) int
|
||||||
|
CreateObject(bucketName string, fn func(uint64) (int, interface{})) error
|
||||||
|
CreateObjectWithId(bucketName string, id int, obj interface{}) error
|
||||||
|
CreateObjectWithSetSequence(bucketName string, id int, obj interface{}) error
|
||||||
|
GetAll(bucketName string, obj interface{}, append func(o interface{}) (interface{}, error)) error
|
||||||
|
GetAllWithJsoniter(bucketName string, obj interface{}, append func(o interface{}) (interface{}, error)) error
|
||||||
|
ConvertToKey(v int) []byte
|
||||||
|
}
|
||||||
378
api/database/boltdb/db.go
Normal file
378
api/database/boltdb/db.go
Normal file
@@ -0,0 +1,378 @@
|
|||||||
|
package boltdb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/boltdb/bolt"
|
||||||
|
dserrors "github.com/portainer/portainer/api/dataservices/errors"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
DatabaseFileName = "portainer.db"
|
||||||
|
EncryptedDatabaseFileName = "portainer.edb"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrHaveEncryptedAndUnencrypted = errors.New("Portainer has detected both an encrypted and un-encrypted database and cannot start. Only one database should exist")
|
||||||
|
ErrHaveEncryptedWithNoKey = errors.New("The portainer database is encrypted, but no secret was loaded")
|
||||||
|
)
|
||||||
|
|
||||||
|
type DbConnection struct {
|
||||||
|
Path string
|
||||||
|
MaxBatchSize int
|
||||||
|
MaxBatchDelay time.Duration
|
||||||
|
InitialMmapSize int
|
||||||
|
EncryptionKey []byte
|
||||||
|
isEncrypted bool
|
||||||
|
|
||||||
|
*bolt.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDatabaseFileName get the database filename
|
||||||
|
func (connection *DbConnection) GetDatabaseFileName() string {
|
||||||
|
if connection.IsEncryptedStore() {
|
||||||
|
return EncryptedDatabaseFileName
|
||||||
|
}
|
||||||
|
|
||||||
|
return DatabaseFileName
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDataseFilePath get the path + filename for the database file
|
||||||
|
func (connection *DbConnection) GetDatabaseFilePath() string {
|
||||||
|
if connection.IsEncryptedStore() {
|
||||||
|
return path.Join(connection.Path, EncryptedDatabaseFileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
return path.Join(connection.Path, DatabaseFileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetStorePath get the filename and path for the database file
|
||||||
|
func (connection *DbConnection) GetStorePath() string {
|
||||||
|
return connection.Path
|
||||||
|
}
|
||||||
|
|
||||||
|
func (connection *DbConnection) SetEncrypted(flag bool) {
|
||||||
|
connection.isEncrypted = flag
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return true if the database is encrypted
|
||||||
|
func (connection *DbConnection) IsEncryptedStore() bool {
|
||||||
|
return connection.getEncryptionKey() != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NeedsEncryptionMigration returns true if database encryption is enabled and
|
||||||
|
// we have an un-encrypted DB that requires migration to an encrypted DB
|
||||||
|
func (connection *DbConnection) NeedsEncryptionMigration() (bool, error) {
|
||||||
|
|
||||||
|
// Cases: Note, we need to check both portainer.db and portainer.edb
|
||||||
|
// to determine if it's a new store. We only need to differentiate between cases 2,3 and 5
|
||||||
|
|
||||||
|
// 1) portainer.edb + key => False
|
||||||
|
// 2) portainer.edb + no key => ERROR Fatal!
|
||||||
|
// 3) portainer.db + key => True (needs migration)
|
||||||
|
// 4) portainer.db + no key => False
|
||||||
|
// 5) NoDB (new) + key => False
|
||||||
|
// 6) NoDB (new) + no key => False
|
||||||
|
// 7) portainer.db & portainer.edb => ERROR Fatal!
|
||||||
|
|
||||||
|
// If we have a loaded encryption key, always set encrypted
|
||||||
|
if connection.EncryptionKey != nil {
|
||||||
|
connection.SetEncrypted(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for portainer.db
|
||||||
|
dbFile := path.Join(connection.Path, DatabaseFileName)
|
||||||
|
_, err := os.Stat(dbFile)
|
||||||
|
haveDbFile := err == nil
|
||||||
|
|
||||||
|
// Check for portainer.edb
|
||||||
|
edbFile := path.Join(connection.Path, EncryptedDatabaseFileName)
|
||||||
|
_, err = os.Stat(edbFile)
|
||||||
|
haveEdbFile := err == nil
|
||||||
|
|
||||||
|
if haveDbFile && haveEdbFile {
|
||||||
|
// 7 - encrypted and unencrypted db?
|
||||||
|
return false, ErrHaveEncryptedAndUnencrypted
|
||||||
|
}
|
||||||
|
|
||||||
|
if haveDbFile && connection.EncryptionKey != nil {
|
||||||
|
// 3 - needs migration
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if haveEdbFile && connection.EncryptionKey == nil {
|
||||||
|
// 2 - encrypted db, but no key?
|
||||||
|
return false, ErrHaveEncryptedWithNoKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1, 4, 5, 6
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open opens and initializes the BoltDB database.
|
||||||
|
func (connection *DbConnection) Open() error {
|
||||||
|
|
||||||
|
logrus.Infof("Loading PortainerDB: %s", connection.GetDatabaseFileName())
|
||||||
|
|
||||||
|
// Now we open the db
|
||||||
|
databasePath := connection.GetDatabaseFilePath()
|
||||||
|
db, err := bolt.Open(databasePath, 0600, &bolt.Options{
|
||||||
|
Timeout: 1 * time.Second,
|
||||||
|
InitialMmapSize: connection.InitialMmapSize,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
db.MaxBatchSize = connection.MaxBatchSize
|
||||||
|
db.MaxBatchDelay = connection.MaxBatchDelay
|
||||||
|
connection.DB = db
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the BoltDB database.
|
||||||
|
// Safe to being called multiple times.
|
||||||
|
func (connection *DbConnection) Close() error {
|
||||||
|
if connection.DB != nil {
|
||||||
|
return connection.DB.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BackupTo backs up db to a provided writer.
|
||||||
|
// It does hot backup and doesn't block other database reads and writes
|
||||||
|
func (connection *DbConnection) BackupTo(w io.Writer) error {
|
||||||
|
return connection.View(func(tx *bolt.Tx) error {
|
||||||
|
_, err := tx.WriteTo(w)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (connection *DbConnection) ExportRaw(filename string) error {
|
||||||
|
databasePath := connection.GetDatabaseFilePath()
|
||||||
|
if _, err := os.Stat(databasePath); err != nil {
|
||||||
|
return fmt.Errorf("stat on %s failed: %s", databasePath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := connection.exportJson(databasePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return ioutil.WriteFile(filename, b, 0600)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConvertToKey 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 (connection *DbConnection) ConvertToKey(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 database database.
|
||||||
|
func (connection *DbConnection) SetServiceName(bucketName string) error {
|
||||||
|
return connection.Batch(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 database database.
|
||||||
|
func (connection *DbConnection) GetObject(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 dserrors.ErrObjectNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
data = make([]byte, len(value))
|
||||||
|
copy(data, value)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return connection.UnmarshalObjectWithJsoniter(data, object)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (connection *DbConnection) getEncryptionKey() []byte {
|
||||||
|
if !connection.isEncrypted {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return connection.EncryptionKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateObject is a generic function used to update an object inside a database database.
|
||||||
|
func (connection *DbConnection) UpdateObject(bucketName string, key []byte, object interface{}) error {
|
||||||
|
data, err := connection.MarshalObject(object)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return connection.Batch(func(tx *bolt.Tx) error {
|
||||||
|
bucket := tx.Bucket([]byte(bucketName))
|
||||||
|
return bucket.Put(key, data)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteObject is a generic function used to delete an object inside a database database.
|
||||||
|
func (connection *DbConnection) DeleteObject(bucketName string, key []byte) error {
|
||||||
|
return connection.Batch(func(tx *bolt.Tx) error {
|
||||||
|
bucket := tx.Bucket([]byte(bucketName))
|
||||||
|
return bucket.Delete(key)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteAllObjects delete all objects where matching() returns (id, ok).
|
||||||
|
// TODO: think about how to return the error inside (maybe change ok to type err, and use "notfound"?
|
||||||
|
func (connection *DbConnection) DeleteAllObjects(bucketName string, matching func(o interface{}) (id int, ok bool)) error {
|
||||||
|
return connection.Batch(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 obj interface{}
|
||||||
|
err := connection.UnmarshalObject(v, &obj)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if id, ok := matching(obj); ok {
|
||||||
|
err := bucket.Delete(connection.ConvertToKey(id))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNextIdentifier is a generic function that returns the specified bucket identifier incremented by 1.
|
||||||
|
func (connection *DbConnection) GetNextIdentifier(bucketName string) int {
|
||||||
|
var identifier int
|
||||||
|
|
||||||
|
connection.Batch(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
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateObject creates a new object in the bucket, using the next bucket sequence id
|
||||||
|
func (connection *DbConnection) CreateObject(bucketName string, fn func(uint64) (int, interface{})) error {
|
||||||
|
return connection.Batch(func(tx *bolt.Tx) error {
|
||||||
|
bucket := tx.Bucket([]byte(bucketName))
|
||||||
|
|
||||||
|
seqId, _ := bucket.NextSequence()
|
||||||
|
id, obj := fn(seqId)
|
||||||
|
|
||||||
|
data, err := connection.MarshalObject(obj)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return bucket.Put(connection.ConvertToKey(int(id)), data)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateObjectWithId creates a new object in the bucket, using the specified id
|
||||||
|
func (connection *DbConnection) CreateObjectWithId(bucketName string, id int, obj interface{}) error {
|
||||||
|
return connection.Batch(func(tx *bolt.Tx) error {
|
||||||
|
bucket := tx.Bucket([]byte(bucketName))
|
||||||
|
data, err := connection.MarshalObject(obj)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return bucket.Put(connection.ConvertToKey(id), data)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateObjectWithSetSequence creates a new object in the bucket, using the specified id, and sets the bucket sequence
|
||||||
|
// avoid this :)
|
||||||
|
func (connection *DbConnection) CreateObjectWithSetSequence(bucketName string, id int, obj interface{}) error {
|
||||||
|
return connection.Batch(func(tx *bolt.Tx) error {
|
||||||
|
bucket := tx.Bucket([]byte(bucketName))
|
||||||
|
|
||||||
|
// We manually manage sequences for schedules
|
||||||
|
err := bucket.SetSequence(uint64(id))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := connection.MarshalObject(obj)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return bucket.Put(connection.ConvertToKey(id), data)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (connection *DbConnection) GetAll(bucketName string, obj interface{}, append func(o interface{}) (interface{}, error)) error {
|
||||||
|
err := 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() {
|
||||||
|
err := connection.UnmarshalObject(v, obj)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
obj, err = append(obj)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: decide which Unmarshal to use, and use one...
|
||||||
|
func (connection *DbConnection) GetAllWithJsoniter(bucketName string, obj interface{}, append func(o interface{}) (interface{}, error)) error {
|
||||||
|
err := 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() {
|
||||||
|
err := connection.UnmarshalObjectWithJsoniter(v, obj)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
obj, err = append(obj)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
124
api/database/boltdb/db_test.go
Normal file
124
api/database/boltdb/db_test.go
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
package boltdb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_NeedsEncryptionMigration(t *testing.T) {
|
||||||
|
// Test the specific scenarios mentioned in NeedsEncryptionMigration
|
||||||
|
|
||||||
|
// i.e.
|
||||||
|
// Cases: Note, we need to check both portainer.db and portainer.edb
|
||||||
|
// to determine if it's a new store. We only need to differentiate between cases 2,3 and 5
|
||||||
|
|
||||||
|
// 1) portainer.edb + key => False
|
||||||
|
// 2) portainer.edb + no key => ERROR Fatal!
|
||||||
|
// 3) portainer.db + key => True (needs migration)
|
||||||
|
// 4) portainer.db + no key => False
|
||||||
|
// 5) NoDB (new) + key => False
|
||||||
|
// 6) NoDB (new) + no key => False
|
||||||
|
// 7) portainer.db & portainer.edb (key not important) => ERROR Fatal!
|
||||||
|
|
||||||
|
is := assert.New(t)
|
||||||
|
dir := t.TempDir()
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
dbname string
|
||||||
|
key bool
|
||||||
|
expectError error
|
||||||
|
expectResult bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "portainer.edb + key",
|
||||||
|
dbname: EncryptedDatabaseFileName,
|
||||||
|
key: true,
|
||||||
|
expectError: nil,
|
||||||
|
expectResult: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "portainer.db + key (migration needed)",
|
||||||
|
dbname: DatabaseFileName,
|
||||||
|
key: true,
|
||||||
|
expectError: nil,
|
||||||
|
expectResult: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "portainer.db + no key",
|
||||||
|
dbname: DatabaseFileName,
|
||||||
|
key: false,
|
||||||
|
expectError: nil,
|
||||||
|
expectResult: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "NoDB (new) + key",
|
||||||
|
dbname: "",
|
||||||
|
key: false,
|
||||||
|
expectError: nil,
|
||||||
|
expectResult: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "NoDB (new) + no key",
|
||||||
|
dbname: "",
|
||||||
|
key: false,
|
||||||
|
expectError: nil,
|
||||||
|
expectResult: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
// error tests
|
||||||
|
{
|
||||||
|
name: "portainer.edb + no key",
|
||||||
|
dbname: EncryptedDatabaseFileName,
|
||||||
|
key: false,
|
||||||
|
expectError: ErrHaveEncryptedWithNoKey,
|
||||||
|
expectResult: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "portainer.db & portainer.edb",
|
||||||
|
dbname: "both",
|
||||||
|
key: true,
|
||||||
|
expectError: ErrHaveEncryptedAndUnencrypted,
|
||||||
|
expectResult: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
tc := tc
|
||||||
|
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
|
||||||
|
connection := DbConnection{Path: dir}
|
||||||
|
|
||||||
|
if tc.dbname == "both" {
|
||||||
|
// Special case. If portainer.db and portainer.edb exist.
|
||||||
|
dbFile1 := path.Join(connection.Path, DatabaseFileName)
|
||||||
|
f, _ := os.Create(dbFile1)
|
||||||
|
f.Close()
|
||||||
|
defer os.Remove(dbFile1)
|
||||||
|
|
||||||
|
dbFile2 := path.Join(connection.Path, EncryptedDatabaseFileName)
|
||||||
|
f, _ = os.Create(dbFile2)
|
||||||
|
f.Close()
|
||||||
|
defer os.Remove(dbFile2)
|
||||||
|
} else if tc.dbname != "" {
|
||||||
|
dbFile := path.Join(connection.Path, tc.dbname)
|
||||||
|
f, _ := os.Create(dbFile)
|
||||||
|
f.Close()
|
||||||
|
defer os.Remove(dbFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.key {
|
||||||
|
connection.EncryptionKey = []byte("secret")
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := connection.NeedsEncryptionMigration()
|
||||||
|
|
||||||
|
is.Equal(tc.expectError, err, "Fatal Error failure. Test: %s", tc.name)
|
||||||
|
is.Equal(result, tc.expectResult, "Failed test: %s", tc.name)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
68
api/database/boltdb/export.go
Normal file
68
api/database/boltdb/export.go
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
package boltdb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/boltdb/bolt"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// inspired by github.com/konoui/boltdb-exporter (which has no license)
|
||||||
|
// but very much simplified, based on how we use boltdb
|
||||||
|
func (c *DbConnection) exportJson(databasePath string) ([]byte, error) {
|
||||||
|
logrus.WithField("databasePath", databasePath).Infof("exportJson")
|
||||||
|
|
||||||
|
connection, err := bolt.Open(databasePath, 0600, &bolt.Options{Timeout: 1 * time.Second, ReadOnly: true})
|
||||||
|
if err != nil {
|
||||||
|
return []byte("{}"), err
|
||||||
|
}
|
||||||
|
defer connection.Close()
|
||||||
|
|
||||||
|
backup := make(map[string]interface{})
|
||||||
|
|
||||||
|
err = connection.View(func(tx *bolt.Tx) error {
|
||||||
|
err = tx.ForEach(func(name []byte, bucket *bolt.Bucket) error {
|
||||||
|
bucketName := string(name)
|
||||||
|
var list []interface{}
|
||||||
|
version := make(map[string]string)
|
||||||
|
cursor := bucket.Cursor()
|
||||||
|
for k, v := cursor.First(); k != nil; k, v = cursor.Next() {
|
||||||
|
if v == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var obj interface{}
|
||||||
|
err := c.UnmarshalObject(v, &obj)
|
||||||
|
if err != nil {
|
||||||
|
logrus.WithError(err).Errorf("Failed to unmarshal (bucket %s): %v", bucketName, string(v))
|
||||||
|
obj = v
|
||||||
|
}
|
||||||
|
if bucketName == "version" {
|
||||||
|
version[string(k)] = string(v)
|
||||||
|
} else {
|
||||||
|
list = append(list, obj)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if bucketName == "version" {
|
||||||
|
backup[bucketName] = version
|
||||||
|
}
|
||||||
|
if len(list) > 0 {
|
||||||
|
if bucketName == "ssl" ||
|
||||||
|
bucketName == "settings" ||
|
||||||
|
bucketName == "tunnel_server" {
|
||||||
|
backup[bucketName] = list[0]
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
backup[bucketName] = list
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return []byte("{}"), err
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.MarshalIndent(backup, "", " ")
|
||||||
|
}
|
||||||
133
api/database/boltdb/json.go
Normal file
133
api/database/boltdb/json.go
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
package boltdb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
jsoniter "github.com/json-iterator/go"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
var errEncryptedStringTooShort = fmt.Errorf("encrypted string too short")
|
||||||
|
|
||||||
|
// MarshalObject encodes an object to binary format
|
||||||
|
func (connection *DbConnection) MarshalObject(object interface{}) (data []byte, err error) {
|
||||||
|
// Special case for the VERSION bucket. Here we're not using json
|
||||||
|
if v, ok := object.(string); ok {
|
||||||
|
data = []byte(v)
|
||||||
|
} else {
|
||||||
|
data, err = json.Marshal(object)
|
||||||
|
if err != nil {
|
||||||
|
return data, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if connection.getEncryptionKey() == nil {
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
return encrypt(data, connection.getEncryptionKey())
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalObject decodes an object from binary data
|
||||||
|
func (connection *DbConnection) UnmarshalObject(data []byte, object interface{}) error {
|
||||||
|
var err error
|
||||||
|
if connection.getEncryptionKey() != nil {
|
||||||
|
data, err = decrypt(data, connection.getEncryptionKey())
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "Failed decrypting object")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
e := json.Unmarshal(data, object)
|
||||||
|
if e != nil {
|
||||||
|
// Special case for the VERSION bucket. Here we're not using json
|
||||||
|
// So we need to return it as a string
|
||||||
|
s, ok := object.(*string)
|
||||||
|
if !ok {
|
||||||
|
return errors.Wrap(err, e.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
*s = string(data)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalObjectWithJsoniter decodes an object from binary data
|
||||||
|
// using the jsoniter library. It is mainly used to accelerate environment(endpoint)
|
||||||
|
// decoding at the moment.
|
||||||
|
func (connection *DbConnection) UnmarshalObjectWithJsoniter(data []byte, object interface{}) error {
|
||||||
|
if connection.getEncryptionKey() != nil {
|
||||||
|
var err error
|
||||||
|
data, err = decrypt(data, connection.getEncryptionKey())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var jsoni = jsoniter.ConfigCompatibleWithStandardLibrary
|
||||||
|
err := jsoni.Unmarshal(data, &object)
|
||||||
|
if err != nil {
|
||||||
|
if s, ok := object.(*string); ok {
|
||||||
|
*s = string(data)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// mmm, don't have a KMS .... aes GCM seems the most likely from
|
||||||
|
// https://gist.github.com/atoponce/07d8d4c833873be2f68c34f9afc5a78a#symmetric-encryption
|
||||||
|
|
||||||
|
func encrypt(plaintext []byte, passphrase []byte) (encrypted []byte, err error) {
|
||||||
|
block, _ := aes.NewCipher(passphrase)
|
||||||
|
gcm, err := cipher.NewGCM(block)
|
||||||
|
if err != nil {
|
||||||
|
return encrypted, err
|
||||||
|
}
|
||||||
|
nonce := make([]byte, gcm.NonceSize())
|
||||||
|
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
|
||||||
|
return encrypted, err
|
||||||
|
}
|
||||||
|
ciphertextByte := gcm.Seal(
|
||||||
|
nonce,
|
||||||
|
nonce,
|
||||||
|
plaintext,
|
||||||
|
nil)
|
||||||
|
return ciphertextByte, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func decrypt(encrypted []byte, passphrase []byte) (plaintextByte []byte, err error) {
|
||||||
|
if string(encrypted) == "false" {
|
||||||
|
return []byte("false"), nil
|
||||||
|
}
|
||||||
|
block, err := aes.NewCipher(passphrase)
|
||||||
|
if err != nil {
|
||||||
|
return encrypted, errors.Wrap(err, "Error creating cypher block")
|
||||||
|
}
|
||||||
|
|
||||||
|
gcm, err := cipher.NewGCM(block)
|
||||||
|
if err != nil {
|
||||||
|
return encrypted, errors.Wrap(err, "Error creating GCM")
|
||||||
|
}
|
||||||
|
|
||||||
|
nonceSize := gcm.NonceSize()
|
||||||
|
if len(encrypted) < nonceSize {
|
||||||
|
return encrypted, errEncryptedStringTooShort
|
||||||
|
}
|
||||||
|
|
||||||
|
nonce, ciphertextByteClean := encrypted[:nonceSize], encrypted[nonceSize:]
|
||||||
|
plaintextByte, err = gcm.Open(
|
||||||
|
nil,
|
||||||
|
nonce,
|
||||||
|
ciphertextByteClean,
|
||||||
|
nil)
|
||||||
|
if err != nil {
|
||||||
|
return encrypted, errors.Wrap(err, "Error decrypting text")
|
||||||
|
}
|
||||||
|
|
||||||
|
return plaintextByte, err
|
||||||
|
}
|
||||||
177
api/database/boltdb/json_test.go
Normal file
177
api/database/boltdb/json_test.go
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
package boltdb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gofrs/uuid"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
jsonobject = `{"LogoURL":"","BlackListedLabels":[],"AuthenticationMethod":1,"LDAPSettings":{"AnonymousMode":true,"ReaderDN":"","URL":"","TLSConfig":{"TLS":false,"TLSSkipVerify":false},"StartTLS":false,"SearchSettings":[{"BaseDN":"","Filter":"","UserNameAttribute":""}],"GroupSearchSettings":[{"GroupBaseDN":"","GroupFilter":"","GroupAttribute":""}],"AutoCreateUsers":true},"OAuthSettings":{"ClientID":"","AccessTokenURI":"","AuthorizationURI":"","ResourceURI":"","RedirectURI":"","UserIdentifier":"","Scopes":"","OAuthAutoCreateUsers":false,"DefaultTeamID":0,"SSO":true,"LogoutURI":"","KubeSecretKey":"j0zLVtY/lAWBk62ByyF0uP80SOXaitsABP0TTJX8MhI="},"OpenAMTConfiguration":{"Enabled":false,"MPSServer":"","MPSUser":"","MPSPassword":"","MPSToken":"","CertFileContent":"","CertFileName":"","CertFilePassword":"","DomainName":""},"FeatureFlagSettings":{},"SnapshotInterval":"5m","TemplatesURL":"https://raw.githubusercontent.com/portainer/templates/master/templates-2.0.json","EdgeAgentCheckinInterval":5,"EnableEdgeComputeFeatures":false,"UserSessionTimeout":"8h","KubeconfigExpiry":"0","EnableTelemetry":true,"HelmRepositoryURL":"https://charts.bitnami.com/bitnami","KubectlShellImage":"portainer/kubectl-shell","DisplayDonationHeader":false,"DisplayExternalContributors":false,"EnableHostManagementFeatures":false,"AllowVolumeBrowserForRegularUsers":false,"AllowBindMountsForRegularUsers":false,"AllowPrivilegedModeForRegularUsers":false,"AllowHostNamespaceForRegularUsers":false,"AllowStackManagementForRegularUsers":false,"AllowDeviceMappingForRegularUsers":false,"AllowContainerCapabilitiesForRegularUsers":false}`
|
||||||
|
passphrase = "my secret key"
|
||||||
|
)
|
||||||
|
|
||||||
|
func secretToEncryptionKey(passphrase string) []byte {
|
||||||
|
hash := sha256.Sum256([]byte(passphrase))
|
||||||
|
return hash[:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_MarshalObjectUnencrypted(t *testing.T) {
|
||||||
|
is := assert.New(t)
|
||||||
|
|
||||||
|
uuid := uuid.Must(uuid.NewV4())
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
object interface{}
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
object: nil,
|
||||||
|
expected: `null`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
object: true,
|
||||||
|
expected: `true`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
object: false,
|
||||||
|
expected: `false`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
object: 123,
|
||||||
|
expected: `123`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
object: "456",
|
||||||
|
expected: "456",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
object: uuid,
|
||||||
|
expected: "\"" + uuid.String() + "\"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
object: uuid.String(),
|
||||||
|
expected: uuid.String(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
object: map[string]interface{}{"key": "value"},
|
||||||
|
expected: `{"key":"value"}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
object: []bool{true, false},
|
||||||
|
expected: `[true,false]`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
object: []int{1, 2, 3},
|
||||||
|
expected: `[1,2,3]`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
object: []string{"1", "2", "3"},
|
||||||
|
expected: `["1","2","3"]`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
object: []map[string]interface{}{{"key1": "value1"}, {"key2": "value2"}},
|
||||||
|
expected: `[{"key1":"value1"},{"key2":"value2"}]`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
object: []interface{}{1, "2", false, map[string]interface{}{"key1": "value1"}},
|
||||||
|
expected: `[1,"2",false,{"key1":"value1"}]`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
conn := DbConnection{}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(fmt.Sprintf("%s -> %s", test.object, test.expected), func(t *testing.T) {
|
||||||
|
data, err := conn.MarshalObject(test.object)
|
||||||
|
is.NoError(err)
|
||||||
|
is.Equal(test.expected, string(data))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_UnMarshalObjectUnencrypted(t *testing.T) {
|
||||||
|
is := assert.New(t)
|
||||||
|
|
||||||
|
// Based on actual data entering and what we expect out of the function
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
object []byte
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
object: []byte(""),
|
||||||
|
expected: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
object: []byte("35"),
|
||||||
|
expected: "35",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// An unmarshalled byte string should return the same without error
|
||||||
|
object: []byte("9ca4a1dd-a439-4593-b386-a7dfdc2e9fc6"),
|
||||||
|
expected: "9ca4a1dd-a439-4593-b386-a7dfdc2e9fc6",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// An un-marshalled json object string should return the same as a string without error also
|
||||||
|
object: []byte(jsonobject),
|
||||||
|
expected: jsonobject,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
conn := DbConnection{}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(fmt.Sprintf("%s -> %s", test.object, test.expected), func(t *testing.T) {
|
||||||
|
var object string
|
||||||
|
err := conn.UnmarshalObject(test.object, &object)
|
||||||
|
is.NoError(err)
|
||||||
|
is.Equal(test.expected, string(object))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_ObjectMarshallingEncrypted(t *testing.T) {
|
||||||
|
is := assert.New(t)
|
||||||
|
|
||||||
|
// Based on actual data entering and what we expect out of the function
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
object []byte
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
object: []byte(""),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
object: []byte("35"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// An unmarshalled byte string should return the same without error
|
||||||
|
object: []byte("9ca4a1dd-a439-4593-b386-a7dfdc2e9fc6"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// An un-marshalled json object string should return the same as a string without error also
|
||||||
|
object: []byte(jsonobject),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
key := secretToEncryptionKey(passphrase)
|
||||||
|
conn := DbConnection{EncryptionKey: key}
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(fmt.Sprintf("%s -> %s", test.object, test.expected), func(t *testing.T) {
|
||||||
|
|
||||||
|
data, err := conn.MarshalObject(test.object)
|
||||||
|
is.NoError(err)
|
||||||
|
|
||||||
|
var object []byte
|
||||||
|
err = conn.UnmarshalObject(data, &object)
|
||||||
|
|
||||||
|
is.NoError(err)
|
||||||
|
is.Equal(test.object, object)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
20
api/database/database.go
Normal file
20
api/database/database.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
portainer "github.com/portainer/portainer/api"
|
||||||
|
"github.com/portainer/portainer/api/database/boltdb"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewDatabase should use config options to return a connection to the requested database
|
||||||
|
func NewDatabase(storeType, storePath string, encryptionKey []byte) (connection portainer.Connection, err error) {
|
||||||
|
switch storeType {
|
||||||
|
case "boltdb":
|
||||||
|
return &boltdb.DbConnection{
|
||||||
|
Path: storePath,
|
||||||
|
EncryptionKey: encryptionKey,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("unknown storage database: %s", storeType)
|
||||||
|
}
|
||||||
119
api/dataservices/apikeyrepository/apikeyrepository.go
Normal file
119
api/dataservices/apikeyrepository/apikeyrepository.go
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
package apikeyrepository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
portainer "github.com/portainer/portainer/api"
|
||||||
|
"github.com/portainer/portainer/api/dataservices/errors"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// BucketName represents the name of the bucket where this service stores data.
|
||||||
|
BucketName = "api_key"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Service represents a service for managing api-key data.
|
||||||
|
type Service struct {
|
||||||
|
connection portainer.Connection
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewService creates a new instance of a service.
|
||||||
|
func NewService(connection portainer.Connection) (*Service, error) {
|
||||||
|
err := connection.SetServiceName(BucketName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Service{
|
||||||
|
connection: connection,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAPIKeysByUserID returns a slice containing all the APIKeys a user has access to.
|
||||||
|
func (service *Service) GetAPIKeysByUserID(userID portainer.UserID) ([]portainer.APIKey, error) {
|
||||||
|
var result = make([]portainer.APIKey, 0)
|
||||||
|
|
||||||
|
err := service.connection.GetAll(
|
||||||
|
BucketName,
|
||||||
|
&portainer.APIKey{},
|
||||||
|
func(obj interface{}) (interface{}, error) {
|
||||||
|
record, ok := obj.(*portainer.APIKey)
|
||||||
|
if !ok {
|
||||||
|
logrus.WithField("obj", obj).Errorf("Failed to convert to APIKey object")
|
||||||
|
return nil, fmt.Errorf("Failed to convert to APIKey object: %s", obj)
|
||||||
|
}
|
||||||
|
if record.UserID == userID {
|
||||||
|
result = append(result, *record)
|
||||||
|
}
|
||||||
|
return &portainer.APIKey{}, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAPIKeyByDigest returns the API key for the associated digest.
|
||||||
|
// Note: there is a 1-to-1 mapping of api-key and digest
|
||||||
|
func (service *Service) GetAPIKeyByDigest(digest []byte) (*portainer.APIKey, error) {
|
||||||
|
var k *portainer.APIKey
|
||||||
|
stop := fmt.Errorf("ok")
|
||||||
|
err := service.connection.GetAll(
|
||||||
|
BucketName,
|
||||||
|
&portainer.APIKey{},
|
||||||
|
func(obj interface{}) (interface{}, error) {
|
||||||
|
key, ok := obj.(*portainer.APIKey)
|
||||||
|
if !ok {
|
||||||
|
logrus.WithField("obj", obj).Errorf("Failed to convert to APIKey object")
|
||||||
|
return nil, fmt.Errorf("Failed to convert to APIKey object: %s", obj)
|
||||||
|
}
|
||||||
|
if bytes.Equal(key.Digest, digest) {
|
||||||
|
k = key
|
||||||
|
return nil, stop
|
||||||
|
}
|
||||||
|
return &portainer.APIKey{}, nil
|
||||||
|
})
|
||||||
|
if err == stop {
|
||||||
|
return k, nil
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
return nil, errors.ErrObjectNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateAPIKey creates a new APIKey object.
|
||||||
|
func (service *Service) CreateAPIKey(record *portainer.APIKey) error {
|
||||||
|
return service.connection.CreateObject(
|
||||||
|
BucketName,
|
||||||
|
func(id uint64) (int, interface{}) {
|
||||||
|
record.ID = portainer.APIKeyID(id)
|
||||||
|
|
||||||
|
return int(record.ID), record
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAPIKey retrieves an existing APIKey object by api key ID.
|
||||||
|
func (service *Service) GetAPIKey(keyID portainer.APIKeyID) (*portainer.APIKey, error) {
|
||||||
|
var key portainer.APIKey
|
||||||
|
identifier := service.connection.ConvertToKey(int(keyID))
|
||||||
|
|
||||||
|
err := service.connection.GetObject(BucketName, identifier, &key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &key, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (service *Service) UpdateAPIKey(key *portainer.APIKey) error {
|
||||||
|
identifier := service.connection.ConvertToKey(int(key.ID))
|
||||||
|
return service.connection.UpdateObject(BucketName, identifier, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (service *Service) DeleteAPIKey(ID portainer.APIKeyID) error {
|
||||||
|
identifier := service.connection.ConvertToKey(int(ID))
|
||||||
|
return service.connection.DeleteObject(BucketName, identifier)
|
||||||
|
}
|
||||||
91
api/dataservices/customtemplate/customtemplate.go
Normal file
91
api/dataservices/customtemplate/customtemplate.go
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
package customtemplate
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
portainer "github.com/portainer/portainer/api"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
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 portainer.Connection
|
||||||
|
}
|
||||||
|
|
||||||
|
func (service *Service) BucketName() string {
|
||||||
|
return BucketName
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewService creates a new instance of a service.
|
||||||
|
func NewService(connection portainer.Connection) (*Service, error) {
|
||||||
|
err := connection.SetServiceName(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.GetAll(
|
||||||
|
BucketName,
|
||||||
|
&portainer.CustomTemplate{},
|
||||||
|
func(obj interface{}) (interface{}, error) {
|
||||||
|
//var tag portainer.Tag
|
||||||
|
customTemplate, ok := obj.(*portainer.CustomTemplate)
|
||||||
|
if !ok {
|
||||||
|
logrus.WithField("obj", obj).Errorf("Failed to convert to CustomTemplate object")
|
||||||
|
return nil, fmt.Errorf("Failed to convert to CustomTemplate object: %s", obj)
|
||||||
|
}
|
||||||
|
customTemplates = append(customTemplates, *customTemplate)
|
||||||
|
return &portainer.CustomTemplate{}, 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 := service.connection.ConvertToKey(int(ID))
|
||||||
|
|
||||||
|
err := service.connection.GetObject(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 := service.connection.ConvertToKey(int(ID))
|
||||||
|
return service.connection.UpdateObject(BucketName, identifier, customTemplate)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteCustomTemplate deletes an custom template.
|
||||||
|
func (service *Service) DeleteCustomTemplate(ID portainer.CustomTemplateID) error {
|
||||||
|
identifier := service.connection.ConvertToKey(int(ID))
|
||||||
|
return service.connection.DeleteObject(BucketName, identifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateCustomTemplate uses the existing id and saves it.
|
||||||
|
// TODO: where does the ID come from, and is it safe?
|
||||||
|
func (service *Service) Create(customTemplate *portainer.CustomTemplate) error {
|
||||||
|
return service.connection.CreateObjectWithId(BucketName, int(customTemplate.ID), customTemplate)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNextIdentifier returns the next identifier for a custom template.
|
||||||
|
func (service *Service) GetNextIdentifier() int {
|
||||||
|
return service.connection.GetNextIdentifier(BucketName)
|
||||||
|
}
|
||||||
@@ -2,7 +2,6 @@ package dockerhub
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
portainer "github.com/portainer/portainer/api"
|
portainer "github.com/portainer/portainer/api"
|
||||||
"github.com/portainer/portainer/api/bolt/internal"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -13,12 +12,16 @@ const (
|
|||||||
|
|
||||||
// Service represents a service for managing Dockerhub data.
|
// Service represents a service for managing Dockerhub data.
|
||||||
type Service struct {
|
type Service struct {
|
||||||
connection *internal.DbConnection
|
connection portainer.Connection
|
||||||
|
}
|
||||||
|
|
||||||
|
func (service *Service) BucketName() string {
|
||||||
|
return BucketName
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewService creates a new instance of a service.
|
// NewService creates a new instance of a service.
|
||||||
func NewService(connection *internal.DbConnection) (*Service, error) {
|
func NewService(connection portainer.Connection) (*Service, error) {
|
||||||
err := internal.CreateBucket(connection, BucketName)
|
err := connection.SetServiceName(BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -32,7 +35,7 @@ func NewService(connection *internal.DbConnection) (*Service, error) {
|
|||||||
func (service *Service) DockerHub() (*portainer.DockerHub, error) {
|
func (service *Service) DockerHub() (*portainer.DockerHub, error) {
|
||||||
var dockerhub portainer.DockerHub
|
var dockerhub portainer.DockerHub
|
||||||
|
|
||||||
err := internal.GetObject(service.connection, BucketName, []byte(dockerHubKey), &dockerhub)
|
err := service.connection.GetObject(BucketName, []byte(dockerHubKey), &dockerhub)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -42,5 +45,5 @@ func (service *Service) DockerHub() (*portainer.DockerHub, error) {
|
|||||||
|
|
||||||
// UpdateDockerHub updates a DockerHub object.
|
// UpdateDockerHub updates a DockerHub object.
|
||||||
func (service *Service) UpdateDockerHub(dockerhub *portainer.DockerHub) error {
|
func (service *Service) UpdateDockerHub(dockerhub *portainer.DockerHub) error {
|
||||||
return internal.UpdateObject(service.connection, BucketName, []byte(dockerHubKey), dockerhub)
|
return service.connection.UpdateObject(BucketName, []byte(dockerHubKey), dockerhub)
|
||||||
}
|
}
|
||||||
90
api/dataservices/edgegroup/edgegroup.go
Normal file
90
api/dataservices/edgegroup/edgegroup.go
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
package edgegroup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
portainer "github.com/portainer/portainer/api"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
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 portainer.Connection
|
||||||
|
}
|
||||||
|
|
||||||
|
func (service *Service) BucketName() string {
|
||||||
|
return BucketName
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewService creates a new instance of a service.
|
||||||
|
func NewService(connection portainer.Connection) (*Service, error) {
|
||||||
|
err := connection.SetServiceName(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.GetAllWithJsoniter(
|
||||||
|
BucketName,
|
||||||
|
&portainer.EdgeGroup{},
|
||||||
|
func(obj interface{}) (interface{}, error) {
|
||||||
|
group, ok := obj.(*portainer.EdgeGroup)
|
||||||
|
if !ok {
|
||||||
|
logrus.WithField("obj", obj).Errorf("Failed to convert to EdgeGroup object")
|
||||||
|
return nil, fmt.Errorf("Failed to convert to EdgeGroup object: %s", obj)
|
||||||
|
}
|
||||||
|
groups = append(groups, *group)
|
||||||
|
return &portainer.EdgeGroup{}, 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 := service.connection.ConvertToKey(int(ID))
|
||||||
|
|
||||||
|
err := service.connection.GetObject(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 := service.connection.ConvertToKey(int(ID))
|
||||||
|
return service.connection.UpdateObject(BucketName, identifier, group)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteEdgeGroup deletes an Edge group.
|
||||||
|
func (service *Service) DeleteEdgeGroup(ID portainer.EdgeGroupID) error {
|
||||||
|
identifier := service.connection.ConvertToKey(int(ID))
|
||||||
|
return service.connection.DeleteObject(BucketName, identifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateEdgeGroup assign an ID to a new Edge group and saves it.
|
||||||
|
func (service *Service) Create(group *portainer.EdgeGroup) error {
|
||||||
|
return service.connection.CreateObject(
|
||||||
|
BucketName,
|
||||||
|
func(id uint64) (int, interface{}) {
|
||||||
|
group.ID = portainer.EdgeGroupID(id)
|
||||||
|
return int(group.ID), group
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
96
api/dataservices/edgejob/edgejob.go
Normal file
96
api/dataservices/edgejob/edgejob.go
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
package edgejob
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
portainer "github.com/portainer/portainer/api"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
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 portainer.Connection
|
||||||
|
}
|
||||||
|
|
||||||
|
func (service *Service) BucketName() string {
|
||||||
|
return BucketName
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewService creates a new instance of a service.
|
||||||
|
func NewService(connection portainer.Connection) (*Service, error) {
|
||||||
|
err := connection.SetServiceName(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.GetAll(
|
||||||
|
BucketName,
|
||||||
|
&portainer.EdgeJob{},
|
||||||
|
func(obj interface{}) (interface{}, error) {
|
||||||
|
//var tag portainer.Tag
|
||||||
|
job, ok := obj.(*portainer.EdgeJob)
|
||||||
|
if !ok {
|
||||||
|
logrus.WithField("obj", obj).Errorf("Failed to convert to EdgeJob object")
|
||||||
|
return nil, fmt.Errorf("Failed to convert to EdgeJob object: %s", obj)
|
||||||
|
}
|
||||||
|
edgeJobs = append(edgeJobs, *job)
|
||||||
|
return &portainer.EdgeJob{}, 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 := service.connection.ConvertToKey(int(ID))
|
||||||
|
|
||||||
|
err := service.connection.GetObject(BucketName, identifier, &edgeJob)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &edgeJob, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateEdgeJob creates a new Edge job
|
||||||
|
func (service *Service) Create(edgeJob *portainer.EdgeJob) error {
|
||||||
|
return service.connection.CreateObject(
|
||||||
|
BucketName,
|
||||||
|
func(id uint64) (int, interface{}) {
|
||||||
|
edgeJob.ID = portainer.EdgeJobID(id)
|
||||||
|
return int(edgeJob.ID), edgeJob
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateEdgeJob updates an Edge job by ID
|
||||||
|
func (service *Service) UpdateEdgeJob(ID portainer.EdgeJobID, edgeJob *portainer.EdgeJob) error {
|
||||||
|
identifier := service.connection.ConvertToKey(int(ID))
|
||||||
|
return service.connection.UpdateObject(BucketName, identifier, edgeJob)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteEdgeJob deletes an Edge job
|
||||||
|
func (service *Service) DeleteEdgeJob(ID portainer.EdgeJobID) error {
|
||||||
|
identifier := service.connection.ConvertToKey(int(ID))
|
||||||
|
return service.connection.DeleteObject(BucketName, identifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNextIdentifier returns the next identifier for an environment(endpoint).
|
||||||
|
func (service *Service) GetNextIdentifier() int {
|
||||||
|
return service.connection.GetNextIdentifier(BucketName)
|
||||||
|
}
|
||||||
97
api/dataservices/edgestack/edgestack.go
Normal file
97
api/dataservices/edgestack/edgestack.go
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
package edgestack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
portainer "github.com/portainer/portainer/api"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
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 portainer.Connection
|
||||||
|
}
|
||||||
|
|
||||||
|
func (service *Service) BucketName() string {
|
||||||
|
return BucketName
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewService creates a new instance of a service.
|
||||||
|
func NewService(connection portainer.Connection) (*Service, error) {
|
||||||
|
err := connection.SetServiceName(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.GetAll(
|
||||||
|
BucketName,
|
||||||
|
&portainer.EdgeStack{},
|
||||||
|
func(obj interface{}) (interface{}, error) {
|
||||||
|
//var tag portainer.Tag
|
||||||
|
stack, ok := obj.(*portainer.EdgeStack)
|
||||||
|
if !ok {
|
||||||
|
logrus.WithField("obj", obj).Errorf("Failed to convert to EdgeStack object")
|
||||||
|
return nil, fmt.Errorf("Failed to convert to EdgeStack object: %s", obj)
|
||||||
|
}
|
||||||
|
stacks = append(stacks, *stack)
|
||||||
|
return &portainer.EdgeStack{}, 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 := service.connection.ConvertToKey(int(ID))
|
||||||
|
|
||||||
|
err := service.connection.GetObject(BucketName, identifier, &stack)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &stack, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateEdgeStack saves an Edge stack object to db.
|
||||||
|
func (service *Service) Create(id portainer.EdgeStackID, edgeStack *portainer.EdgeStack) error {
|
||||||
|
|
||||||
|
edgeStack.ID = id
|
||||||
|
|
||||||
|
return service.connection.CreateObjectWithId(
|
||||||
|
BucketName,
|
||||||
|
int(edgeStack.ID),
|
||||||
|
edgeStack,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateEdgeStack updates an Edge stack.
|
||||||
|
func (service *Service) UpdateEdgeStack(ID portainer.EdgeStackID, edgeStack *portainer.EdgeStack) error {
|
||||||
|
identifier := service.connection.ConvertToKey(int(ID))
|
||||||
|
return service.connection.UpdateObject(BucketName, identifier, edgeStack)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteEdgeStack deletes an Edge stack.
|
||||||
|
func (service *Service) DeleteEdgeStack(ID portainer.EdgeStackID) error {
|
||||||
|
identifier := service.connection.ConvertToKey(int(ID))
|
||||||
|
return service.connection.DeleteObject(BucketName, identifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNextIdentifier returns the next identifier for an environment(endpoint).
|
||||||
|
func (service *Service) GetNextIdentifier() int {
|
||||||
|
return service.connection.GetNextIdentifier(BucketName)
|
||||||
|
}
|
||||||
89
api/dataservices/endpoint/endpoint.go
Normal file
89
api/dataservices/endpoint/endpoint.go
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
package endpoint
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
portainer "github.com/portainer/portainer/api"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// BucketName represents the name of the bucket where this service stores data.
|
||||||
|
BucketName = "endpoints"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Service represents a service for managing environment(endpoint) data.
|
||||||
|
type Service struct {
|
||||||
|
connection portainer.Connection
|
||||||
|
}
|
||||||
|
|
||||||
|
func (service *Service) BucketName() string {
|
||||||
|
return BucketName
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewService creates a new instance of a service.
|
||||||
|
func NewService(connection portainer.Connection) (*Service, error) {
|
||||||
|
err := connection.SetServiceName(BucketName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Service{
|
||||||
|
connection: connection,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Endpoint returns an environment(endpoint) by ID.
|
||||||
|
func (service *Service) Endpoint(ID portainer.EndpointID) (*portainer.Endpoint, error) {
|
||||||
|
var endpoint portainer.Endpoint
|
||||||
|
identifier := service.connection.ConvertToKey(int(ID))
|
||||||
|
|
||||||
|
err := service.connection.GetObject(BucketName, identifier, &endpoint)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &endpoint, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateEndpoint updates an environment(endpoint).
|
||||||
|
func (service *Service) UpdateEndpoint(ID portainer.EndpointID, endpoint *portainer.Endpoint) error {
|
||||||
|
identifier := service.connection.ConvertToKey(int(ID))
|
||||||
|
return service.connection.UpdateObject(BucketName, identifier, endpoint)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteEndpoint deletes an environment(endpoint).
|
||||||
|
func (service *Service) DeleteEndpoint(ID portainer.EndpointID) error {
|
||||||
|
identifier := service.connection.ConvertToKey(int(ID))
|
||||||
|
return service.connection.DeleteObject(BucketName, identifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Endpoints return an array containing all the environments(endpoints).
|
||||||
|
func (service *Service) Endpoints() ([]portainer.Endpoint, error) {
|
||||||
|
var endpoints = make([]portainer.Endpoint, 0)
|
||||||
|
|
||||||
|
err := service.connection.GetAllWithJsoniter(
|
||||||
|
BucketName,
|
||||||
|
&portainer.Endpoint{},
|
||||||
|
func(obj interface{}) (interface{}, error) {
|
||||||
|
endpoint, ok := obj.(*portainer.Endpoint)
|
||||||
|
if !ok {
|
||||||
|
logrus.WithField("obj", obj).Errorf("Failed to convert to Endpoint object")
|
||||||
|
return nil, fmt.Errorf("failed to convert to Endpoint object: %s", obj)
|
||||||
|
}
|
||||||
|
endpoints = append(endpoints, *endpoint)
|
||||||
|
return &portainer.Endpoint{}, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return endpoints, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateEndpoint assign an ID to a new environment(endpoint) and saves it.
|
||||||
|
func (service *Service) Create(endpoint *portainer.Endpoint) error {
|
||||||
|
return service.connection.CreateObjectWithSetSequence(BucketName, int(endpoint.ID), endpoint)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNextIdentifier returns the next identifier for an environment(endpoint).
|
||||||
|
func (service *Service) GetNextIdentifier() int {
|
||||||
|
return service.connection.GetNextIdentifier(BucketName)
|
||||||
|
}
|
||||||
91
api/dataservices/endpointgroup/endpointgroup.go
Normal file
91
api/dataservices/endpointgroup/endpointgroup.go
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
package endpointgroup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
portainer "github.com/portainer/portainer/api"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// BucketName represents the name of the bucket where this service stores data.
|
||||||
|
BucketName = "endpoint_groups"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Service represents a service for managing environment(endpoint) data.
|
||||||
|
type Service struct {
|
||||||
|
connection portainer.Connection
|
||||||
|
}
|
||||||
|
|
||||||
|
func (service *Service) BucketName() string {
|
||||||
|
return BucketName
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewService creates a new instance of a service.
|
||||||
|
func NewService(connection portainer.Connection) (*Service, error) {
|
||||||
|
err := connection.SetServiceName(BucketName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Service{
|
||||||
|
connection: connection,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EndpointGroup returns an environment(endpoint) group by ID.
|
||||||
|
func (service *Service) EndpointGroup(ID portainer.EndpointGroupID) (*portainer.EndpointGroup, error) {
|
||||||
|
var endpointGroup portainer.EndpointGroup
|
||||||
|
identifier := service.connection.ConvertToKey(int(ID))
|
||||||
|
|
||||||
|
err := service.connection.GetObject(BucketName, identifier, &endpointGroup)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &endpointGroup, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateEndpointGroup updates an environment(endpoint) group.
|
||||||
|
func (service *Service) UpdateEndpointGroup(ID portainer.EndpointGroupID, endpointGroup *portainer.EndpointGroup) error {
|
||||||
|
identifier := service.connection.ConvertToKey(int(ID))
|
||||||
|
return service.connection.UpdateObject(BucketName, identifier, endpointGroup)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteEndpointGroup deletes an environment(endpoint) group.
|
||||||
|
func (service *Service) DeleteEndpointGroup(ID portainer.EndpointGroupID) error {
|
||||||
|
identifier := service.connection.ConvertToKey(int(ID))
|
||||||
|
return service.connection.DeleteObject(BucketName, identifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EndpointGroups return an array containing all the environment(endpoint) groups.
|
||||||
|
func (service *Service) EndpointGroups() ([]portainer.EndpointGroup, error) {
|
||||||
|
var endpointGroups = make([]portainer.EndpointGroup, 0)
|
||||||
|
|
||||||
|
err := service.connection.GetAll(
|
||||||
|
BucketName,
|
||||||
|
&portainer.EndpointGroup{},
|
||||||
|
func(obj interface{}) (interface{}, error) {
|
||||||
|
//var tag portainer.Tag
|
||||||
|
endpointGroup, ok := obj.(*portainer.EndpointGroup)
|
||||||
|
if !ok {
|
||||||
|
logrus.WithField("obj", obj).Errorf("Failed to convert to EndpointGroup object")
|
||||||
|
return nil, fmt.Errorf("Failed to convert to EndpointGroup object: %s", obj)
|
||||||
|
}
|
||||||
|
endpointGroups = append(endpointGroups, *endpointGroup)
|
||||||
|
return &portainer.EndpointGroup{}, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return endpointGroups, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateEndpointGroup assign an ID to a new environment(endpoint) group and saves it.
|
||||||
|
func (service *Service) Create(endpointGroup *portainer.EndpointGroup) error {
|
||||||
|
return service.connection.CreateObject(
|
||||||
|
BucketName,
|
||||||
|
func(id uint64) (int, interface{}) {
|
||||||
|
endpointGroup.ID = portainer.EndpointGroupID(id)
|
||||||
|
return int(endpointGroup.ID), endpointGroup
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
84
api/dataservices/endpointrelation/endpointrelation.go
Normal file
84
api/dataservices/endpointrelation/endpointrelation.go
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
package endpointrelation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
portainer "github.com/portainer/portainer/api"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// BucketName represents the name of the bucket where this service stores data.
|
||||||
|
BucketName = "endpoint_relations"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Service represents a service for managing environment(endpoint) relation data.
|
||||||
|
type Service struct {
|
||||||
|
connection portainer.Connection
|
||||||
|
}
|
||||||
|
|
||||||
|
func (service *Service) BucketName() string {
|
||||||
|
return BucketName
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewService creates a new instance of a service.
|
||||||
|
func NewService(connection portainer.Connection) (*Service, error) {
|
||||||
|
err := connection.SetServiceName(BucketName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Service{
|
||||||
|
connection: connection,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//EndpointRelations returns an array of all EndpointRelations
|
||||||
|
func (service *Service) EndpointRelations() ([]portainer.EndpointRelation, error) {
|
||||||
|
var all = make([]portainer.EndpointRelation, 0)
|
||||||
|
|
||||||
|
err := service.connection.GetAll(
|
||||||
|
BucketName,
|
||||||
|
&portainer.EndpointRelation{},
|
||||||
|
func(obj interface{}) (interface{}, error) {
|
||||||
|
r, ok := obj.(*portainer.EndpointRelation)
|
||||||
|
if !ok {
|
||||||
|
logrus.WithField("obj", obj).Errorf("Failed to convert to EndpointRelation object")
|
||||||
|
return nil, fmt.Errorf("Failed to convert to EndpointRelation object: %s", obj)
|
||||||
|
}
|
||||||
|
all = append(all, *r)
|
||||||
|
return &portainer.EndpointRelation{}, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return all, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// EndpointRelation returns a Environment(Endpoint) relation object by EndpointID
|
||||||
|
func (service *Service) EndpointRelation(endpointID portainer.EndpointID) (*portainer.EndpointRelation, error) {
|
||||||
|
var endpointRelation portainer.EndpointRelation
|
||||||
|
identifier := service.connection.ConvertToKey(int(endpointID))
|
||||||
|
|
||||||
|
err := service.connection.GetObject(BucketName, identifier, &endpointRelation)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &endpointRelation, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateEndpointRelation saves endpointRelation
|
||||||
|
func (service *Service) Create(endpointRelation *portainer.EndpointRelation) error {
|
||||||
|
return service.connection.CreateObjectWithId(BucketName, int(endpointRelation.EndpointID), endpointRelation)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateEndpointRelation updates an Environment(Endpoint) relation object
|
||||||
|
func (service *Service) UpdateEndpointRelation(EndpointID portainer.EndpointID, endpointRelation *portainer.EndpointRelation) error {
|
||||||
|
identifier := service.connection.ConvertToKey(int(EndpointID))
|
||||||
|
return service.connection.UpdateObject(BucketName, identifier, endpointRelation)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteEndpointRelation deletes an Environment(Endpoint) relation object
|
||||||
|
func (service *Service) DeleteEndpointRelation(EndpointID portainer.EndpointID) error {
|
||||||
|
identifier := service.connection.ConvertToKey(int(EndpointID))
|
||||||
|
return service.connection.DeleteObject(BucketName, identifier)
|
||||||
|
}
|
||||||
10
api/dataservices/errors/errors.go
Normal file
10
api/dataservices/errors/errors.go
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package errors
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
var (
|
||||||
|
// TODO: i'm pretty sure this needs wrapping at several levels
|
||||||
|
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/")
|
||||||
|
ErrDBImportFailed = errors.New("importing backup failed")
|
||||||
|
)
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
package extension
|
package extension
|
||||||
|
|
||||||
import (
|
import (
|
||||||
portainer "github.com/portainer/portainer/api"
|
"fmt"
|
||||||
"github.com/portainer/portainer/api/bolt/internal"
|
|
||||||
|
|
||||||
"github.com/boltdb/bolt"
|
portainer "github.com/portainer/portainer/api"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -14,12 +14,16 @@ const (
|
|||||||
|
|
||||||
// Service represents a service for managing environment(endpoint) data.
|
// Service represents a service for managing environment(endpoint) data.
|
||||||
type Service struct {
|
type Service struct {
|
||||||
connection *internal.DbConnection
|
connection portainer.Connection
|
||||||
|
}
|
||||||
|
|
||||||
|
func (service *Service) BucketName() string {
|
||||||
|
return BucketName
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewService creates a new instance of a service.
|
// NewService creates a new instance of a service.
|
||||||
func NewService(connection *internal.DbConnection) (*Service, error) {
|
func NewService(connection portainer.Connection) (*Service, error) {
|
||||||
err := internal.CreateBucket(connection, BucketName)
|
err := connection.SetServiceName(BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -32,9 +36,9 @@ func NewService(connection *internal.DbConnection) (*Service, error) {
|
|||||||
// Extension returns a extension by ID
|
// Extension returns a extension by ID
|
||||||
func (service *Service) Extension(ID portainer.ExtensionID) (*portainer.Extension, error) {
|
func (service *Service) Extension(ID portainer.ExtensionID) (*portainer.Extension, error) {
|
||||||
var extension portainer.Extension
|
var extension portainer.Extension
|
||||||
identifier := internal.Itob(int(ID))
|
identifier := service.connection.ConvertToKey(int(ID))
|
||||||
|
|
||||||
err := internal.GetObject(service.connection, BucketName, identifier, &extension)
|
err := service.connection.GetObject(BucketName, identifier, &extension)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -46,41 +50,29 @@ func (service *Service) Extension(ID portainer.ExtensionID) (*portainer.Extensio
|
|||||||
func (service *Service) Extensions() ([]portainer.Extension, error) {
|
func (service *Service) Extensions() ([]portainer.Extension, error) {
|
||||||
var extensions = make([]portainer.Extension, 0)
|
var extensions = make([]portainer.Extension, 0)
|
||||||
|
|
||||||
err := service.connection.View(func(tx *bolt.Tx) error {
|
err := service.connection.GetAll(
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
BucketName,
|
||||||
|
&portainer.Extension{},
|
||||||
cursor := bucket.Cursor()
|
func(obj interface{}) (interface{}, error) {
|
||||||
for k, v := cursor.First(); k != nil; k, v = cursor.Next() {
|
extension, ok := obj.(*portainer.Extension)
|
||||||
var extension portainer.Extension
|
if !ok {
|
||||||
err := internal.UnmarshalObject(v, &extension)
|
logrus.WithField("obj", obj).Errorf("Failed to convert to Extension object")
|
||||||
if err != nil {
|
return nil, fmt.Errorf("Failed to convert to Extension object: %s", obj)
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
extensions = append(extensions, extension)
|
extensions = append(extensions, *extension)
|
||||||
}
|
return &portainer.Extension{}, nil
|
||||||
|
})
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
return extensions, err
|
return extensions, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Persist persists a extension inside the database.
|
// Persist persists a extension inside the database.
|
||||||
func (service *Service) Persist(extension *portainer.Extension) error {
|
func (service *Service) Persist(extension *portainer.Extension) error {
|
||||||
return service.connection.Update(func(tx *bolt.Tx) error {
|
return service.connection.CreateObjectWithId(BucketName, int(extension.ID), extension)
|
||||||
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.
|
// DeleteExtension deletes a Extension.
|
||||||
func (service *Service) DeleteExtension(ID portainer.ExtensionID) error {
|
func (service *Service) DeleteExtension(ID portainer.ExtensionID) error {
|
||||||
identifier := internal.Itob(int(ID))
|
identifier := service.connection.ConvertToKey(int(ID))
|
||||||
return internal.DeleteObject(service.connection, BucketName, identifier)
|
return service.connection.DeleteObject(BucketName, identifier)
|
||||||
}
|
}
|
||||||
92
api/dataservices/fdoprofile/fdoprofile.go
Normal file
92
api/dataservices/fdoprofile/fdoprofile.go
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
package fdoprofile
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
portainer "github.com/portainer/portainer/api"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// BucketName represents the name of the bucket where this service stores data.
|
||||||
|
BucketName = "fdo_profiles"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Service represents a service for managingFDO Profiles data.
|
||||||
|
type Service struct {
|
||||||
|
connection portainer.Connection
|
||||||
|
}
|
||||||
|
|
||||||
|
func (service *Service) BucketName() string {
|
||||||
|
return BucketName
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewService creates a new instance of a service.
|
||||||
|
func NewService(connection portainer.Connection) (*Service, error) {
|
||||||
|
err := connection.SetServiceName(BucketName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Service{
|
||||||
|
connection: connection,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FDOProfiles return an array containing all the FDO Profiles.
|
||||||
|
func (service *Service) FDOProfiles() ([]portainer.FDOProfile, error) {
|
||||||
|
var fdoProfiles = make([]portainer.FDOProfile, 0)
|
||||||
|
|
||||||
|
err := service.connection.GetAll(
|
||||||
|
BucketName,
|
||||||
|
&portainer.FDOProfile{},
|
||||||
|
func(obj interface{}) (interface{}, error) {
|
||||||
|
fdoProfile, ok := obj.(*portainer.FDOProfile)
|
||||||
|
if !ok {
|
||||||
|
logrus.WithField("obj", obj).Errorf("Failed to convert to FDOProfile object")
|
||||||
|
return nil, fmt.Errorf("failed to convert to FDOProfile object: %s", obj)
|
||||||
|
}
|
||||||
|
fdoProfiles = append(fdoProfiles, *fdoProfile)
|
||||||
|
return &portainer.FDOProfile{}, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return fdoProfiles, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// FDOProfile returns an FDO Profile by ID.
|
||||||
|
func (service *Service) FDOProfile(ID portainer.FDOProfileID) (*portainer.FDOProfile, error) {
|
||||||
|
var FDOProfile portainer.FDOProfile
|
||||||
|
identifier := service.connection.ConvertToKey(int(ID))
|
||||||
|
|
||||||
|
err := service.connection.GetObject(BucketName, identifier, &FDOProfile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &FDOProfile, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create assign an ID to a new FDO Profile and saves it.
|
||||||
|
func (service *Service) Create(FDOProfile *portainer.FDOProfile) error {
|
||||||
|
return service.connection.CreateObjectWithId(
|
||||||
|
BucketName,
|
||||||
|
int(FDOProfile.ID),
|
||||||
|
FDOProfile,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update updates an FDO Profile.
|
||||||
|
func (service *Service) Update(ID portainer.FDOProfileID, FDOProfile *portainer.FDOProfile) error {
|
||||||
|
identifier := service.connection.ConvertToKey(int(ID))
|
||||||
|
return service.connection.UpdateObject(BucketName, identifier, FDOProfile)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete deletes an FDO Profile.
|
||||||
|
func (service *Service) Delete(ID portainer.FDOProfileID) error {
|
||||||
|
identifier := service.connection.ConvertToKey(int(ID))
|
||||||
|
return service.connection.DeleteObject(BucketName, identifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNextIdentifier returns the next identifier for a FDO Profile.
|
||||||
|
func (service *Service) GetNextIdentifier() int {
|
||||||
|
return service.connection.GetNextIdentifier(BucketName)
|
||||||
|
}
|
||||||
99
api/dataservices/helmuserrepository/helmuserrepository.go
Normal file
99
api/dataservices/helmuserrepository/helmuserrepository.go
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
package helmuserrepository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
portainer "github.com/portainer/portainer/api"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// BucketName represents the name of the bucket where this service stores data.
|
||||||
|
BucketName = "helm_user_repository"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Service represents a service for managing environment(endpoint) data.
|
||||||
|
type Service struct {
|
||||||
|
connection portainer.Connection
|
||||||
|
}
|
||||||
|
|
||||||
|
func (service *Service) BucketName() string {
|
||||||
|
return BucketName
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewService creates a new instance of a service.
|
||||||
|
func NewService(connection portainer.Connection) (*Service, error) {
|
||||||
|
err := connection.SetServiceName(BucketName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Service{
|
||||||
|
connection: connection,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//HelmUserRepository returns an array of all HelmUserRepository
|
||||||
|
func (service *Service) HelmUserRepositories() ([]portainer.HelmUserRepository, error) {
|
||||||
|
var repos = make([]portainer.HelmUserRepository, 0)
|
||||||
|
|
||||||
|
err := service.connection.GetAll(
|
||||||
|
BucketName,
|
||||||
|
&portainer.HelmUserRepository{},
|
||||||
|
func(obj interface{}) (interface{}, error) {
|
||||||
|
r, ok := obj.(*portainer.HelmUserRepository)
|
||||||
|
if !ok {
|
||||||
|
logrus.WithField("obj", obj).Errorf("Failed to convert to HelmUserRepository object")
|
||||||
|
return nil, fmt.Errorf("Failed to convert to HelmUserRepository object: %s", obj)
|
||||||
|
}
|
||||||
|
repos = append(repos, *r)
|
||||||
|
return &portainer.HelmUserRepository{}, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return repos, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// HelmUserRepositoryByUserID return an array containing all the HelmUserRepository objects where the specified userID is present.
|
||||||
|
func (service *Service) HelmUserRepositoryByUserID(userID portainer.UserID) ([]portainer.HelmUserRepository, error) {
|
||||||
|
var result = make([]portainer.HelmUserRepository, 0)
|
||||||
|
|
||||||
|
err := service.connection.GetAll(
|
||||||
|
BucketName,
|
||||||
|
&portainer.HelmUserRepository{},
|
||||||
|
func(obj interface{}) (interface{}, error) {
|
||||||
|
record, ok := obj.(*portainer.HelmUserRepository)
|
||||||
|
if !ok {
|
||||||
|
logrus.WithField("obj", obj).Errorf("Failed to convert to HelmUserRepository object")
|
||||||
|
return nil, fmt.Errorf("Failed to convert to HelmUserRepository object: %s", obj)
|
||||||
|
}
|
||||||
|
if record.UserID == userID {
|
||||||
|
result = append(result, *record)
|
||||||
|
}
|
||||||
|
return &portainer.HelmUserRepository{}, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateHelmUserRepository creates a new HelmUserRepository object.
|
||||||
|
func (service *Service) Create(record *portainer.HelmUserRepository) error {
|
||||||
|
return service.connection.CreateObject(
|
||||||
|
BucketName,
|
||||||
|
func(id uint64) (int, interface{}) {
|
||||||
|
record.ID = portainer.HelmUserRepositoryID(id)
|
||||||
|
return int(record.ID), record
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateHelmUserRepostory updates an registry.
|
||||||
|
func (service *Service) UpdateHelmUserRepository(ID portainer.HelmUserRepositoryID, registry *portainer.HelmUserRepository) error {
|
||||||
|
identifier := service.connection.ConvertToKey(int(ID))
|
||||||
|
return service.connection.UpdateObject(BucketName, identifier, registry)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteHelmUserRepository deletes an registry.
|
||||||
|
func (service *Service) DeleteHelmUserRepository(ID portainer.HelmUserRepositoryID) error {
|
||||||
|
identifier := service.connection.ConvertToKey(int(ID))
|
||||||
|
return service.connection.DeleteObject(BucketName, identifier)
|
||||||
|
}
|
||||||
305
api/dataservices/interface.go
Normal file
305
api/dataservices/interface.go
Normal file
@@ -0,0 +1,305 @@
|
|||||||
|
package dataservices
|
||||||
|
|
||||||
|
// "github.com/portainer/portainer/api/dataservices"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/portainer/portainer/api/dataservices/errors"
|
||||||
|
|
||||||
|
portainer "github.com/portainer/portainer/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
// DataStore defines the interface to manage the data
|
||||||
|
DataStore interface {
|
||||||
|
Open() (newStore bool, err error)
|
||||||
|
Init() error
|
||||||
|
Close() error
|
||||||
|
MigrateData() error
|
||||||
|
Rollback(force bool) error
|
||||||
|
CheckCurrentEdition() error
|
||||||
|
BackupTo(w io.Writer) error
|
||||||
|
Export(filename string) (err error)
|
||||||
|
IsErrObjectNotFound(err error) bool
|
||||||
|
Connection() portainer.Connection
|
||||||
|
CustomTemplate() CustomTemplateService
|
||||||
|
EdgeGroup() EdgeGroupService
|
||||||
|
EdgeJob() EdgeJobService
|
||||||
|
EdgeStack() EdgeStackService
|
||||||
|
Endpoint() EndpointService
|
||||||
|
EndpointGroup() EndpointGroupService
|
||||||
|
EndpointRelation() EndpointRelationService
|
||||||
|
FDOProfile() FDOProfileService
|
||||||
|
HelmUserRepository() HelmUserRepositoryService
|
||||||
|
Registry() RegistryService
|
||||||
|
ResourceControl() ResourceControlService
|
||||||
|
Role() RoleService
|
||||||
|
APIKeyRepository() APIKeyRepository
|
||||||
|
Settings() SettingsService
|
||||||
|
SSLSettings() SSLSettingsService
|
||||||
|
Stack() StackService
|
||||||
|
Tag() TagService
|
||||||
|
TeamMembership() TeamMembershipService
|
||||||
|
Team() TeamService
|
||||||
|
TunnelServer() TunnelServerService
|
||||||
|
User() UserService
|
||||||
|
Version() VersionService
|
||||||
|
Webhook() WebhookService
|
||||||
|
}
|
||||||
|
|
||||||
|
// CustomTemplateService represents a service to manage custom templates
|
||||||
|
CustomTemplateService interface {
|
||||||
|
GetNextIdentifier() int
|
||||||
|
CustomTemplates() ([]portainer.CustomTemplate, error)
|
||||||
|
CustomTemplate(ID portainer.CustomTemplateID) (*portainer.CustomTemplate, error)
|
||||||
|
Create(customTemplate *portainer.CustomTemplate) error
|
||||||
|
UpdateCustomTemplate(ID portainer.CustomTemplateID, customTemplate *portainer.CustomTemplate) error
|
||||||
|
DeleteCustomTemplate(ID portainer.CustomTemplateID) error
|
||||||
|
BucketName() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// EdgeGroupService represents a service to manage Edge groups
|
||||||
|
EdgeGroupService interface {
|
||||||
|
EdgeGroups() ([]portainer.EdgeGroup, error)
|
||||||
|
EdgeGroup(ID portainer.EdgeGroupID) (*portainer.EdgeGroup, error)
|
||||||
|
Create(group *portainer.EdgeGroup) error
|
||||||
|
UpdateEdgeGroup(ID portainer.EdgeGroupID, group *portainer.EdgeGroup) error
|
||||||
|
DeleteEdgeGroup(ID portainer.EdgeGroupID) error
|
||||||
|
BucketName() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// EdgeJobService represents a service to manage Edge jobs
|
||||||
|
EdgeJobService interface {
|
||||||
|
EdgeJobs() ([]portainer.EdgeJob, error)
|
||||||
|
EdgeJob(ID portainer.EdgeJobID) (*portainer.EdgeJob, error)
|
||||||
|
Create(edgeJob *portainer.EdgeJob) error
|
||||||
|
UpdateEdgeJob(ID portainer.EdgeJobID, edgeJob *portainer.EdgeJob) error
|
||||||
|
DeleteEdgeJob(ID portainer.EdgeJobID) error
|
||||||
|
GetNextIdentifier() int
|
||||||
|
BucketName() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// EdgeStackService represents a service to manage Edge stacks
|
||||||
|
EdgeStackService interface {
|
||||||
|
EdgeStacks() ([]portainer.EdgeStack, error)
|
||||||
|
EdgeStack(ID portainer.EdgeStackID) (*portainer.EdgeStack, error)
|
||||||
|
Create(id portainer.EdgeStackID, edgeStack *portainer.EdgeStack) error
|
||||||
|
UpdateEdgeStack(ID portainer.EdgeStackID, edgeStack *portainer.EdgeStack) error
|
||||||
|
DeleteEdgeStack(ID portainer.EdgeStackID) error
|
||||||
|
GetNextIdentifier() int
|
||||||
|
BucketName() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// EndpointService represents a service for managing environment(endpoint) data
|
||||||
|
EndpointService interface {
|
||||||
|
Endpoint(ID portainer.EndpointID) (*portainer.Endpoint, error)
|
||||||
|
Endpoints() ([]portainer.Endpoint, error)
|
||||||
|
Create(endpoint *portainer.Endpoint) error
|
||||||
|
UpdateEndpoint(ID portainer.EndpointID, endpoint *portainer.Endpoint) error
|
||||||
|
DeleteEndpoint(ID portainer.EndpointID) error
|
||||||
|
GetNextIdentifier() int
|
||||||
|
BucketName() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// EndpointGroupService represents a service for managing environment(endpoint) group data
|
||||||
|
EndpointGroupService interface {
|
||||||
|
EndpointGroup(ID portainer.EndpointGroupID) (*portainer.EndpointGroup, error)
|
||||||
|
EndpointGroups() ([]portainer.EndpointGroup, error)
|
||||||
|
Create(group *portainer.EndpointGroup) error
|
||||||
|
UpdateEndpointGroup(ID portainer.EndpointGroupID, group *portainer.EndpointGroup) error
|
||||||
|
DeleteEndpointGroup(ID portainer.EndpointGroupID) error
|
||||||
|
BucketName() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// EndpointRelationService represents a service for managing environment(endpoint) relations data
|
||||||
|
EndpointRelationService interface {
|
||||||
|
EndpointRelations() ([]portainer.EndpointRelation, error)
|
||||||
|
EndpointRelation(EndpointID portainer.EndpointID) (*portainer.EndpointRelation, error)
|
||||||
|
Create(endpointRelation *portainer.EndpointRelation) error
|
||||||
|
UpdateEndpointRelation(EndpointID portainer.EndpointID, endpointRelation *portainer.EndpointRelation) error
|
||||||
|
DeleteEndpointRelation(EndpointID portainer.EndpointID) error
|
||||||
|
BucketName() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// FDOProfileService represents a service to manage FDO Profiles
|
||||||
|
FDOProfileService interface {
|
||||||
|
FDOProfiles() ([]portainer.FDOProfile, error)
|
||||||
|
FDOProfile(ID portainer.FDOProfileID) (*portainer.FDOProfile, error)
|
||||||
|
Create(FDOProfile *portainer.FDOProfile) error
|
||||||
|
Update(ID portainer.FDOProfileID, FDOProfile *portainer.FDOProfile) error
|
||||||
|
Delete(ID portainer.FDOProfileID) error
|
||||||
|
GetNextIdentifier() int
|
||||||
|
BucketName() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// HelmUserRepositoryService represents a service to manage HelmUserRepositories
|
||||||
|
HelmUserRepositoryService interface {
|
||||||
|
HelmUserRepositories() ([]portainer.HelmUserRepository, error)
|
||||||
|
HelmUserRepositoryByUserID(userID portainer.UserID) ([]portainer.HelmUserRepository, error)
|
||||||
|
Create(record *portainer.HelmUserRepository) error
|
||||||
|
UpdateHelmUserRepository(ID portainer.HelmUserRepositoryID, repository *portainer.HelmUserRepository) error
|
||||||
|
DeleteHelmUserRepository(ID portainer.HelmUserRepositoryID) error
|
||||||
|
BucketName() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// JWTService represents a service for managing JWT tokens
|
||||||
|
JWTService interface {
|
||||||
|
GenerateToken(data *portainer.TokenData) (string, error)
|
||||||
|
GenerateTokenForOAuth(data *portainer.TokenData, expiryTime *time.Time) (string, error)
|
||||||
|
GenerateTokenForKubeconfig(data *portainer.TokenData) (string, error)
|
||||||
|
ParseAndVerifyToken(token string) (*portainer.TokenData, error)
|
||||||
|
SetUserSessionDuration(userSessionDuration time.Duration)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegistryService represents a service for managing registry data
|
||||||
|
RegistryService interface {
|
||||||
|
Registry(ID portainer.RegistryID) (*portainer.Registry, error)
|
||||||
|
Registries() ([]portainer.Registry, error)
|
||||||
|
Create(registry *portainer.Registry) error
|
||||||
|
UpdateRegistry(ID portainer.RegistryID, registry *portainer.Registry) error
|
||||||
|
DeleteRegistry(ID portainer.RegistryID) error
|
||||||
|
BucketName() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResourceControlService represents a service for managing resource control data
|
||||||
|
ResourceControlService interface {
|
||||||
|
ResourceControl(ID portainer.ResourceControlID) (*portainer.ResourceControl, error)
|
||||||
|
ResourceControlByResourceIDAndType(resourceID string, resourceType portainer.ResourceControlType) (*portainer.ResourceControl, error)
|
||||||
|
ResourceControls() ([]portainer.ResourceControl, error)
|
||||||
|
Create(rc *portainer.ResourceControl) error
|
||||||
|
UpdateResourceControl(ID portainer.ResourceControlID, resourceControl *portainer.ResourceControl) error
|
||||||
|
DeleteResourceControl(ID portainer.ResourceControlID) error
|
||||||
|
BucketName() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// RoleService represents a service for managing user roles
|
||||||
|
RoleService interface {
|
||||||
|
Role(ID portainer.RoleID) (*portainer.Role, error)
|
||||||
|
Roles() ([]portainer.Role, error)
|
||||||
|
Create(role *portainer.Role) error
|
||||||
|
UpdateRole(ID portainer.RoleID, role *portainer.Role) error
|
||||||
|
BucketName() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// APIKeyRepositoryService
|
||||||
|
APIKeyRepository interface {
|
||||||
|
CreateAPIKey(key *portainer.APIKey) error
|
||||||
|
GetAPIKey(keyID portainer.APIKeyID) (*portainer.APIKey, error)
|
||||||
|
UpdateAPIKey(key *portainer.APIKey) error
|
||||||
|
DeleteAPIKey(ID portainer.APIKeyID) error
|
||||||
|
GetAPIKeysByUserID(userID portainer.UserID) ([]portainer.APIKey, error)
|
||||||
|
GetAPIKeyByDigest(digest []byte) (*portainer.APIKey, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SettingsService represents a service for managing application settings
|
||||||
|
SettingsService interface {
|
||||||
|
Settings() (*portainer.Settings, error)
|
||||||
|
UpdateSettings(settings *portainer.Settings) error
|
||||||
|
IsFeatureFlagEnabled(feature portainer.Feature) bool
|
||||||
|
BucketName() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// SSLSettingsService represents a service for managing application settings
|
||||||
|
SSLSettingsService interface {
|
||||||
|
Settings() (*portainer.SSLSettings, error)
|
||||||
|
UpdateSettings(settings *portainer.SSLSettings) error
|
||||||
|
BucketName() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// StackService represents a service for managing stack data
|
||||||
|
StackService interface {
|
||||||
|
Stack(ID portainer.StackID) (*portainer.Stack, error)
|
||||||
|
StackByName(name string) (*portainer.Stack, error)
|
||||||
|
StacksByName(name string) ([]portainer.Stack, error)
|
||||||
|
Stacks() ([]portainer.Stack, error)
|
||||||
|
Create(stack *portainer.Stack) error
|
||||||
|
UpdateStack(ID portainer.StackID, stack *portainer.Stack) error
|
||||||
|
DeleteStack(ID portainer.StackID) error
|
||||||
|
GetNextIdentifier() int
|
||||||
|
StackByWebhookID(ID string) (*portainer.Stack, error)
|
||||||
|
RefreshableStacks() ([]portainer.Stack, error)
|
||||||
|
BucketName() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// TagService represents a service for managing tag data
|
||||||
|
TagService interface {
|
||||||
|
Tags() ([]portainer.Tag, error)
|
||||||
|
Tag(ID portainer.TagID) (*portainer.Tag, error)
|
||||||
|
Create(tag *portainer.Tag) error
|
||||||
|
UpdateTag(ID portainer.TagID, tag *portainer.Tag) error
|
||||||
|
DeleteTag(ID portainer.TagID) error
|
||||||
|
BucketName() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// TeamService represents a service for managing user data
|
||||||
|
TeamService interface {
|
||||||
|
Team(ID portainer.TeamID) (*portainer.Team, error)
|
||||||
|
TeamByName(name string) (*portainer.Team, error)
|
||||||
|
Teams() ([]portainer.Team, error)
|
||||||
|
Create(team *portainer.Team) error
|
||||||
|
UpdateTeam(ID portainer.TeamID, team *portainer.Team) error
|
||||||
|
DeleteTeam(ID portainer.TeamID) error
|
||||||
|
BucketName() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// TeamMembershipService represents a service for managing team membership data
|
||||||
|
TeamMembershipService interface {
|
||||||
|
TeamMembership(ID portainer.TeamMembershipID) (*portainer.TeamMembership, error)
|
||||||
|
TeamMemberships() ([]portainer.TeamMembership, error)
|
||||||
|
TeamMembershipsByUserID(userID portainer.UserID) ([]portainer.TeamMembership, error)
|
||||||
|
TeamMembershipsByTeamID(teamID portainer.TeamID) ([]portainer.TeamMembership, error)
|
||||||
|
Create(membership *portainer.TeamMembership) error
|
||||||
|
UpdateTeamMembership(ID portainer.TeamMembershipID, membership *portainer.TeamMembership) error
|
||||||
|
DeleteTeamMembership(ID portainer.TeamMembershipID) error
|
||||||
|
DeleteTeamMembershipByUserID(userID portainer.UserID) error
|
||||||
|
DeleteTeamMembershipByTeamID(teamID portainer.TeamID) error
|
||||||
|
BucketName() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// TunnelServerService represents a service for managing data associated to the tunnel server
|
||||||
|
TunnelServerService interface {
|
||||||
|
Info() (*portainer.TunnelServerInfo, error)
|
||||||
|
UpdateInfo(info *portainer.TunnelServerInfo) error
|
||||||
|
BucketName() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserService represents a service for managing user data
|
||||||
|
UserService interface {
|
||||||
|
User(ID portainer.UserID) (*portainer.User, error)
|
||||||
|
UserByUsername(username string) (*portainer.User, error)
|
||||||
|
Users() ([]portainer.User, error)
|
||||||
|
UsersByRole(role portainer.UserRole) ([]portainer.User, error)
|
||||||
|
Create(user *portainer.User) error
|
||||||
|
UpdateUser(ID portainer.UserID, user *portainer.User) error
|
||||||
|
DeleteUser(ID portainer.UserID) error
|
||||||
|
BucketName() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// VersionService represents a service for managing version data
|
||||||
|
VersionService interface {
|
||||||
|
DBVersion() (int, error)
|
||||||
|
Edition() (portainer.SoftwareEdition, error)
|
||||||
|
InstanceID() (string, error)
|
||||||
|
StoreDBVersion(version int) error
|
||||||
|
StoreInstanceID(ID string) error
|
||||||
|
BucketName() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// WebhookService represents a service for managing webhook data.
|
||||||
|
WebhookService interface {
|
||||||
|
Webhooks() ([]portainer.Webhook, error)
|
||||||
|
Webhook(ID portainer.WebhookID) (*portainer.Webhook, error)
|
||||||
|
Create(portainer *portainer.Webhook) error
|
||||||
|
UpdateWebhook(ID portainer.WebhookID, webhook *portainer.Webhook) error
|
||||||
|
WebhookByResourceID(resourceID string) (*portainer.Webhook, error)
|
||||||
|
WebhookByToken(token string) (*portainer.Webhook, error)
|
||||||
|
DeleteWebhook(ID portainer.WebhookID) error
|
||||||
|
BucketName() string
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func IsErrObjectNotFound(e error) bool {
|
||||||
|
return e == errors.ErrObjectNotFound
|
||||||
|
}
|
||||||
90
api/dataservices/registry/registry.go
Normal file
90
api/dataservices/registry/registry.go
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
package registry
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
portainer "github.com/portainer/portainer/api"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// BucketName represents the name of the bucket where this service stores data.
|
||||||
|
BucketName = "registries"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Service represents a service for managing environment(endpoint) data.
|
||||||
|
type Service struct {
|
||||||
|
connection portainer.Connection
|
||||||
|
}
|
||||||
|
|
||||||
|
func (service *Service) BucketName() string {
|
||||||
|
return BucketName
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewService creates a new instance of a service.
|
||||||
|
func NewService(connection portainer.Connection) (*Service, error) {
|
||||||
|
err := connection.SetServiceName(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 := service.connection.ConvertToKey(int(ID))
|
||||||
|
|
||||||
|
err := service.connection.GetObject(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.GetAll(
|
||||||
|
BucketName,
|
||||||
|
&portainer.Registry{},
|
||||||
|
func(obj interface{}) (interface{}, error) {
|
||||||
|
registry, ok := obj.(*portainer.Registry)
|
||||||
|
if !ok {
|
||||||
|
logrus.WithField("obj", obj).Errorf("Failed to convert to Registry object")
|
||||||
|
return nil, fmt.Errorf("Failed to convert to Registry object: %s", obj)
|
||||||
|
}
|
||||||
|
registries = append(registries, *registry)
|
||||||
|
return &portainer.Registry{}, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return registries, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateRegistry creates a new registry.
|
||||||
|
func (service *Service) Create(registry *portainer.Registry) error {
|
||||||
|
return service.connection.CreateObject(
|
||||||
|
BucketName,
|
||||||
|
func(id uint64) (int, interface{}) {
|
||||||
|
registry.ID = portainer.RegistryID(id)
|
||||||
|
return int(registry.ID), registry
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateRegistry updates an registry.
|
||||||
|
func (service *Service) UpdateRegistry(ID portainer.RegistryID, registry *portainer.Registry) error {
|
||||||
|
identifier := service.connection.ConvertToKey(int(ID))
|
||||||
|
return service.connection.UpdateObject(BucketName, identifier, registry)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteRegistry deletes an registry.
|
||||||
|
func (service *Service) DeleteRegistry(ID portainer.RegistryID) error {
|
||||||
|
identifier := service.connection.ConvertToKey(int(ID))
|
||||||
|
return service.connection.DeleteObject(BucketName, identifier)
|
||||||
|
}
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
package resourcecontrol
|
package resourcecontrol
|
||||||
|
|
||||||
import (
|
import (
|
||||||
portainer "github.com/portainer/portainer/api"
|
"fmt"
|
||||||
"github.com/portainer/portainer/api/bolt/internal"
|
|
||||||
|
|
||||||
"github.com/boltdb/bolt"
|
portainer "github.com/portainer/portainer/api"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -14,12 +14,16 @@ const (
|
|||||||
|
|
||||||
// Service represents a service for managing environment(endpoint) data.
|
// Service represents a service for managing environment(endpoint) data.
|
||||||
type Service struct {
|
type Service struct {
|
||||||
connection *internal.DbConnection
|
connection portainer.Connection
|
||||||
|
}
|
||||||
|
|
||||||
|
func (service *Service) BucketName() string {
|
||||||
|
return BucketName
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewService creates a new instance of a service.
|
// NewService creates a new instance of a service.
|
||||||
func NewService(connection *internal.DbConnection) (*Service, error) {
|
func NewService(connection portainer.Connection) (*Service, error) {
|
||||||
err := internal.CreateBucket(connection, BucketName)
|
err := connection.SetServiceName(BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -32,9 +36,9 @@ func NewService(connection *internal.DbConnection) (*Service, error) {
|
|||||||
// ResourceControl returns a ResourceControl object by ID
|
// ResourceControl returns a ResourceControl object by ID
|
||||||
func (service *Service) ResourceControl(ID portainer.ResourceControlID) (*portainer.ResourceControl, error) {
|
func (service *Service) ResourceControl(ID portainer.ResourceControlID) (*portainer.ResourceControl, error) {
|
||||||
var resourceControl portainer.ResourceControl
|
var resourceControl portainer.ResourceControl
|
||||||
identifier := internal.Itob(int(ID))
|
identifier := service.connection.ConvertToKey(int(ID))
|
||||||
|
|
||||||
err := internal.GetObject(service.connection, BucketName, identifier, &resourceControl)
|
err := service.connection.GetObject(BucketName, identifier, &resourceControl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -47,85 +51,76 @@ func (service *Service) ResourceControl(ID portainer.ResourceControlID) (*portai
|
|||||||
// if no ResourceControl was found.
|
// if no ResourceControl was found.
|
||||||
func (service *Service) ResourceControlByResourceIDAndType(resourceID string, resourceType portainer.ResourceControlType) (*portainer.ResourceControl, error) {
|
func (service *Service) ResourceControlByResourceIDAndType(resourceID string, resourceType portainer.ResourceControlType) (*portainer.ResourceControl, error) {
|
||||||
var resourceControl *portainer.ResourceControl
|
var resourceControl *portainer.ResourceControl
|
||||||
|
stop := fmt.Errorf("ok")
|
||||||
err := service.connection.View(func(tx *bolt.Tx) error {
|
err := service.connection.GetAll(
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
BucketName,
|
||||||
cursor := bucket.Cursor()
|
&portainer.ResourceControl{},
|
||||||
|
func(obj interface{}) (interface{}, error) {
|
||||||
for k, v := cursor.First(); k != nil; k, v = cursor.Next() {
|
rc, ok := obj.(*portainer.ResourceControl)
|
||||||
var rc portainer.ResourceControl
|
if !ok {
|
||||||
err := internal.UnmarshalObject(v, &rc)
|
logrus.WithField("obj", obj).Errorf("Failed to convert to ResourceControl object")
|
||||||
if err != nil {
|
return nil, fmt.Errorf("Failed to convert to ResourceControl object: %s", obj)
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if rc.ResourceID == resourceID && rc.Type == resourceType {
|
if rc.ResourceID == resourceID && rc.Type == resourceType {
|
||||||
resourceControl = &rc
|
resourceControl = rc
|
||||||
break
|
return nil, stop
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, subResourceID := range rc.SubResourceIDs {
|
for _, subResourceID := range rc.SubResourceIDs {
|
||||||
if subResourceID == resourceID {
|
if subResourceID == resourceID {
|
||||||
resourceControl = &rc
|
resourceControl = rc
|
||||||
break
|
return nil, stop
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
return &portainer.ResourceControl{}, nil
|
||||||
|
})
|
||||||
|
if err == stop {
|
||||||
|
return resourceControl, nil
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil, err
|
||||||
})
|
|
||||||
|
|
||||||
return resourceControl, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResourceControls returns all the ResourceControl objects
|
// ResourceControls returns all the ResourceControl objects
|
||||||
func (service *Service) ResourceControls() ([]portainer.ResourceControl, error) {
|
func (service *Service) ResourceControls() ([]portainer.ResourceControl, error) {
|
||||||
var rcs = make([]portainer.ResourceControl, 0)
|
var rcs = make([]portainer.ResourceControl, 0)
|
||||||
|
|
||||||
err := service.connection.View(func(tx *bolt.Tx) error {
|
err := service.connection.GetAll(
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
BucketName,
|
||||||
|
&portainer.ResourceControl{},
|
||||||
cursor := bucket.Cursor()
|
func(obj interface{}) (interface{}, error) {
|
||||||
for k, v := cursor.First(); k != nil; k, v = cursor.Next() {
|
rc, ok := obj.(*portainer.ResourceControl)
|
||||||
var resourceControl portainer.ResourceControl
|
if !ok {
|
||||||
err := internal.UnmarshalObject(v, &resourceControl)
|
logrus.WithField("obj", obj).Errorf("Failed to convert to ResourceControl object")
|
||||||
if err != nil {
|
return nil, fmt.Errorf("Failed to convert to ResourceControl object: %s", obj)
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
rcs = append(rcs, resourceControl)
|
rcs = append(rcs, *rc)
|
||||||
}
|
return &portainer.ResourceControl{}, nil
|
||||||
|
})
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
return rcs, err
|
return rcs, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateResourceControl creates a new ResourceControl object
|
// CreateResourceControl creates a new ResourceControl object
|
||||||
func (service *Service) CreateResourceControl(resourceControl *portainer.ResourceControl) error {
|
func (service *Service) Create(resourceControl *portainer.ResourceControl) error {
|
||||||
return service.connection.Update(func(tx *bolt.Tx) error {
|
return service.connection.CreateObject(
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
BucketName,
|
||||||
|
func(id uint64) (int, interface{}) {
|
||||||
id, _ := bucket.NextSequence()
|
resourceControl.ID = portainer.ResourceControlID(id)
|
||||||
resourceControl.ID = portainer.ResourceControlID(id)
|
return int(resourceControl.ID), resourceControl
|
||||||
|
},
|
||||||
data, err := internal.MarshalObject(resourceControl)
|
)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return bucket.Put(internal.Itob(int(resourceControl.ID)), data)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateResourceControl saves a ResourceControl object.
|
// UpdateResourceControl saves a ResourceControl object.
|
||||||
func (service *Service) UpdateResourceControl(ID portainer.ResourceControlID, resourceControl *portainer.ResourceControl) error {
|
func (service *Service) UpdateResourceControl(ID portainer.ResourceControlID, resourceControl *portainer.ResourceControl) error {
|
||||||
identifier := internal.Itob(int(ID))
|
identifier := service.connection.ConvertToKey(int(ID))
|
||||||
return internal.UpdateObject(service.connection, BucketName, identifier, resourceControl)
|
return service.connection.UpdateObject(BucketName, identifier, resourceControl)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteResourceControl deletes a ResourceControl object by ID
|
// DeleteResourceControl deletes a ResourceControl object by ID
|
||||||
func (service *Service) DeleteResourceControl(ID portainer.ResourceControlID) error {
|
func (service *Service) DeleteResourceControl(ID portainer.ResourceControlID) error {
|
||||||
identifier := internal.Itob(int(ID))
|
identifier := service.connection.ConvertToKey(int(ID))
|
||||||
return internal.DeleteObject(service.connection, BucketName, identifier)
|
return service.connection.DeleteObject(BucketName, identifier)
|
||||||
}
|
}
|
||||||
84
api/dataservices/role/role.go
Normal file
84
api/dataservices/role/role.go
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
package role
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
portainer "github.com/portainer/portainer/api"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// BucketName represents the name of the bucket where this service stores data.
|
||||||
|
BucketName = "roles"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Service represents a service for managing environment(endpoint) data.
|
||||||
|
type Service struct {
|
||||||
|
connection portainer.Connection
|
||||||
|
}
|
||||||
|
|
||||||
|
func (service *Service) BucketName() string {
|
||||||
|
return BucketName
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewService creates a new instance of a service.
|
||||||
|
func NewService(connection portainer.Connection) (*Service, error) {
|
||||||
|
err := connection.SetServiceName(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 := service.connection.ConvertToKey(int(ID))
|
||||||
|
|
||||||
|
err := service.connection.GetObject(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.GetAll(
|
||||||
|
BucketName,
|
||||||
|
&portainer.Role{},
|
||||||
|
func(obj interface{}) (interface{}, error) {
|
||||||
|
set, ok := obj.(*portainer.Role)
|
||||||
|
if !ok {
|
||||||
|
logrus.WithField("obj", obj).Errorf("Failed to convert to Role object")
|
||||||
|
return nil, fmt.Errorf("Failed to convert to Role object: %s", obj)
|
||||||
|
}
|
||||||
|
sets = append(sets, *set)
|
||||||
|
return &portainer.Role{}, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return sets, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateRole creates a new Role.
|
||||||
|
func (service *Service) Create(role *portainer.Role) error {
|
||||||
|
return service.connection.CreateObject(
|
||||||
|
BucketName,
|
||||||
|
func(id uint64) (int, interface{}) {
|
||||||
|
role.ID = portainer.RoleID(id)
|
||||||
|
return int(role.ID), role
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateRole updates a role.
|
||||||
|
func (service *Service) UpdateRole(ID portainer.RoleID, role *portainer.Role) error {
|
||||||
|
identifier := service.connection.ConvertToKey(int(ID))
|
||||||
|
return service.connection.UpdateObject(BucketName, identifier, role)
|
||||||
|
}
|
||||||
112
api/dataservices/schedule/schedule.go
Normal file
112
api/dataservices/schedule/schedule.go
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
package schedule
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
portainer "github.com/portainer/portainer/api"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
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 portainer.Connection
|
||||||
|
}
|
||||||
|
|
||||||
|
func (service *Service) BucketName() string {
|
||||||
|
return BucketName
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewService creates a new instance of a service.
|
||||||
|
func NewService(connection portainer.Connection) (*Service, error) {
|
||||||
|
err := connection.SetServiceName(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 := service.connection.ConvertToKey(int(ID))
|
||||||
|
|
||||||
|
err := service.connection.GetObject(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 := service.connection.ConvertToKey(int(ID))
|
||||||
|
return service.connection.UpdateObject(BucketName, identifier, schedule)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteSchedule deletes a schedule.
|
||||||
|
func (service *Service) DeleteSchedule(ID portainer.ScheduleID) error {
|
||||||
|
identifier := service.connection.ConvertToKey(int(ID))
|
||||||
|
return service.connection.DeleteObject(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.GetAll(
|
||||||
|
BucketName,
|
||||||
|
&portainer.Schedule{},
|
||||||
|
func(obj interface{}) (interface{}, error) {
|
||||||
|
schedule, ok := obj.(*portainer.Schedule)
|
||||||
|
if !ok {
|
||||||
|
logrus.WithField("obj", obj).Errorf("Failed to convert to Schedule object")
|
||||||
|
return nil, fmt.Errorf("Failed to convert to Schedule object: %s", obj)
|
||||||
|
}
|
||||||
|
schedules = append(schedules, *schedule)
|
||||||
|
return &portainer.Schedule{}, 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.GetAll(
|
||||||
|
BucketName,
|
||||||
|
&portainer.Schedule{},
|
||||||
|
func(obj interface{}) (interface{}, error) {
|
||||||
|
schedule, ok := obj.(*portainer.Schedule)
|
||||||
|
if !ok {
|
||||||
|
logrus.WithField("obj", obj).Errorf("Failed to convert to Schedule object")
|
||||||
|
return nil, fmt.Errorf("Failed to convert to Schedule object: %s", obj)
|
||||||
|
}
|
||||||
|
if schedule.JobType == jobType {
|
||||||
|
schedules = append(schedules, *schedule)
|
||||||
|
}
|
||||||
|
return &portainer.Schedule{}, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return schedules, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create assign an ID to a new schedule and saves it.
|
||||||
|
func (service *Service) CreateSchedule(schedule *portainer.Schedule) error {
|
||||||
|
return service.connection.CreateObjectWithSetSequence(BucketName, int(schedule.ID), schedule)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNextIdentifier returns the next identifier for a schedule.
|
||||||
|
func (service *Service) GetNextIdentifier() int {
|
||||||
|
return service.connection.GetNextIdentifier(BucketName)
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user