Compare commits

..

39 Commits

Author SHA1 Message Date
congs 8c1977e0aa fix(permission): EE-3772 Team leaders are able to see all environments (#7330)
/ triage (push) Has been cancelled
Lint / Run linters (push) Has been cancelled
Test / test-client (push) Has been cancelled
2022-07-26 11:02:33 +12:00
Matt Hook c47fd9f9ed bump version to 2.14.2 (#7304) 2022-07-22 10:55:17 +12:00
LP B 767d1d1970 fix(edge): pagination on create/edit edge jobs/groups [EE-3219] (#7297) 2022-07-21 18:47:20 +02:00
itsconquest ef81e5c0e0 fix(auth): correctly calculate LDAP teamsync [EE-3704] (#7292) 2022-07-21 21:29:27 +12:00
Prabhat Khera 234b7a3d5e fix(kubeconfig): fix kubeconfig url EE-3455 (#7283) 2022-07-21 16:59:48 +12:00
itsconquest af49305e64 fix(TLS): remove file type validation [EE-3672] (#7279) 2022-07-21 16:25:19 +12:00
LP B d181d1251c fix(app/mustache): reuse mustache variables in templates [EE-3689] (#7287) 2022-07-19 15:38:10 +02:00
LP B 5f7db66e95 fix(app/templates): handle special characters in mustache templates [EE-3708] (#7289) 2022-07-19 14:05:31 +02:00
Dmitry Salakhov 17378bdef6 fix(users): admin can change password with any auth method (#7269) [EE-3671] 2022-07-19 11:26:43 +12:00
Oscar Zhou 010542ac1e fix(setting): update the por switch field component property (#7258) 2022-07-15 08:27:53 +12:00
Matt Hook 1bb253479a bump version to 2.14.1 (#7237)
Test / test-client (push) Has been cancelled
2022-07-12 11:03:49 +12:00
Dakota Walsh f0a13a2ad1 fix(migration): close the database before running backups EE-3627 (#7217)
* fix(migration): close the database before running backups

On certain filesystems, particuarly NTFS when a network mounted windows
file server is used to store portainer's database, you are unable to
copy the database while it is open. To fix this we simply close the
database and then re-open it after a backup.

* handle close and open errors

* dont return error on nil
2022-07-08 21:04:55 +12:00
Matt Hook f9b28aa0a1 fix(compose): use docker-compose plugin directly [EE-3631] (#7201)
* use simplifed method of calling compose directly with new compose wrapper

* download compose binary to docker-compose

* update to newer wrapper that fixes -H issue

* update to released
2022-07-08 16:02:37 +12:00
LP B d26e1b6983 fix(k8s/app-templates): display moustache variables fields when deploying from app template (#7185) 2022-07-08 14:15:16 +12:00
Dmitry Salakhov 7b00fdd208 fix(users): enable manual user addition [EE-3639] (#7196) 2022-07-06 15:47:51 +12:00
Steven Kang 14b998d270 Set static DOCKER_VERSION for ppc64le and s390x (#7135)
Test / test-client (push) Has been cancelled
2022-06-28 11:38:12 +12:00
Chaim Lev-Ari 605ff8c1da fix(environments): hide async mode on deployment [EE-3380] (#7129)
fixes [EE-3380]
2022-06-28 10:23:07 +12:00
Chaim Lev-Ari 13f93f4262 fix(analytics): load public settings [EE-3590] (#7127) 2022-06-27 19:29:06 +03:00
Steven Kang 16be5ed329 feat(build): set static DOCKER_VERSION for ppc64le and s390x (#7124) 2022-06-27 09:54:04 +12:00
Chaim Lev-Ari c6612898f3 fix(api): add missing edge types [EE-3590] (#7117) 2022-06-26 08:38:20 +03:00
andres-portainer 564f34b0ba fix(wizard): replace the YAML file by the docker commands EE-3589 (#7112) 2022-06-24 14:59:00 -03:00
LP B 392fbdb4a7 fix(app/account): ensure newTransition exists in uiCanExit [EE-3336] (#7109) 2022-06-24 17:35:39 +02:00
Chaim Lev-Ari a826c78786 fix(edge): show heartbeat for async env [EE-3380] (#7096) 2022-06-22 20:11:42 +03:00
Matt Hook a35f0607f1 fix docker download path for mac platforms (#7101) 2022-06-22 10:06:28 +12:00
LP B 081d32af0d fix(app/account): create access token button (#7091)
* fix(app/account): create access token button

* fix(app/formcontrol): error message overlapping input on smaller screens
2022-06-20 14:14:41 +02:00
itsconquest 4cc0b1f567 fix(auth): track skips per user [EE-3318] (#7088) 2022-06-20 17:00:00 +12:00
Chaim Lev-Ari d4da7e1760 fix(docker/networks): show correct resource control data [EE-3401] (#7061) 2022-06-17 19:21:38 +03:00
itsconquest aced418880 fix(auth): clear skips when using new instance [EE-3331] (#7026) 2022-06-17 14:45:42 +12:00
Chaim Lev-Ari 614f42fe5a feat(custom-templates): hide variables [EE-2602] (#7069) 2022-06-16 08:32:43 +03:00
itsconquest 58736fe93b feat(auth): allow single char passwords [EE-3385] (#7049)
* feat(auth): allow single character passwords

* match weak password modal logic to slider
2022-06-16 12:31:39 +12:00
Matt Hook b78330b10d fix(swarm): don't stomp on the x-registry-auth header EE-3308 (#7037)
* don't stomp on the x-registry-auth header

* del header if empty json provided for registry auth
2022-06-16 09:54:06 +12:00
itsconquest eed4a92ca8 fix(auth): notify user password requirements [EE-3344] (#7041)
* fix(auth): notify user password requirements [EE-3344]

* fix angular code
2022-06-15 17:15:38 +12:00
Dmitry Salakhov 0e7468a1e8 fix: clarify password change error (#7020) 2022-06-15 15:44:54 +12:00
congs b807481f1c fix(teamleader): EE-3411 normal users get an unauthorized error (#7053) 2022-06-14 14:12:33 +12:00
Ali da27de2154 fix(wizard): return back to envs page EE-3419 (#7064) 2022-06-13 14:59:23 +12:00
congs 6743e4fbb2 fix(teamleader): EE-3383 allow teamleader promote member to teamleader (#7039) 2022-06-10 17:13:23 +12:00
Ali b489ffaa63 fix(wizard): show teasers for kaas and kubeconfig features [EE-3316] (#7033)
* fix(wizard): add nomad, kaas, kubeconfig teasers
2022-06-10 09:16:43 +12:00
congs 6e12499d61 fix(teamleader): EE-3332 hide name and leaders (#7032) 2022-06-09 14:22:42 +12:00
Ali f7acbe16ba fix(wizard): use 'New Environments' title EE-3329 (#7035) 2022-06-08 16:37:53 +12:00
1971 changed files with 28328 additions and 52750 deletions
+2 -17
View File
@@ -31,12 +31,7 @@ rules:
[
'error',
{
pathGroups:
[
{ pattern: '@@/**', group: 'internal', position: 'after' },
{ pattern: '@/**', group: 'internal' },
{ pattern: '{Kubernetes,Portainer,Agent,Azure,Docker}/**', group: 'internal' },
],
pathGroups: [{ pattern: '@/**', group: 'internal' }, { pattern: '{Kubernetes,Portainer,Agent,Azure,Docker}/**', group: 'internal' }],
groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'],
pathGroupsExcludedImportTypes: ['internal'],
},
@@ -46,7 +41,6 @@ settings:
'import/resolver':
alias:
map:
- ['@@', './app/react/components']
- ['@', './app']
extensions: ['.js', '.ts', '.tsx']
@@ -58,7 +52,6 @@ overrides:
parser: '@typescript-eslint/parser'
plugins:
- '@typescript-eslint'
- 'regex'
extends:
- airbnb
- airbnb-typescript
@@ -75,14 +68,7 @@ overrides:
version: 'detect'
rules:
import/order:
[
'error',
{
pathGroups: [{ pattern: '@@/**', group: 'internal', position: 'after' }, { pattern: '@/**', group: 'internal' }],
groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'],
'newlines-between': 'always',
},
]
['error', { pathGroups: [{ pattern: '@/**', group: 'internal' }], groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'], 'newlines-between': 'always' }]
func-style: [error, 'declaration']
import/prefer-default-export: off
no-use-before-define: ['error', { functions: false }]
@@ -104,7 +90,6 @@ overrides:
'react/jsx-no-bind': off
'no-await-in-loop': 'off'
'react/jsx-no-useless-fragment': ['error', { allowExpressions: true }]
'regex/invalid': ['error', [{ 'regex': 'data-feather="(.*)"', 'message': 'Please use `react-feather` package instead' }]]
- files:
- app/**/*.test.*
extends:
-1
View File
@@ -7,7 +7,6 @@ storybook-static
.tmp
**/.vscode/settings.json
**/.vscode/tasks.json
.vscode
*.DS_Store
.eslintcache
+8 -2
View File
@@ -12,15 +12,21 @@ Portainer consists of a single container that can run on any cluster. It can be
- [Take5 – get 5 free nodes of Portainer Business for as long as you want them](https://portainer.io/pricing/take5)
- [Portainer BE install guide](https://install.portainer.io)
## Demo
You can try out the public demo instance: http://demo.portainer.io/ (login with the username **admin** and the password **tryportainer**).
Please note that the public demo cluster is **reset every 15min**.
## Latest Version
Portainer CE is updated regularly. We aim to do an update release every couple of months.
[![latest version](https://img.shields.io/github/v/release/portainer/portainer?color=%2344cc11&label=Latest%20release&style=for-the-badge)](https://github.com/portainer/portainer/releases/latest)
**The latest version of Portainer is 2.9.x**. Portainer is on version 2, the second number denotes the month of release.
## Getting started
- [Deploy Portainer](https://docs.portainer.io/start/install)
- [Deploy Portainer](https://docs.portainer.io/v/ce-2.9/start/install)
- [Documentation](https://documentation.portainer.io)
- [Contribute to the project](https://documentation.portainer.io/contributing/instructions/)
+8 -11
View File
@@ -2,6 +2,7 @@ package adminmonitor
import (
"context"
"log"
"net/http"
"strings"
"sync"
@@ -10,10 +11,10 @@ import (
httperror "github.com/portainer/libhttp/error"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
"github.com/rs/zerolog/log"
)
var logFatalf = log.Fatalf
const RedirectReasonAdminInitTimeout string = "AdminInitTimeout"
type Monitor struct {
@@ -48,28 +49,24 @@ func (m *Monitor) Start() {
m.cancellationFunc = cancellationFunc
go func() {
log.Debug().Msg("start initialization monitor")
log.Println("[DEBUG] [internal,init] [message: start initialization monitor ]")
select {
case <-time.After(m.timeout):
initialized, err := m.WasInitialized()
if err != nil {
log.Fatal().Err(err).Msg("")
logFatalf("%s", err)
}
if !initialized {
log.Info().Msg("the Portainer instance timed out for security purposes, to re-enable your Portainer instance, you will need to restart Portainer")
log.Println("[INFO] [internal,init] The Portainer instance timed out for security purposes. To re-enable your Portainer instance, you will need to restart Portainer")
m.mu.Lock()
defer m.mu.Unlock()
m.adminInitDisabled = true
return
}
case <-cancellationCtx.Done():
log.Debug().Msg("canceling initialization monitor")
log.Println("[DEBUG] [internal,init] [message: canceling initialization monitor]")
case <-m.shutdownCtx.Done():
log.Debug().Msg("shutting down initialization monitor")
log.Println("[DEBUG] [internal,init] [message: shutting down initialization monitor]")
}
}()
}
-71
View File
@@ -1,71 +0,0 @@
package agent
import (
"crypto/tls"
"errors"
"fmt"
"net/http"
"strconv"
"time"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/internal/url"
)
// GetAgentVersionAndPlatform returns the agent version and platform
//
// it sends a ping to the agent and parses the version and platform from the headers
func GetAgentVersionAndPlatform(endpointUrl string, tlsConfig *tls.Config) (portainer.AgentPlatform, string, error) {
httpCli := &http.Client{
Timeout: 3 * time.Second,
}
if tlsConfig != nil {
httpCli.Transport = &http.Transport{
TLSClientConfig: tlsConfig,
}
}
parsedURL, err := url.ParseURL(endpointUrl + "/ping")
if err != nil {
return 0, "", err
}
parsedURL.Scheme = "https"
req, err := http.NewRequest(http.MethodGet, parsedURL.String(), nil)
if err != nil {
return 0, "", err
}
resp, err := httpCli.Do(req)
if err != nil {
return 0, "", err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusNoContent {
return 0, "", fmt.Errorf("Failed request with status %d", resp.StatusCode)
}
version := resp.Header.Get(portainer.PortainerAgentHeader)
if version == "" {
return 0, "", errors.New("Version Header is missing")
}
agentPlatformHeader := resp.Header.Get(portainer.HTTPResponseAgentPlatform)
if agentPlatformHeader == "" {
return 0, "", errors.New("Agent Platform Header is missing")
}
agentPlatformNumber, err := strconv.Atoi(agentPlatformHeader)
if err != nil {
return 0, "", err
}
if agentPlatformNumber == 0 {
return 0, "", errors.New("Agent platform is invalid")
}
return portainer.AgentPlatform(agentPlatformNumber), version, nil
}
+10 -11
View File
@@ -2,6 +2,7 @@ package apikey
import (
"crypto/sha256"
"log"
"strings"
"testing"
"time"
@@ -9,8 +10,6 @@ import (
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/datastore"
"github.com/stretchr/testify/assert"
"github.com/rs/zerolog/log"
)
func Test_SatisfiesAPIKeyServiceInterface(t *testing.T) {
@@ -21,7 +20,7 @@ func Test_SatisfiesAPIKeyServiceInterface(t *testing.T) {
func Test_GenerateApiKey(t *testing.T) {
is := assert.New(t)
_, store, teardown := datastore.MustNewTestStore(t, true, true)
_, store, teardown := datastore.MustNewTestStore(true, true)
defer teardown()
service := NewAPIKeyService(store.APIKeyRepository(), store.User())
@@ -75,7 +74,7 @@ func Test_GenerateApiKey(t *testing.T) {
func Test_GetAPIKey(t *testing.T) {
is := assert.New(t)
_, store, teardown := datastore.MustNewTestStore(t, true, true)
_, store, teardown := datastore.MustNewTestStore(true, true)
defer teardown()
service := NewAPIKeyService(store.APIKeyRepository(), store.User())
@@ -95,7 +94,7 @@ func Test_GetAPIKey(t *testing.T) {
func Test_GetAPIKeys(t *testing.T) {
is := assert.New(t)
_, store, teardown := datastore.MustNewTestStore(t, true, true)
_, store, teardown := datastore.MustNewTestStore(true, true)
defer teardown()
service := NewAPIKeyService(store.APIKeyRepository(), store.User())
@@ -116,7 +115,7 @@ func Test_GetAPIKeys(t *testing.T) {
func Test_GetDigestUserAndKey(t *testing.T) {
is := assert.New(t)
_, store, teardown := datastore.MustNewTestStore(t, true, true)
_, store, teardown := datastore.MustNewTestStore(true, true)
defer teardown()
service := NewAPIKeyService(store.APIKeyRepository(), store.User())
@@ -152,7 +151,7 @@ func Test_GetDigestUserAndKey(t *testing.T) {
func Test_UpdateAPIKey(t *testing.T) {
is := assert.New(t)
_, store, teardown := datastore.MustNewTestStore(t, true, true)
_, store, teardown := datastore.MustNewTestStore(true, true)
defer teardown()
service := NewAPIKeyService(store.APIKeyRepository(), store.User())
@@ -170,8 +169,8 @@ func Test_UpdateAPIKey(t *testing.T) {
_, apiKeyGot, err := service.GetDigestUserAndKey(apiKey.Digest)
is.NoError(err)
log.Debug().Msgf("%+v", apiKey)
log.Debug().Msgf("%+v", apiKeyGot)
log.Println(apiKey)
log.Println(apiKeyGot)
is.Equal(apiKey.LastUsed, apiKeyGot.LastUsed)
@@ -200,7 +199,7 @@ func Test_UpdateAPIKey(t *testing.T) {
func Test_DeleteAPIKey(t *testing.T) {
is := assert.New(t)
_, store, teardown := datastore.MustNewTestStore(t, true, true)
_, store, teardown := datastore.MustNewTestStore(true, true)
defer teardown()
service := NewAPIKeyService(store.APIKeyRepository(), store.User())
@@ -241,7 +240,7 @@ func Test_DeleteAPIKey(t *testing.T) {
func Test_InvalidateUserKeyCache(t *testing.T) {
is := assert.New(t)
_, store, teardown := datastore.MustNewTestStore(t, true, true)
_, store, teardown := datastore.MustNewTestStore(true, true)
defer teardown()
service := NewAPIKeyService(store.APIKeyRepository(), store.User())
-42
View File
@@ -34,45 +34,3 @@ func TarFileInBuffer(fileContent []byte, fileName string, mode int64) ([]byte, e
return buffer.Bytes(), nil
}
// tarFileInBuffer represents a tar archive buffer.
type tarFileInBuffer struct {
b *bytes.Buffer
w *tar.Writer
}
func NewTarFileInBuffer() *tarFileInBuffer {
var b bytes.Buffer
return &tarFileInBuffer{
b: &b,
w: tar.NewWriter(&b),
}
}
// Put puts a single file to tar archive buffer.
func (t *tarFileInBuffer) Put(fileContent []byte, fileName string, mode int64) error {
hdr := &tar.Header{
Name: fileName,
Mode: mode,
Size: int64(len(fileContent)),
}
if err := t.w.WriteHeader(hdr); err != nil {
return err
}
if _, err := t.w.Write(fileContent); err != nil {
return err
}
return nil
}
// Bytes returns the archive as a byte array.
func (t *tarFileInBuffer) Bytes() []byte {
return t.b.Bytes()
}
func (t *tarFileInBuffer) Close() error {
return t.w.Close()
}
+13 -4
View File
@@ -9,6 +9,7 @@ import (
"path/filepath"
"testing"
"github.com/docker/docker/pkg/ioutils"
"github.com/stretchr/testify/assert"
)
@@ -26,7 +27,9 @@ func listFiles(dir string) []string {
}
func Test_shouldCreateArhive(t *testing.T) {
tmpdir := t.TempDir()
tmpdir, _ := ioutils.TempDir("", "backup")
defer os.RemoveAll(tmpdir)
content := []byte("content")
ioutil.WriteFile(path.Join(tmpdir, "outer"), content, 0600)
os.MkdirAll(path.Join(tmpdir, "dir"), 0700)
@@ -37,7 +40,9 @@ func Test_shouldCreateArhive(t *testing.T) {
assert.Nil(t, err)
assert.Equal(t, filepath.Join(tmpdir, fmt.Sprintf("%s.tar.gz", filepath.Base(tmpdir))), gzPath)
extractionDir := t.TempDir()
extractionDir, _ := ioutils.TempDir("", "extract")
defer os.RemoveAll(extractionDir)
cmd := exec.Command("tar", "-xzf", gzPath, "-C", extractionDir)
err = cmd.Run()
if err != nil {
@@ -58,7 +63,9 @@ func Test_shouldCreateArhive(t *testing.T) {
}
func Test_shouldCreateArhiveXXXXX(t *testing.T) {
tmpdir := t.TempDir()
tmpdir, _ := ioutils.TempDir("", "backup")
defer os.RemoveAll(tmpdir)
content := []byte("content")
ioutil.WriteFile(path.Join(tmpdir, "outer"), content, 0600)
os.MkdirAll(path.Join(tmpdir, "dir"), 0700)
@@ -69,7 +76,9 @@ func Test_shouldCreateArhiveXXXXX(t *testing.T) {
assert.Nil(t, err)
assert.Equal(t, filepath.Join(tmpdir, fmt.Sprintf("%s.tar.gz", filepath.Base(tmpdir))), gzPath)
extractionDir := t.TempDir()
extractionDir, _ := ioutils.TempDir("", "extract")
defer os.RemoveAll(extractionDir)
r, _ := os.Open(gzPath)
ExtractTarGz(r, extractionDir)
if err != nil {
+6 -2
View File
@@ -2,12 +2,16 @@ package archive
import (
"github.com/stretchr/testify/assert"
"io/ioutil"
"os"
"path/filepath"
"testing"
)
func TestUnzipFile(t *testing.T) {
dir := t.TempDir()
dir, err := ioutil.TempDir("", "unzip-test-")
assert.NoError(t, err)
defer os.RemoveAll(dir)
/*
Archive structure.
├── 0
@@ -17,7 +21,7 @@ func TestUnzipFile(t *testing.T) {
└── 0.txt
*/
err := UnzipFile("./testdata/sample_archive.zip", dir)
err = UnzipFile("./testdata/sample_archive.zip", dir)
assert.NoError(t, err)
archiveDir := dir + "/sample_archive"
+4 -5
View File
@@ -7,14 +7,13 @@ import (
"path/filepath"
"time"
"github.com/pkg/errors"
"github.com/portainer/portainer/api/archive"
"github.com/portainer/portainer/api/crypto"
"github.com/portainer/portainer/api/dataservices"
"github.com/portainer/portainer/api/filesystem"
"github.com/portainer/portainer/api/http/offlinegate"
"github.com/pkg/errors"
"github.com/rs/zerolog/log"
"github.com/sirupsen/logrus"
)
const rwxr__r__ os.FileMode = 0744
@@ -48,9 +47,9 @@ func CreateBackupArchive(password string, gate *offlinegate.OfflineGate, datasto
err := datastore.Export(exportFilename)
if err != nil {
log.Error().Err(err).Str("filename", exportFilename).Msg("failed to export")
logrus.WithError(err).Debugf("failed to export to %s", exportFilename)
} else {
log.Debug().Str("filename", exportFilename).Msg("file exported")
logrus.Debugf("exported to %s", exportFilename)
}
}
-9
View File
@@ -1,9 +0,0 @@
package build
// Variables to be set during the build time
var BuildNumber string
var ImageTag string
var NodejsVersion string
var YarnVersion string
var WebpackVersion string
var GoVersion string
+14 -53
View File
@@ -3,17 +3,16 @@ package chisel
import (
"context"
"fmt"
"log"
"net/http"
"sync"
"time"
"github.com/dchest/uniuri"
chserver "github.com/jpillora/chisel/server"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
"github.com/portainer/portainer/api/http/proxy"
"github.com/dchest/uniuri"
chserver "github.com/jpillora/chisel/server"
"github.com/rs/zerolog/log"
)
const (
@@ -65,11 +64,7 @@ func (service *Service) pingAgent(endpointID portainer.EndpointID) error {
// KeepTunnelAlive keeps the tunnel of the given environment for maxAlive duration, or until ctx is done
func (service *Service) KeepTunnelAlive(endpointID portainer.EndpointID, ctx context.Context, maxAlive time.Duration) {
go func() {
log.Debug().
Int("endpoint_id", int(endpointID)).
Float64("max_alive_minutes", maxAlive.Minutes()).
Msg("start")
log.Printf("[DEBUG] [chisel,KeepTunnelAlive] [endpoint_id: %d] [message: start for %.0f minutes]\n", endpointID, maxAlive.Minutes())
maxAliveTicker := time.NewTicker(maxAlive)
defer maxAliveTicker.Stop()
pingTicker := time.NewTicker(tunnelCleanupInterval)
@@ -81,25 +76,14 @@ func (service *Service) KeepTunnelAlive(endpointID portainer.EndpointID, ctx con
service.SetTunnelStatusToActive(endpointID)
err := service.pingAgent(endpointID)
if err != nil {
log.Debug().
Int("endpoint_id", int(endpointID)).
Err(err).
Msg("ping agent")
log.Printf("[DEBUG] [chisel,KeepTunnelAlive] [endpoint_id: %d] [warning: ping agent err=%s]\n", endpointID, err)
}
case <-maxAliveTicker.C:
log.Debug().
Int("endpoint_id", int(endpointID)).
Float64("timeout_minutes", maxAlive.Minutes()).
Msg("tunnel keep alive timeout")
log.Printf("[DEBUG] [chisel,KeepTunnelAlive] [endpoint_id: %d] [message: stop as %.0f minutes timeout]\n", endpointID, maxAlive.Minutes())
return
case <-ctx.Done():
err := ctx.Err()
log.Debug().
Int("endpoint_id", int(endpointID)).
Err(err).
Msg("tunnel stop")
log.Printf("[DEBUG] [chisel,KeepTunnelAlive] [endpoint_id: %d] [message: stop as err=%s]\n", endpointID, err)
return
}
}
@@ -178,10 +162,7 @@ func (service *Service) retrievePrivateKeySeed() (string, error) {
}
func (service *Service) startTunnelVerificationLoop() {
log.Debug().
Float64("check_interval_seconds", tunnelCleanupInterval.Seconds()).
Msg("starting tunnel management process")
log.Printf("[DEBUG] [chisel, monitoring] [check_interval_seconds: %f] [message: starting tunnel management process]", tunnelCleanupInterval.Seconds())
ticker := time.NewTicker(tunnelCleanupInterval)
for {
@@ -189,12 +170,10 @@ func (service *Service) startTunnelVerificationLoop() {
case <-ticker.C:
service.checkTunnels()
case <-service.shutdownCtx.Done():
log.Debug().Msg("shutting down tunnel service")
log.Println("[DEBUG] Shutting down tunnel service")
if err := service.StopTunnelServer(); err != nil {
log.Debug().Err(err).Msg("stopped tunnel service")
log.Printf("Stopped tunnel service: %s", err)
}
ticker.Stop()
return
}
@@ -216,40 +195,22 @@ func (service *Service) checkTunnels() {
}
elapsed := time.Since(tunnel.LastActivity)
log.Debug().
Int("endpoint_id", int(endpointID)).
Str("status", tunnel.Status).
Float64("status_time_seconds", elapsed.Seconds()).
Msg("environment tunnel monitoring")
log.Printf("[DEBUG] [chisel,monitoring] [endpoint_id: %d] [status: %s] [status_time_seconds: %f] [message: environment tunnel monitoring]", endpointID, tunnel.Status, elapsed.Seconds())
if tunnel.Status == portainer.EdgeAgentManagementRequired && elapsed.Seconds() < requiredTimeout.Seconds() {
continue
} else if tunnel.Status == portainer.EdgeAgentManagementRequired && elapsed.Seconds() > requiredTimeout.Seconds() {
log.Debug().
Int("endpoint_id", int(endpointID)).
Str("status", tunnel.Status).
Float64("status_time_seconds", elapsed.Seconds()).
Float64("timeout_seconds", requiredTimeout.Seconds()).
Msg("REQUIRED state timeout exceeded")
log.Printf("[DEBUG] [chisel,monitoring] [endpoint_id: %d] [status: %s] [status_time_seconds: %f] [timeout_seconds: %f] [message: REQUIRED state timeout exceeded]", endpointID, tunnel.Status, elapsed.Seconds(), requiredTimeout.Seconds())
}
if tunnel.Status == portainer.EdgeAgentActive && elapsed.Seconds() < activeTimeout.Seconds() {
continue
} else if tunnel.Status == portainer.EdgeAgentActive && elapsed.Seconds() > activeTimeout.Seconds() {
log.Debug().
Int("endpoint_id", int(endpointID)).
Str("status", tunnel.Status).
Float64("status_time_seconds", elapsed.Seconds()).
Float64("timeout_seconds", activeTimeout.Seconds()).
Msg("ACTIVE state timeout exceeded")
log.Printf("[DEBUG] [chisel,monitoring] [endpoint_id: %d] [status: %s] [status_time_seconds: %f] [timeout_seconds: %f] [message: ACTIVE state timeout exceeded]", endpointID, tunnel.Status, elapsed.Seconds(), activeTimeout.Seconds())
err := service.snapshotEnvironment(endpointID, tunnel.Port)
if err != nil {
log.Error().
Int("endpoint_id", int(endpointID)).Err(
err).
Msg("unable to snapshot Edge environment")
log.Printf("[ERROR] [snapshot] Unable to snapshot Edge environment (id: %d): %s", endpointID, err)
}
}
+7 -8
View File
@@ -2,14 +2,15 @@ package cli
import (
"errors"
"os"
"path/filepath"
"strings"
"log"
"time"
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
"os"
"path/filepath"
"strings"
"gopkg.in/alecthomas/kingpin.v2"
)
@@ -61,8 +62,6 @@ func (*Service) ParseFlags(version string) (*portainer.CLIFlags, error) {
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(),
LogLevel: kingpin.Flag("log-level", "Set the minimum logging level to show").Default("INFO").Enum("DEBUG", "INFO", "WARN", "ERROR"),
LogMode: kingpin.Flag("log-mode", "Set the logging output mode").Default("PRETTY").Enum("PRETTY", "JSON"),
}
kingpin.Parse()
@@ -102,11 +101,11 @@ func (*Service) ValidateFlags(flags *portainer.CLIFlags) error {
func displayDeprecationWarnings(flags *portainer.CLIFlags) {
if *flags.NoAnalytics {
log.Warn().Msg("the --no-analytics flag has been kept to allow migration of instances running a previous version of Portainer with this flag enabled, to version 2.0 where enabling this flag will have no effect")
log.Println("Warning: The --no-analytics flag has been kept to allow migration of instances running a previous version of Portainer with this flag enabled, to version 2.0 where enabling this flag will have no effect.")
}
if *flags.SSL {
log.Warn().Msg("SSL is enabled by default and there is no need for the --ssl flag, it has been kept to allow migration of instances running a previous version of Portainer with this flag enabled")
log.Println("Warning: SSL is enabled by default and there is no need for the --ssl flag. It has been kept to allow migration of instances running a previous version of Portainer with this flag enabled")
}
}
+7 -6
View File
@@ -1,10 +1,11 @@
package main
import (
"log"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/datastore"
"github.com/rs/zerolog/log"
"github.com/sirupsen/logrus"
)
func importFromJson(fileService portainer.FileService, store *datastore.Store) {
@@ -12,17 +13,17 @@ func importFromJson(fileService portainer.FileService, store *datastore.Store) {
importFile := "/data/import.json"
if exists, _ := fileService.FileExists(importFile); exists {
if err := store.Import(importFile); err != nil {
log.Error().Str("filename", importFile).Err(err).Msg("import failed")
logrus.WithError(err).Debugf("Import %s failed", importFile)
// TODO: should really rollback on failure, but then we have nothing.
} else {
log.Info().Str("filename", importFile).Msg("successfully imported the file to a new portainer database")
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.Fatal().Err(err).Msg("failed initializing data store")
log.Fatalf("Failed initializing data store: %v", err)
}
}
}
+10 -45
View File
@@ -1,55 +1,20 @@
package main
import (
"fmt"
stdlog "log"
"os"
"log"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/rs/zerolog/pkgerrors"
"github.com/sirupsen/logrus"
)
func configureLogger() {
zerolog.ErrorStackFieldName = "stack_trace"
zerolog.ErrorStackMarshaler = pkgerrors.MarshalStack
zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
logger := logrus.New() // logger is to implicitly substitute stdlib's log
log.SetOutput(logger.Writer())
stdlog.SetFlags(0)
stdlog.SetOutput(log.Logger)
formatter := &logrus.TextFormatter{DisableTimestamp: false, DisableLevelTruncation: true}
log.Logger = log.Logger.With().Caller().Stack().Logger()
}
func setLoggingLevel(level string) {
switch level {
case "ERROR":
zerolog.SetGlobalLevel(zerolog.ErrorLevel)
case "WARN":
zerolog.SetGlobalLevel(zerolog.WarnLevel)
case "INFO":
zerolog.SetGlobalLevel(zerolog.InfoLevel)
case "DEBUG":
zerolog.SetGlobalLevel(zerolog.DebugLevel)
}
}
func setLoggingMode(mode string) {
switch mode {
case "PRETTY":
log.Logger = log.Output(zerolog.ConsoleWriter{
Out: os.Stderr,
NoColor: true,
TimeFormat: "2006/01/02 03:04PM",
FormatMessage: formatMessage})
case "JSON":
log.Logger = log.Output(os.Stderr)
}
}
func formatMessage(i interface{}) string {
if i == nil {
return ""
}
return fmt.Sprintf("%s |", i)
logger.SetFormatter(formatter)
logrus.SetFormatter(formatter)
logger.SetLevel(logrus.DebugLevel)
logrus.SetLevel(logrus.DebugLevel)
}
+63 -107
View File
@@ -4,16 +4,18 @@ import (
"context"
"crypto/sha256"
"fmt"
"log"
"os"
"path"
"strconv"
"strings"
"time"
"github.com/sirupsen/logrus"
"github.com/portainer/libhelm"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/apikey"
"github.com/portainer/portainer/api/build"
"github.com/portainer/portainer/api/chisel"
"github.com/portainer/portainer/api/cli"
"github.com/portainer/portainer/api/crypto"
@@ -42,38 +44,34 @@ import (
"github.com/portainer/portainer/api/oauth"
"github.com/portainer/portainer/api/scheduler"
"github.com/portainer/portainer/api/stacks"
"github.com/rs/zerolog/log"
)
func initCLI() *portainer.CLIFlags {
var cliService portainer.CLIService = &cli.Service{}
flags, err := cliService.ParseFlags(portainer.APIVersion)
if err != nil {
log.Fatal().Err(err).Msg("failed parsing flags")
logrus.Fatalf("Failed parsing flags: %v", err)
}
err = cliService.ValidateFlags(flags)
if err != nil {
log.Fatal().Err(err).Msg("failed validating flags")
logrus.Fatalf("Failed validating flags:%v", err)
}
return flags
}
func initFileService(dataStorePath string) portainer.FileService {
fileService, err := filesystem.NewService(dataStorePath, "")
if err != nil {
log.Fatal().Err(err).Msg("failed creating file service")
logrus.Fatalf("Failed creating file service: %v", err)
}
return fileService
}
func initDataStore(flags *portainer.CLIFlags, secretKey []byte, fileService portainer.FileService, shutdownCtx context.Context) dataservices.DataStore {
connection, err := database.NewDatabase("boltdb", *flags.Data, secretKey)
if err != nil {
log.Fatal().Err(err).Msg("failed creating database connection")
logrus.Fatalf("failed creating database connection: %s", err)
}
if bconn, ok := connection.(*boltdb.DbConnection); ok {
@@ -81,31 +79,30 @@ func initDataStore(flags *portainer.CLIFlags, secretKey []byte, fileService port
bconn.MaxBatchDelay = *flags.MaxBatchDelay
bconn.InitialMmapSize = *flags.InitialMmapSize
} else {
log.Fatal().Msg("failed creating database connection: expecting a boltdb database type but a different one was received")
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 {
log.Fatal().Err(err).Msg("failed opening store")
logrus.Fatalf("Failed opening store: %v", err)
}
if *flags.Rollback {
err := store.Rollback(false)
if err != nil {
log.Fatal().Err(err).Msg("failed rolling back")
logrus.Fatalf("Failed rolling back: %v", err)
}
log.Info().Msg("exiting rollback")
logrus.Println("Exiting rollback")
os.Exit(0)
return nil
}
// Init sets some defaults - it's basically a migration
err = store.Init()
if err != nil {
log.Fatal().Err(err).Msg("failed initializing data store")
logrus.Fatalf("Failed initializing data store: %v", err)
}
if isNew {
@@ -114,25 +111,24 @@ func initDataStore(flags *portainer.CLIFlags, secretKey []byte, fileService port
err := updateSettingsFromFlags(store, flags)
if err != nil {
log.Fatal().Err(err).Msg("failed updating settings from flags")
logrus.Fatalf("Failed updating settings from flags: %v", err)
}
} else {
storedVersion, err := store.VersionService.DBVersion()
if err != nil {
log.Fatal().Err(err).Msg("failure during creation of new database")
logrus.Fatalf("Something Failed during creation of new database: %v", err)
}
if storedVersion != portainer.DBVersion {
err = store.MigrateData()
if err != nil {
log.Fatal().Err(err).Msg("failed migration")
logrus.Fatalf("Failed migration: %v", err)
}
}
}
err = updateSettingsFromFlags(store, flags)
if err != nil {
log.Fatal().Err(err).Msg("failed updating settings from flags")
log.Fatalf("Failed updating settings from flags: %v", err)
}
// this is for the db restore functionality - needs more tests.
@@ -144,19 +140,19 @@ func initDataStore(flags *portainer.CLIFlags, secretKey []byte, fileService port
err := store.Export(exportFilename)
if err != nil {
log.Error().Str("filename", exportFilename).Err(err).Msg("failed to export")
logrus.WithError(err).Debugf("Failed to export to %s", exportFilename)
} else {
log.Debug().Str("filename", exportFilename).Msg("exported")
logrus.Debugf("exported to %s", exportFilename)
}
connection.Close()
}()
return store
}
func initComposeStackManager(assetsPath string, configPath string, reverseTunnelService portainer.ReverseTunnelService, proxyManager *proxy.Manager) portainer.ComposeStackManager {
composeWrapper, err := exec.NewComposeStackManager(assetsPath, configPath, proxyManager)
if err != nil {
log.Fatal().Err(err).Msg("failed creating compose manager")
logrus.Fatalf("Failed creating compose manager: %v", err)
}
return composeWrapper
@@ -190,7 +186,6 @@ func initJWTService(userSessionTimeout string, dataStore dataservices.DataStore)
if err != nil {
return nil, err
}
return jwtService, nil
}
@@ -210,8 +205,8 @@ func initOAuthService() portainer.OAuthService {
return oauth.NewService()
}
func initGitService(ctx context.Context) portainer.GitService {
return git.NewService(ctx)
func initGitService() portainer.GitService {
return git.NewService()
}
func initSSLService(addr, certPath, keyPath string, fileService portainer.FileService, dataStore dataservices.DataStore, shutdownTrigger context.CancelFunc) (*ssl.Service, error) {
@@ -235,8 +230,8 @@ func initDockerClientFactory(signatureService portainer.DigitalSignatureService,
return docker.NewClientFactory(signatureService, reverseTunnelService)
}
func initKubernetesClientFactory(signatureService portainer.DigitalSignatureService, reverseTunnelService portainer.ReverseTunnelService, dataStore dataservices.DataStore, instanceID, addrHTTPS, userSessionTimeout string) (*kubecli.ClientFactory, error) {
return kubecli.NewClientFactory(signatureService, reverseTunnelService, dataStore, instanceID, addrHTTPS, userSessionTimeout)
func initKubernetesClientFactory(signatureService portainer.DigitalSignatureService, reverseTunnelService portainer.ReverseTunnelService, instanceID string, dataStore dataservices.DataStore) *kubecli.ClientFactory {
return kubecli.NewClientFactory(signatureService, reverseTunnelService, instanceID, dataStore)
}
func initSnapshotService(snapshotIntervalFromFlag string, dataStore dataservices.DataStore, dockerClientFactory *docker.ClientFactory, kubernetesClientFactory *kubecli.ClientFactory, shutdownCtx context.Context) (portainer.SnapshotService, error) {
@@ -345,7 +340,11 @@ func enableFeaturesFromFlags(dataStore dataservices.DataStore, flags *portainer.
return fmt.Errorf("feature flag's '%s' value should be true or false", feat.Name)
}
log.Info().Str("feature", string(*correspondingFeature)).Bool("state", featureState).Msg("")
if featureState {
logrus.Printf("Feature %v : on", *correspondingFeature)
} else {
logrus.Printf("Feature %v : off", *correspondingFeature)
}
settings.FeatureFlagSettings[*correspondingFeature] = featureState
}
@@ -373,7 +372,7 @@ func generateAndStoreKeyPair(fileService portainer.FileService, signatureService
func initKeyPair(fileService portainer.FileService, signatureService portainer.DigitalSignatureService) error {
existingKeyPair, err := fileService.KeyPairFilesExist()
if err != nil {
log.Fatal().Err(err).Msg("failed checking for existing key pair")
logrus.Fatalf("Failed checking for existing key pair: %v", err)
}
if existingKeyPair {
@@ -443,11 +442,7 @@ func createTLSSecuredEndpoint(flags *portainer.CLIFlags, dataStore dataservices.
err := snapshotService.SnapshotEndpoint(endpoint)
if err != nil {
log.Error().
Str("endpoint", endpoint.Name).
Str("URL", endpoint.URL).
Err(err).
Msg("environment snapshot error")
logrus.Printf("http error: environment snapshot error (environment=%s, URL=%s) (err=%s)\n", endpoint.Name, endpoint.URL, err)
}
return dataStore.Endpoint().Create(endpoint)
@@ -492,10 +487,7 @@ func createUnsecuredEndpoint(endpointURL string, dataStore dataservices.DataStor
err := snapshotService.SnapshotEndpoint(endpoint)
if err != nil {
log.Error().
Str("endpoint", endpoint.Name).
Str("URL", endpoint.URL).Err(err).
Msg("environment snapshot error")
logrus.Printf("http error: environment snapshot error (environment=%s, URL=%s) (err=%s)\n", endpoint.Name, endpoint.URL, err)
}
return dataStore.Endpoint().Create(endpoint)
@@ -512,8 +504,7 @@ func initEndpoint(flags *portainer.CLIFlags, dataStore dataservices.DataStore, s
}
if len(endpoints) > 0 {
log.Info().Msg("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
}
@@ -527,9 +518,9 @@ func loadEncryptionSecretKey(keyfilename string) []byte {
content, err := os.ReadFile(path.Join("/run/secrets", keyfilename))
if err != nil {
if os.IsNotExist(err) {
log.Info().Str("filename", keyfilename).Msg("encryption key file not present")
logrus.Printf("Encryption key file `%s` not present", keyfilename)
} else {
log.Info().Err(err).Msg("error reading encryption key file")
logrus.Printf("Error reading encryption key file: %v", err)
}
return nil
@@ -546,41 +537,38 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
fileService := initFileService(*flags.Data)
encryptionKey := loadEncryptionSecretKey(*flags.SecretKeyName)
if encryptionKey == nil {
log.Info().Msg("proceeding without encryption key")
logrus.Println("Proceeding without encryption key")
}
dataStore := initDataStore(flags, encryptionKey, fileService, shutdownCtx)
if err := dataStore.CheckCurrentEdition(); err != nil {
log.Fatal().Err(err).Msg("")
logrus.Fatal(err)
}
instanceID, err := dataStore.Version().InstanceID()
if err != nil {
log.Fatal().Err(err).Msg("failed getting instance id")
logrus.Fatalf("Failed getting instance id: %v", err)
}
apiKeyService := initAPIKeyService(dataStore)
settings, err := dataStore.Settings().Settings()
if err != nil {
log.Fatal().Err(err).Msg("")
logrus.Fatal(err)
}
jwtService, err := initJWTService(settings.UserSessionTimeout, dataStore)
if err != nil {
log.Fatal().Err(err).Msg("failed initializing JWT service")
logrus.Fatalf("Failed initializing JWT service: %v", err)
}
err = enableFeaturesFromFlags(dataStore, flags)
if err != nil {
log.Fatal().Err(err).Msg("failed enabling feature flag")
logrus.Fatalf("Failed enabling feature flag: %v", err)
}
ldapService := initLDAPService()
oauthService := initOAuthService()
gitService := initGitService(shutdownCtx)
gitService := initGitService()
openAMTService := openamt.NewService()
@@ -590,27 +578,27 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
sslService, err := initSSLService(*flags.AddrHTTPS, *flags.SSLCert, *flags.SSLKey, fileService, dataStore, shutdownTrigger)
if err != nil {
log.Fatal().Err(err).Msg("")
logrus.Fatal(err)
}
sslSettings, err := sslService.GetSSLSettings()
if err != nil {
log.Fatal().Err(err).Msg("failed to get SSL settings")
logrus.Fatalf("Failed to get ssl settings: %s", err)
}
err = initKeyPair(fileService, digitalSignatureService)
if err != nil {
log.Fatal().Err(err).Msg("failed initializing key pair")
logrus.Fatalf("Failed initializing key pair: %v", err)
}
reverseTunnelService := chisel.NewService(dataStore, shutdownCtx)
dockerClientFactory := initDockerClientFactory(digitalSignatureService, reverseTunnelService)
kubernetesClientFactory, err := initKubernetesClientFactory(digitalSignatureService, reverseTunnelService, dataStore, instanceID, *flags.AddrHTTPS, settings.UserSessionTimeout)
kubernetesClientFactory := initKubernetesClientFactory(digitalSignatureService, reverseTunnelService, instanceID, dataStore)
snapshotService, err := initSnapshotService(*flags.SnapshotInterval, dataStore, dockerClientFactory, kubernetesClientFactory, shutdownCtx)
if err != nil {
log.Fatal().Err(err).Msg("failed initializing snapshot service")
logrus.Fatalf("Failed initializing snapshot service: %v", err)
}
snapshotService.Start()
@@ -631,19 +619,19 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
swarmStackManager, err := initSwarmStackManager(*flags.Assets, dockerConfigPath, digitalSignatureService, fileService, reverseTunnelService, dataStore)
if err != nil {
log.Fatal().Err(err).Msg("failed initializing swarm stack manager")
logrus.Fatalf("Failed initializing swarm stack manager: %v", err)
}
kubernetesDeployer := initKubernetesDeployer(kubernetesTokenCacheManager, kubernetesClientFactory, dataStore, reverseTunnelService, digitalSignatureService, proxyManager, *flags.Assets)
helmPackageManager, err := initHelmPackageManager(*flags.Assets)
if err != nil {
log.Fatal().Err(err).Msg("failed initializing helm package manager")
logrus.Fatalf("Failed initializing helm package manager: %v", err)
}
err = edge.LoadEdgeJobs(dataStore, reverseTunnelService)
if err != nil {
log.Fatal().Err(err).Msg("failed loading edge jobs from database")
logrus.Fatalf("Failed loading edge jobs from database: %v", err)
}
applicationStatus := initStatus(instanceID)
@@ -652,25 +640,24 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
if *flags.DemoEnvironment {
err := demoService.Init(dataStore, cryptoService)
if err != nil {
log.Fatal().Err(err).Msg("failed initializing demo environment")
log.Fatalf("failed initializing demo environment: %v", err)
}
}
err = initEndpoint(flags, dataStore, snapshotService)
if err != nil {
log.Fatal().Err(err).Msg("failed initializing environment")
logrus.Fatalf("Failed initializing environment: %v", err)
}
adminPasswordHash := ""
if *flags.AdminPasswordFile != "" {
content, err := fileService.GetFileContent(*flags.AdminPasswordFile, "")
if err != nil {
log.Fatal().Err(err).Msg("failed getting admin password file")
logrus.Fatalf("Failed getting admin password file: %v", err)
}
adminPasswordHash, err = cryptoService.Hash(strings.TrimSuffix(string(content), "\n"))
if err != nil {
log.Fatal().Err(err).Msg("failed hashing admin password")
logrus.Fatalf("Failed hashing admin password: %v", err)
}
} else if *flags.AdminPassword != "" {
adminPasswordHash = *flags.AdminPassword
@@ -679,57 +666,39 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
if adminPasswordHash != "" {
users, err := dataStore.User().UsersByRole(portainer.AdministratorRole)
if err != nil {
log.Fatal().Err(err).Msg("failed getting admin user")
logrus.Fatalf("Failed getting admin user: %v", err)
}
if len(users) == 0 {
log.Info().Msg("created admin user with the given password.")
logrus.Println("Created admin user with the given password.")
user := &portainer.User{
Username: "admin",
Role: portainer.AdministratorRole,
Password: adminPasswordHash,
}
err := dataStore.User().Create(user)
if err != nil {
log.Fatal().Err(err).Msg("failed creating admin user")
logrus.Fatalf("Failed creating admin user: %v", err)
}
} else {
log.Info().Msg("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)
if err != nil {
log.Fatal().Err(err).Msg("failed starting tunnel server")
logrus.Fatalf("Failed starting tunnel server: %v", err)
}
sslDBSettings, err := dataStore.SSLSettings().Settings()
if err != nil {
log.Fatal().Msg("failed to fetch SSL settings from DB")
logrus.Fatalf("Failed to fetch ssl settings from DB")
}
scheduler := scheduler.NewScheduler(shutdownCtx)
stackDeployer := stacks.NewStackDeployer(swarmStackManager, composeStackManager, kubernetesDeployer)
stacks.StartStackSchedules(scheduler, stackDeployer, dataStore, gitService)
// FIXME: In 2.16 we changed the way ingress controller permissions are
// stored. Instead of being stored as annotation on an ingress rule, we keep
// them in our database. However, in order to run the migration we need an
// admin kube client to run lookup the old ingress rules and compare them
// with the current existing ingress classes.
//
// Unfortunately, our migrations run as part of the database initialization
// and our kubeclients require an initialized database. So it is not
// possible to do this migration as part of our normal flow. We DO have a
// migration which toggles a boolean in kubernetes configuration that
// indicated that this "post init" migration should be run. If/when this is
// resolved we can remove this function.
err = kubernetesClientFactory.PostInitMigrateIngresses()
if err != nil {
log.Fatal().Err(err).Msg("failure during creation of new database")
}
return &http.Server{
AuthorizationService: authorizationService,
ReverseTunnelService: reverseTunnelService,
@@ -768,27 +737,14 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
}
func main() {
configureLogger()
setLoggingMode("PRETTY")
flags := initCLI()
setLoggingLevel(*flags.LogLevel)
setLoggingMode(*flags.LogMode)
configureLogger()
for {
server := buildServer(flags)
log.Info().
Str("version", portainer.APIVersion).
Str("build_number", build.BuildNumber).
Str("image_tag", build.ImageTag).
Str("nodejs_version", build.NodejsVersion).
Str("yarn_version", build.YarnVersion).
Str("webpack_version", build.WebpackVersion).
Str("go_version", build.GoVersion).
Msg("starting Portainer")
logrus.Printf("[INFO] [cmd,main] Starting Portainer version %s\n", portainer.APIVersion)
err := server.Start()
log.Info().Err(err).Msg("HTTP server exited")
logrus.Printf("[INFO] [cmd,main] Http server exited: %v\n", err)
}
}
+2 -2
View File
@@ -21,7 +21,7 @@ func (m mockKingpinSetting) SetValue(value kingpin.Value) {
func Test_enableFeaturesFromFlags(t *testing.T) {
is := assert.New(t)
_, store, teardown := datastore.MustNewTestStore(t, true, true)
_, store, teardown := datastore.MustNewTestStore(true, true)
defer teardown()
tests := []struct {
@@ -76,7 +76,7 @@ func Test_optionalFeature(t *testing.T) {
is := assert.New(t)
_, store, teardown := datastore.MustNewTestStore(t, true, true)
_, store, teardown := datastore.MustNewTestStore(true, true)
defer teardown()
// Enable the test feature
+7 -3
View File
@@ -7,11 +7,13 @@ import (
"path/filepath"
"testing"
"github.com/docker/docker/pkg/ioutils"
"github.com/stretchr/testify/assert"
)
func Test_encryptAndDecrypt_withTheSamePassword(t *testing.T) {
tmpdir := t.TempDir()
tmpdir, _ := ioutils.TempDir("", "encrypt")
defer os.RemoveAll(tmpdir)
var (
originFilePath = filepath.Join(tmpdir, "origin")
@@ -50,7 +52,8 @@ func Test_encryptAndDecrypt_withTheSamePassword(t *testing.T) {
}
func Test_encryptAndDecrypt_withEmptyPassword(t *testing.T) {
tmpdir := t.TempDir()
tmpdir, _ := ioutils.TempDir("", "encrypt")
defer os.RemoveAll(tmpdir)
var (
originFilePath = filepath.Join(tmpdir, "origin")
@@ -89,7 +92,8 @@ func Test_encryptAndDecrypt_withEmptyPassword(t *testing.T) {
}
func Test_decryptWithDifferentPassphrase_shouldProduceWrongResult(t *testing.T) {
tmpdir := t.TempDir()
tmpdir, _ := ioutils.TempDir("", "encrypt")
defer os.RemoveAll(tmpdir)
var (
originFilePath = filepath.Join(tmpdir, "origin")
+3 -7
View File
@@ -11,8 +11,7 @@ import (
"time"
dserrors "github.com/portainer/portainer/api/dataservices/errors"
"github.com/rs/zerolog/log"
"github.com/sirupsen/logrus"
bolt "go.etcd.io/bbolt"
)
@@ -121,7 +120,7 @@ func (connection *DbConnection) NeedsEncryptionMigration() (bool, error) {
// Open opens and initializes the BoltDB database.
func (connection *DbConnection) Open() error {
log.Info().Str("filename", connection.GetDatabaseFileName()).Msg("loading PortainerDB")
logrus.Infof("Loading PortainerDB: %s", connection.GetDatabaseFileName())
// Now we open the db
databasePath := connection.GetDatabaseFilePath()
@@ -349,7 +348,6 @@ func (connection *DbConnection) CreateObjectWithSetSequence(bucketName string, i
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)
@@ -364,7 +362,6 @@ func (connection *DbConnection) GetAll(bucketName string, obj interface{}, appen
return nil
})
return err
}
@@ -414,7 +411,7 @@ func (connection *DbConnection) RestoreMetadata(s map[string]interface{}) error
for bucketName, v := range s {
id, ok := v.(float64) // JSON ints are unmarshalled to interface as float64. See: https://pkg.go.dev/encoding/json#Decoder.Decode
if !ok {
log.Error().Str("bucket", bucketName).Msg("failed to restore metadata to bucket, skipped")
logrus.Errorf("Failed to restore metadata to bucket %s, skipped", bucketName)
continue
}
@@ -423,7 +420,6 @@ func (connection *DbConnection) RestoreMetadata(s map[string]interface{}) error
if err != nil {
return err
}
return bucket.SetSequence(uint64(id))
})
}
+5 -17
View File
@@ -4,7 +4,7 @@ import (
"encoding/json"
"time"
"github.com/rs/zerolog/log"
"github.com/sirupsen/logrus"
bolt "go.etcd.io/bbolt"
)
@@ -28,11 +28,11 @@ func backupMetadata(connection *bolt.DB) (map[string]interface{}, error) {
// ExportJSON creates a JSON representation from a DbConnection. You can include
// the database's metadata or ignore it. Ensure the database is closed before
// using this function.
// using this function
// 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, metadata bool) ([]byte, error) {
log.Debug().Str("databasePath", databasePath).Msg("exportJson")
logrus.WithField("databasePath", databasePath).Infof("exportJson")
connection, err := bolt.Open(databasePath, 0600, &bolt.Options{Timeout: 1 * time.Second, ReadOnly: true})
if err != nil {
@@ -44,9 +44,8 @@ func (c *DbConnection) ExportJson(databasePath string, metadata bool) ([]byte, e
if metadata {
meta, err := backupMetadata(connection)
if err != nil {
log.Error().Err(err).Msg("failed exporting metadata")
logrus.WithError(err).Errorf("Failed exporting metadata: %v", err)
}
backup["__metadata"] = meta
}
@@ -60,31 +59,22 @@ func (c *DbConnection) ExportJson(databasePath string, metadata bool) ([]byte, e
if v == nil {
continue
}
var obj interface{}
err := c.UnmarshalObject(v, &obj)
if err != nil {
log.Error().
Str("bucket", bucketName).
Str("object", string(v)).
Err(err).
Msg("failed to unmarshal")
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
return nil
}
if len(list) > 0 {
if bucketName == "ssl" ||
bucketName == "settings" ||
@@ -101,10 +91,8 @@ func (c *DbConnection) ExportJson(databasePath string, metadata bool) ([]byte, e
return nil
})
return err
})
if err != nil {
return []byte("{}"), err
}
@@ -1,17 +0,0 @@
package models
type (
K8sConfigMapOrSecret struct {
UID string `json:"UID"`
Name string `json:"Name"`
Namespace string `json:"Namespace"`
CreationDate string `json:"CreationDate"`
Annotations map[string]string `json:"Annotations"`
Data map[string]string `json:"Data"`
Applications []string `json:"Applications"`
IsSecret bool `json:"IsSecret"`
// SecretType will be an empty string for config maps.
SecretType string `json:"SecretType"`
}
)
-75
View File
@@ -1,75 +0,0 @@
package models
import (
"errors"
"net/http"
)
type (
K8sIngressController struct {
Name string `json:"Name"`
ClassName string `json:"ClassName"`
Type string `json:"Type"`
Availability bool `json:"Availability"`
New bool `json:"New"`
Used bool `json:"Used"`
}
K8sIngressControllers []K8sIngressController
K8sIngressInfo struct {
Name string `json:"Name"`
UID string `json:"UID"`
Type string `json:"Type"`
Namespace string `json:"Namespace"`
ClassName string `json:"ClassName"`
Annotations map[string]string `json:"Annotations"`
Hosts []string `json:"Hosts"`
Paths []K8sIngressPath `json:"Paths"`
TLS []K8sIngressTLS `json:"TLS"`
}
K8sIngressTLS struct {
Hosts []string `json:"Hosts"`
SecretName string `json:"SecretName"`
}
K8sIngressPath struct {
IngressName string `json:"IngressName"`
Host string `json:"Host"`
ServiceName string `json:"ServiceName"`
Port int `json:"Port"`
Path string `json:"Path"`
PathType string `json:"PathType"`
}
// K8sIngressDeleteRequests is a mapping of namespace names to a slice of
// ingress names.
K8sIngressDeleteRequests map[string][]string
)
func (r K8sIngressControllers) Validate(request *http.Request) error {
return nil
}
func (r K8sIngressInfo) Validate(request *http.Request) error {
if r.Name == "" {
return errors.New("missing ingress name from the request payload")
}
if r.Namespace == "" {
return errors.New("missing ingress Namespace from the request payload")
}
return nil
}
func (r K8sIngressDeleteRequests) Validate(request *http.Request) error {
if len(r) == 0 {
return errors.New("missing deletion request list in payload")
}
for ns := range r {
if len(ns) == 0 {
return errors.New("deletion given with empty namespace")
}
}
return nil
}
-12
View File
@@ -1,12 +0,0 @@
package models
import "net/http"
type K8sNamespaceDetails struct {
Name string `json:"Name"`
Annotations map[string]string `json:"Annotations"`
}
func (r *K8sNamespaceDetails) Validate(request *http.Request) error {
return nil
}
-64
View File
@@ -1,64 +0,0 @@
package models
import (
"errors"
"net/http"
)
type (
K8sServiceInfo struct {
Name string `json:"Name"`
UID string `json:"UID"`
Type string `json:"Type"`
Namespace string `json:"Namespace"`
Annotations map[string]string `json:"Annotations"`
CreationTimestamp string `json:"CreationTimestamp"`
Labels map[string]string `json:"Labels"`
AllocateLoadBalancerNodePorts *bool `json:"AllocateLoadBalancerNodePorts,omitempty"`
Ports []K8sServicePort `json:"Ports"`
Selector map[string]string `json:"Selector"`
IngressStatus []K8sServiceIngress `json:"IngressStatus"`
}
K8sServicePort struct {
Name string `json:"Name"`
NodePort int `json:"NodePort"`
Port int `json:"Port"`
Protocol string `json:"Protocol"`
TargetPort int `json:"TargetPort"`
}
K8sServiceIngress struct {
IP string `json:"IP"`
Host string `json:"Host"`
}
// K8sServiceDeleteRequests is a mapping of namespace names to a slice of
// service names.
K8sServiceDeleteRequests map[string][]string
)
func (s *K8sServiceInfo) Validate(request *http.Request) error {
if s.Name == "" {
return errors.New("missing service name from the request payload")
}
if s.Namespace == "" {
return errors.New("missing service namespace from the request payload")
}
if s.Ports == nil {
return errors.New("missing service ports from the request payload")
}
return nil
}
func (r K8sServiceDeleteRequests) Validate(request *http.Request) error {
if len(r) == 0 {
return errors.New("missing deletion request list in payload")
}
for ns := range r {
if len(ns) == 0 {
return errors.New("deletion given with empty namespace")
}
}
return nil
}
@@ -6,8 +6,7 @@ import (
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices/errors"
"github.com/rs/zerolog/log"
"github.com/sirupsen/logrus"
)
const (
@@ -42,14 +41,12 @@ func (service *Service) GetAPIKeysByUserID(userID portainer.UserID) ([]portainer
func(obj interface{}) (interface{}, error) {
record, ok := obj.(*portainer.APIKey)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to APIKey object")
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
})
@@ -67,21 +64,18 @@ func (service *Service) GetAPIKeyByDigest(digest []byte) (*portainer.APIKey, err
func(obj interface{}) (interface{}, error) {
key, ok := obj.(*portainer.APIKey)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to APIKey object")
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
}
@@ -4,8 +4,7 @@ import (
"fmt"
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
"github.com/sirupsen/logrus"
)
const (
@@ -45,11 +44,10 @@ func (service *Service) CustomTemplates() ([]portainer.CustomTemplate, error) {
//var tag portainer.Tag
customTemplate, ok := obj.(*portainer.CustomTemplate)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to CustomTemplate object")
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
})
+2 -4
View File
@@ -4,8 +4,7 @@ import (
"fmt"
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
"github.com/sirupsen/logrus"
)
const (
@@ -44,11 +43,10 @@ func (service *Service) EdgeGroups() ([]portainer.EdgeGroup, error) {
func(obj interface{}) (interface{}, error) {
group, ok := obj.(*portainer.EdgeGroup)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to EdgeGroup object")
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
})
+3 -5
View File
@@ -4,8 +4,7 @@ import (
"fmt"
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
"github.com/sirupsen/logrus"
)
const (
@@ -42,14 +41,13 @@ func (service *Service) EdgeJobs() ([]portainer.EdgeJob, error) {
BucketName,
&portainer.EdgeJob{},
func(obj interface{}) (interface{}, error) {
//var tag portainer.Tag
job, ok := obj.(*portainer.EdgeJob)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to EdgeJob object")
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
})
+2 -5
View File
@@ -4,8 +4,7 @@ import (
"fmt"
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
"github.com/sirupsen/logrus"
)
const (
@@ -45,12 +44,10 @@ func (service *Service) EdgeStacks() ([]portainer.EdgeStack, error) {
//var tag portainer.Tag
stack, ok := obj.(*portainer.EdgeStack)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to EdgeStack object")
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
})
@@ -1,185 +0,0 @@
package edgeupdateschedule
import (
"fmt"
"sync"
"github.com/pkg/errors"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/edgetypes"
"github.com/sirupsen/logrus"
)
const (
// BucketName represents the name of the bucket where this service stores data.
BucketName = "edge_update_schedule"
)
// Service represents a service for managing Edge Update Schedule data.
type Service struct {
connection portainer.Connection
mu sync.Mutex
idxActiveSchedules map[portainer.EndpointID]*edgetypes.EndpointUpdateScheduleRelation
}
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
}
service := &Service{
connection: connection,
}
service.idxActiveSchedules = map[portainer.EndpointID]*edgetypes.EndpointUpdateScheduleRelation{}
schedules, err := service.List()
if err != nil {
return nil, errors.WithMessage(err, "Unable to list schedules")
}
for _, schedule := range schedules {
service.setRelation(&schedule)
}
return service, nil
}
func (service *Service) ActiveSchedule(environmentID portainer.EndpointID) *edgetypes.EndpointUpdateScheduleRelation {
service.mu.Lock()
defer service.mu.Unlock()
return service.idxActiveSchedules[environmentID]
}
func (service *Service) ActiveSchedules(environmentsIDs []portainer.EndpointID) []edgetypes.EndpointUpdateScheduleRelation {
service.mu.Lock()
defer service.mu.Unlock()
schedules := []edgetypes.EndpointUpdateScheduleRelation{}
for _, environmentID := range environmentsIDs {
if s, ok := service.idxActiveSchedules[environmentID]; ok {
schedules = append(schedules, *s)
}
}
return schedules
}
// List return an array containing all the items in the bucket.
func (service *Service) List() ([]edgetypes.UpdateSchedule, error) {
var list = make([]edgetypes.UpdateSchedule, 0)
err := service.connection.GetAll(
BucketName,
&edgetypes.UpdateSchedule{},
func(obj interface{}) (interface{}, error) {
item, ok := obj.(*edgetypes.UpdateSchedule)
if !ok {
logrus.WithField("obj", obj).Errorf("Failed to convert to EdgeUpdateSchedule object")
return nil, fmt.Errorf("failed to convert to EdgeUpdateSchedule object: %s", obj)
}
list = append(list, *item)
return &edgetypes.UpdateSchedule{}, nil
})
return list, err
}
// Item returns a item by ID.
func (service *Service) Item(ID edgetypes.UpdateScheduleID) (*edgetypes.UpdateSchedule, error) {
var item edgetypes.UpdateSchedule
identifier := service.connection.ConvertToKey(int(ID))
err := service.connection.GetObject(BucketName, identifier, &item)
if err != nil {
return nil, err
}
return &item, nil
}
// Create assign an ID to a new object and saves it.
func (service *Service) Create(item *edgetypes.UpdateSchedule) error {
err := service.connection.CreateObject(
BucketName,
func(id uint64) (int, interface{}) {
item.ID = edgetypes.UpdateScheduleID(id)
return int(item.ID), item
},
)
if err != nil {
return err
}
return service.setRelation(item)
}
// Update updates an item.
func (service *Service) Update(id edgetypes.UpdateScheduleID, item *edgetypes.UpdateSchedule) error {
identifier := service.connection.ConvertToKey(int(id))
err := service.connection.UpdateObject(BucketName, identifier, item)
if err != nil {
return err
}
service.cleanRelation(id)
return service.setRelation(item)
}
// Delete deletes an item.
func (service *Service) Delete(id edgetypes.UpdateScheduleID) error {
service.cleanRelation(id)
identifier := service.connection.ConvertToKey(int(id))
return service.connection.DeleteObject(BucketName, identifier)
}
func (service *Service) cleanRelation(id edgetypes.UpdateScheduleID) {
service.mu.Lock()
defer service.mu.Unlock()
for _, schedule := range service.idxActiveSchedules {
if schedule != nil && schedule.ScheduleID == id {
delete(service.idxActiveSchedules, schedule.EnvironmentID)
}
}
}
func (service *Service) setRelation(schedule *edgetypes.UpdateSchedule) error {
service.mu.Lock()
defer service.mu.Unlock()
for environmentID, environmentStatus := range schedule.Status {
if environmentStatus.Status != edgetypes.UpdateScheduleStatusPending {
continue
}
// this should never happen
if service.idxActiveSchedules[environmentID] != nil && service.idxActiveSchedules[environmentID].ScheduleID != schedule.ID {
return errors.New("Multiple schedules are pending for the same environment")
}
service.idxActiveSchedules[environmentID] = &edgetypes.EndpointUpdateScheduleRelation{
EnvironmentID: environmentID,
ScheduleID: schedule.ID,
TargetVersion: environmentStatus.TargetVersion,
Status: environmentStatus.Status,
Error: environmentStatus.Error,
Type: schedule.Type,
}
}
return nil
}
+2 -5
View File
@@ -4,8 +4,7 @@ import (
"fmt"
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
"github.com/sirupsen/logrus"
)
const (
@@ -69,12 +68,10 @@ func (service *Service) Endpoints() ([]portainer.Endpoint, error) {
func(obj interface{}) (interface{}, error) {
endpoint, ok := obj.(*portainer.Endpoint)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Endpoint object")
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
})
@@ -4,8 +4,7 @@ import (
"fmt"
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
"github.com/sirupsen/logrus"
)
const (
@@ -67,14 +66,13 @@ func (service *Service) EndpointGroups() ([]portainer.EndpointGroup, error) {
BucketName,
&portainer.EndpointGroup{},
func(obj interface{}) (interface{}, error) {
//var tag portainer.Tag
endpointGroup, ok := obj.(*portainer.EndpointGroup)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to EndpointGroup object")
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
})
@@ -4,8 +4,7 @@ import (
"fmt"
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
"github.com/sirupsen/logrus"
)
const (
@@ -34,7 +33,7 @@ func NewService(connection portainer.Connection) (*Service, error) {
}, nil
}
// EndpointRelations returns an array of all EndpointRelations
//EndpointRelations returns an array of all EndpointRelations
func (service *Service) EndpointRelations() ([]portainer.EndpointRelation, error) {
var all = make([]portainer.EndpointRelation, 0)
@@ -44,12 +43,10 @@ func (service *Service) EndpointRelations() ([]portainer.EndpointRelation, error
func(obj interface{}) (interface{}, error) {
r, ok := obj.(*portainer.EndpointRelation)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to EndpointRelation object")
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
})
+2 -5
View File
@@ -4,8 +4,7 @@ import (
"fmt"
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
"github.com/sirupsen/logrus"
)
const (
@@ -57,12 +56,10 @@ func (service *Service) Extensions() ([]portainer.Extension, error) {
func(obj interface{}) (interface{}, error) {
extension, ok := obj.(*portainer.Extension)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Extension object")
logrus.WithField("obj", obj).Errorf("Failed to convert to Extension object")
return nil, fmt.Errorf("Failed to convert to Extension object: %s", obj)
}
extensions = append(extensions, *extension)
return &portainer.Extension{}, nil
})
+3 -5
View File
@@ -4,8 +4,7 @@ import (
"fmt"
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
"github.com/sirupsen/logrus"
)
const (
@@ -44,9 +43,8 @@ func (service *Service) FDOProfiles() ([]portainer.FDOProfile, error) {
func(obj interface{}) (interface{}, error) {
fdoProfile, ok := obj.(*portainer.FDOProfile)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to FDOProfile object")
return nil, fmt.Errorf("Failed to convert to FDOProfile object: %s", obj)
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
@@ -4,8 +4,7 @@ import (
"fmt"
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
"github.com/sirupsen/logrus"
)
const (
@@ -34,7 +33,7 @@ func NewService(connection portainer.Connection) (*Service, error) {
}, nil
}
// HelmUserRepository returns an array of all HelmUserRepository
//HelmUserRepository returns an array of all HelmUserRepository
func (service *Service) HelmUserRepositories() ([]portainer.HelmUserRepository, error) {
var repos = make([]portainer.HelmUserRepository, 0)
@@ -44,12 +43,10 @@ func (service *Service) HelmUserRepositories() ([]portainer.HelmUserRepository,
func(obj interface{}) (interface{}, error) {
r, ok := obj.(*portainer.HelmUserRepository)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to HelmUserRepository object")
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
})
@@ -66,14 +63,12 @@ func (service *Service) HelmUserRepositoryByUserID(userID portainer.UserID) ([]p
func(obj interface{}) (interface{}, error) {
record, ok := obj.(*portainer.HelmUserRepository)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to HelmUserRepository object")
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
})
-23
View File
@@ -7,7 +7,6 @@ import (
"time"
"github.com/portainer/portainer/api/dataservices/errors"
"github.com/portainer/portainer/api/edgetypes"
portainer "github.com/portainer/portainer/api"
)
@@ -29,7 +28,6 @@ type (
EdgeGroup() EdgeGroupService
EdgeJob() EdgeJobService
EdgeStack() EdgeStackService
EdgeUpdateSchedule() EdgeUpdateScheduleService
Endpoint() EndpointService
EndpointGroup() EndpointGroupService
EndpointRelation() EndpointRelationService
@@ -40,7 +38,6 @@ type (
Role() RoleService
APIKeyRepository() APIKeyRepository
Settings() SettingsService
Snapshot() SnapshotService
SSLSettings() SSLSettingsService
Stack() StackService
Tag() TagService
@@ -84,17 +81,6 @@ type (
BucketName() string
}
EdgeUpdateScheduleService interface {
ActiveSchedule(environmentID portainer.EndpointID) *edgetypes.EndpointUpdateScheduleRelation
ActiveSchedules(environmentIDs []portainer.EndpointID) []edgetypes.EndpointUpdateScheduleRelation
List() ([]edgetypes.UpdateSchedule, error)
Item(ID edgetypes.UpdateScheduleID) (*edgetypes.UpdateSchedule, error)
Create(edgeUpdateSchedule *edgetypes.UpdateSchedule) error
Update(ID edgetypes.UpdateScheduleID, edgeUpdateSchedule *edgetypes.UpdateSchedule) error
Delete(ID edgetypes.UpdateScheduleID) error
BucketName() string
}
// EdgeStackService represents a service to manage Edge stacks
EdgeStackService interface {
EdgeStacks() ([]portainer.EdgeStack, error)
@@ -215,15 +201,6 @@ type (
BucketName() string
}
SnapshotService interface {
Snapshot(endpointID portainer.EndpointID) (*portainer.Snapshot, error)
Snapshots() ([]portainer.Snapshot, error)
UpdateSnapshot(snapshot *portainer.Snapshot) error
DeleteSnapshot(endpointID portainer.EndpointID) error
Create(snapshot *portainer.Snapshot) error
BucketName() string
}
// SSLSettingsService represents a service for managing application settings
SSLSettingsService interface {
Settings() (*portainer.SSLSettings, error)
+2 -5
View File
@@ -4,8 +4,7 @@ import (
"fmt"
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
"github.com/sirupsen/logrus"
)
const (
@@ -57,12 +56,10 @@ func (service *Service) Registries() ([]portainer.Registry, error) {
func(obj interface{}) (interface{}, error) {
registry, ok := obj.(*portainer.Registry)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Registry object")
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
})
@@ -4,8 +4,7 @@ import (
"fmt"
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
"github.com/sirupsen/logrus"
)
const (
@@ -59,7 +58,7 @@ func (service *Service) ResourceControlByResourceIDAndType(resourceID string, re
func(obj interface{}) (interface{}, error) {
rc, ok := obj.(*portainer.ResourceControl)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to ResourceControl object")
logrus.WithField("obj", obj).Errorf("Failed to convert to ResourceControl object")
return nil, fmt.Errorf("Failed to convert to ResourceControl object: %s", obj)
}
@@ -74,7 +73,6 @@ func (service *Service) ResourceControlByResourceIDAndType(resourceID string, re
return nil, stop
}
}
return &portainer.ResourceControl{}, nil
})
if err == stop {
@@ -94,12 +92,10 @@ func (service *Service) ResourceControls() ([]portainer.ResourceControl, error)
func(obj interface{}) (interface{}, error) {
rc, ok := obj.(*portainer.ResourceControl)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to ResourceControl object")
logrus.WithField("obj", obj).Errorf("Failed to convert to ResourceControl object")
return nil, fmt.Errorf("Failed to convert to ResourceControl object: %s", obj)
}
rcs = append(rcs, *rc)
return &portainer.ResourceControl{}, nil
})
+2 -5
View File
@@ -4,8 +4,7 @@ import (
"fmt"
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
"github.com/sirupsen/logrus"
)
const (
@@ -57,12 +56,10 @@ func (service *Service) Roles() ([]portainer.Role, error) {
func(obj interface{}) (interface{}, error) {
set, ok := obj.(*portainer.Role)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Role object")
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
})
+3 -8
View File
@@ -4,8 +4,7 @@ import (
"fmt"
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
"github.com/sirupsen/logrus"
)
const (
@@ -69,12 +68,10 @@ func (service *Service) Schedules() ([]portainer.Schedule, error) {
func(obj interface{}) (interface{}, error) {
schedule, ok := obj.(*portainer.Schedule)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Schedule object")
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
})
@@ -92,14 +89,12 @@ func (service *Service) SchedulesByJobType(jobType portainer.JobType) ([]portain
func(obj interface{}) (interface{}, error) {
schedule, ok := obj.(*portainer.Schedule)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Schedule object")
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
})
-76
View File
@@ -1,76 +0,0 @@
package snapshot
import (
"fmt"
portainer "github.com/portainer/portainer/api"
"github.com/sirupsen/logrus"
)
const (
BucketName = "snapshots"
)
type Service struct {
connection portainer.Connection
}
func (service *Service) BucketName() string {
return BucketName
}
func NewService(connection portainer.Connection) (*Service, error) {
err := connection.SetServiceName(BucketName)
if err != nil {
return nil, err
}
return &Service{
connection: connection,
}, nil
}
func (service *Service) Snapshot(endpointID portainer.EndpointID) (*portainer.Snapshot, error) {
var snapshot portainer.Snapshot
identifier := service.connection.ConvertToKey(int(endpointID))
err := service.connection.GetObject(BucketName, identifier, &snapshot)
if err != nil {
return nil, err
}
return &snapshot, nil
}
func (service *Service) Snapshots() ([]portainer.Snapshot, error) {
var snapshots = make([]portainer.Snapshot, 0)
err := service.connection.GetAllWithJsoniter(
BucketName,
&portainer.Snapshot{},
func(obj interface{}) (interface{}, error) {
snapshot, ok := obj.(*portainer.Snapshot)
if !ok {
logrus.WithField("obj", obj).Errorf("Failed to convert to Snapshot object")
return nil, fmt.Errorf("failed to convert to Snapshot object: %s", obj)
}
snapshots = append(snapshots, *snapshot)
return &portainer.Snapshot{}, nil
})
return snapshots, err
}
func (service *Service) UpdateSnapshot(snapshot *portainer.Snapshot) error {
identifier := service.connection.ConvertToKey(int(snapshot.EndpointID))
return service.connection.UpdateObject(BucketName, identifier, snapshot)
}
func (service *Service) DeleteSnapshot(endpointID portainer.EndpointID) error {
identifier := service.connection.ConvertToKey(int(endpointID))
return service.connection.DeleteObject(BucketName, identifier)
}
func (service *Service) Create(snapshot *portainer.Snapshot) error {
return service.connection.CreateObjectWithId(BucketName, int(snapshot.EndpointID), snapshot)
}
+7 -17
View File
@@ -4,10 +4,10 @@ import (
"fmt"
"strings"
"github.com/sirupsen/logrus"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices/errors"
"github.com/rs/zerolog/log"
)
const (
@@ -60,15 +60,13 @@ func (service *Service) StackByName(name string) (*portainer.Stack, error) {
func(obj interface{}) (interface{}, error) {
stack, ok := obj.(*portainer.Stack)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Stack object")
logrus.WithField("obj", obj).Errorf("Failed to convert to Stack object")
return nil, fmt.Errorf("Failed to convert to Stack object: %s", obj)
}
if stack.Name == name {
s = stack
return nil, stop
}
return &portainer.Stack{}, nil
})
if err == stop {
@@ -91,14 +89,12 @@ func (service *Service) StacksByName(name string) ([]portainer.Stack, error) {
func(obj interface{}) (interface{}, error) {
stack, ok := obj.(portainer.Stack)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Stack object")
logrus.WithField("obj", obj).Errorf("Failed to convert to Stack object")
return nil, fmt.Errorf("Failed to convert to Stack object: %s", obj)
}
if stack.Name == name {
stacks = append(stacks, stack)
}
return &portainer.Stack{}, nil
})
@@ -115,12 +111,10 @@ func (service *Service) Stacks() ([]portainer.Stack, error) {
func(obj interface{}) (interface{}, error) {
stack, ok := obj.(*portainer.Stack)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Stack object")
logrus.WithField("obj", obj).Errorf("Failed to convert to Stack object")
return nil, fmt.Errorf("Failed to convert to Stack object: %s", obj)
}
stacks = append(stacks, *stack)
return &portainer.Stack{}, nil
})
@@ -162,15 +156,13 @@ func (service *Service) StackByWebhookID(id string) (*portainer.Stack, error) {
s, ok = obj.(*portainer.Stack)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Stack object")
logrus.WithField("obj", obj).Errorf("Failed to convert to Stack object")
return &portainer.Stack{}, nil
}
if s.AutoUpdate != nil && strings.EqualFold(s.AutoUpdate.Webhook, id) {
return nil, stop
}
return &portainer.Stack{}, nil
})
if err == stop {
@@ -194,14 +186,12 @@ func (service *Service) RefreshableStacks() ([]portainer.Stack, error) {
func(obj interface{}) (interface{}, error) {
stack, ok := obj.(*portainer.Stack)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Stack object")
logrus.WithField("obj", obj).Errorf("Failed to convert to Stack object")
return nil, fmt.Errorf("Failed to convert to Stack object: %s", obj)
}
if stack.AutoUpdate != nil && stack.AutoUpdate.Interval != "" {
stacks = append(stacks, *stack)
}
return &portainer.Stack{}, nil
})
+2 -2
View File
@@ -29,7 +29,7 @@ func TestService_StackByWebhookID(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode. Normally takes ~1s to run.")
}
_, store, teardown := datastore.MustNewTestStore(t, true, true)
_, store, teardown := datastore.MustNewTestStore(true, true)
defer teardown()
b := stackBuilder{t: t, store: store}
@@ -87,7 +87,7 @@ func Test_RefreshableStacks(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode. Normally takes ~1s to run.")
}
_, store, teardown := datastore.MustNewTestStore(t, true, true)
_, store, teardown := datastore.MustNewTestStore(true, true)
defer teardown()
staticStack := portainer.Stack{ID: 1}
+2 -5
View File
@@ -4,8 +4,7 @@ import (
"fmt"
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
"github.com/sirupsen/logrus"
)
const (
@@ -44,12 +43,10 @@ func (service *Service) Tags() ([]portainer.Tag, error) {
func(obj interface{}) (interface{}, error) {
tag, ok := obj.(*portainer.Tag)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Tag object")
logrus.WithField("obj", obj).Errorf("Failed to convert to Tag object")
return nil, fmt.Errorf("Failed to convert to Tag object: %s", obj)
}
tags = append(tags, *tag)
return &portainer.Tag{}, nil
})
+4 -8
View File
@@ -4,10 +4,10 @@ import (
"fmt"
"strings"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices/errors"
"github.com/sirupsen/logrus"
"github.com/rs/zerolog/log"
portainer "github.com/portainer/portainer/api"
)
const (
@@ -60,15 +60,13 @@ func (service *Service) TeamByName(name string) (*portainer.Team, error) {
func(obj interface{}) (interface{}, error) {
team, ok := obj.(*portainer.Team)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Team object")
logrus.WithField("obj", obj).Errorf("Failed to convert to Team object")
return nil, fmt.Errorf("Failed to convert to Team object: %s", obj)
}
if strings.EqualFold(team.Name, name) {
t = team
return nil, stop
}
return &portainer.Team{}, nil
})
if err == stop {
@@ -91,12 +89,10 @@ func (service *Service) Teams() ([]portainer.Team, error) {
func(obj interface{}) (interface{}, error) {
team, ok := obj.(*portainer.Team)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Team object")
logrus.WithField("obj", obj).Errorf("Failed to convert to Team object")
return nil, fmt.Errorf("Failed to convert to Team object: %s", obj)
}
teams = append(teams, *team)
return &portainer.Team{}, nil
})
+3 -3
View File
@@ -10,7 +10,7 @@ import (
func Test_teamByName(t *testing.T) {
t.Run("When store is empty should return ErrObjectNotFound", func(t *testing.T) {
_, store, teardown := datastore.MustNewTestStore(t, true, true)
_, store, teardown := datastore.MustNewTestStore(true, true)
defer teardown()
_, err := store.Team().TeamByName("name")
@@ -19,7 +19,7 @@ func Test_teamByName(t *testing.T) {
})
t.Run("When there is no object with the same name should return ErrObjectNotFound", func(t *testing.T) {
_, store, teardown := datastore.MustNewTestStore(t, true, true)
_, store, teardown := datastore.MustNewTestStore(true, true)
defer teardown()
teamBuilder := teamBuilder{
@@ -35,7 +35,7 @@ func Test_teamByName(t *testing.T) {
})
t.Run("When there is an object with the same name should return the object", func(t *testing.T) {
_, store, teardown := datastore.MustNewTestStore(t, true, true)
_, store, teardown := datastore.MustNewTestStore(true, true)
defer teardown()
teamBuilder := teamBuilder{
@@ -4,8 +4,7 @@ import (
"fmt"
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
"github.com/sirupsen/logrus"
)
const (
@@ -57,12 +56,10 @@ func (service *Service) TeamMemberships() ([]portainer.TeamMembership, error) {
func(obj interface{}) (interface{}, error) {
membership, ok := obj.(*portainer.TeamMembership)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to TeamMembership object")
logrus.WithField("obj", obj).Errorf("Failed to convert to TeamMembership object")
return nil, fmt.Errorf("Failed to convert to TeamMembership object: %s", obj)
}
memberships = append(memberships, *membership)
return &portainer.TeamMembership{}, nil
})
@@ -79,14 +76,12 @@ func (service *Service) TeamMembershipsByUserID(userID portainer.UserID) ([]port
func(obj interface{}) (interface{}, error) {
membership, ok := obj.(*portainer.TeamMembership)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to TeamMembership object")
logrus.WithField("obj", obj).Errorf("Failed to convert to TeamMembership object")
return nil, fmt.Errorf("Failed to convert to TeamMembership object: %s", obj)
}
if membership.UserID == userID {
memberships = append(memberships, *membership)
}
return &portainer.TeamMembership{}, nil
})
@@ -103,14 +98,12 @@ func (service *Service) TeamMembershipsByTeamID(teamID portainer.TeamID) ([]port
func(obj interface{}) (interface{}, error) {
membership, ok := obj.(*portainer.TeamMembership)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to TeamMembership object")
logrus.WithField("obj", obj).Errorf("Failed to convert to TeamMembership object")
return nil, fmt.Errorf("Failed to convert to TeamMembership object: %s", obj)
}
if membership.TeamID == teamID {
memberships = append(memberships, *membership)
}
return &portainer.TeamMembership{}, nil
})
@@ -147,15 +140,13 @@ func (service *Service) DeleteTeamMembershipByUserID(userID portainer.UserID) er
func(obj interface{}) (id int, ok bool) {
membership, ok := obj.(portainer.TeamMembership)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to TeamMembership object")
logrus.WithField("obj", obj).Errorf("Failed to convert to TeamMembership object")
//return fmt.Errorf("Failed to convert to TeamMembership object: %s", obj)
return -1, false
}
if membership.UserID == userID {
return int(membership.ID), true
}
return -1, false
})
}
@@ -167,15 +158,13 @@ func (service *Service) DeleteTeamMembershipByTeamID(teamID portainer.TeamID) er
func(obj interface{}) (id int, ok bool) {
membership, ok := obj.(portainer.TeamMembership)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to TeamMembership object")
logrus.WithField("obj", obj).Errorf("Failed to convert to TeamMembership object")
//return fmt.Errorf("Failed to convert to TeamMembership object: %s", obj)
return -1, false
}
if membership.TeamID == teamID {
return int(membership.ID), true
}
return -1, false
})
}
+5 -16
View File
@@ -4,10 +4,10 @@ import (
"fmt"
"strings"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices/errors"
"github.com/sirupsen/logrus"
"github.com/rs/zerolog/log"
portainer "github.com/portainer/portainer/api"
)
const (
@@ -59,23 +59,18 @@ func (service *Service) UserByUsername(username string) (*portainer.User, error)
func(obj interface{}) (interface{}, error) {
user, ok := obj.(*portainer.User)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to User object")
logrus.WithField("obj", obj).Errorf("Failed to convert to User object")
return nil, fmt.Errorf("Failed to convert to User object: %s", obj)
}
if strings.EqualFold(user.Username, username) {
u = user
return nil, stop
}
return &portainer.User{}, nil
})
if err == stop {
return u, nil
}
if err == nil {
return nil, errors.ErrObjectNotFound
}
@@ -93,13 +88,10 @@ func (service *Service) Users() ([]portainer.User, error) {
func(obj interface{}) (interface{}, error) {
user, ok := obj.(*portainer.User)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to User object")
logrus.WithField("obj", obj).Errorf("Failed to convert to User object")
return nil, fmt.Errorf("Failed to convert to User object: %s", obj)
}
users = append(users, *user)
return &portainer.User{}, nil
})
@@ -116,15 +108,12 @@ func (service *Service) UsersByRole(role portainer.UserRole) ([]portainer.User,
func(obj interface{}) (interface{}, error) {
user, ok := obj.(*portainer.User)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to User object")
logrus.WithField("obj", obj).Errorf("Failed to convert to User object")
return nil, fmt.Errorf("Failed to convert to User object: %s", obj)
}
if user.Role == role {
users = append(users, *user)
}
return &portainer.User{}, nil
})
+5 -18
View File
@@ -5,8 +5,7 @@ import (
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices/errors"
"github.com/rs/zerolog/log"
"github.com/sirupsen/logrus"
)
const (
@@ -35,7 +34,7 @@ func NewService(connection portainer.Connection) (*Service, error) {
}, nil
}
// Webhooks returns an array of all webhooks
//Webhooks returns an array of all webhooks
func (service *Service) Webhooks() ([]portainer.Webhook, error) {
var webhooks = make([]portainer.Webhook, 0)
@@ -45,12 +44,10 @@ func (service *Service) Webhooks() ([]portainer.Webhook, error) {
func(obj interface{}) (interface{}, error) {
webhook, ok := obj.(*portainer.Webhook)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Webhook object")
logrus.WithField("obj", obj).Errorf("Failed to convert to Webhook object")
return nil, fmt.Errorf("Failed to convert to Webhook object: %s", obj)
}
webhooks = append(webhooks, *webhook)
return &portainer.Webhook{}, nil
})
@@ -80,23 +77,18 @@ func (service *Service) WebhookByResourceID(ID string) (*portainer.Webhook, erro
func(obj interface{}) (interface{}, error) {
webhook, ok := obj.(*portainer.Webhook)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Webhook object")
logrus.WithField("obj", obj).Errorf("Failed to convert to Webhook object")
return nil, fmt.Errorf("Failed to convert to Webhook object: %s", obj)
}
if webhook.ResourceID == ID {
w = webhook
return nil, stop
}
return &portainer.Webhook{}, nil
})
if err == stop {
return w, nil
}
if err == nil {
return nil, errors.ErrObjectNotFound
}
@@ -114,23 +106,18 @@ func (service *Service) WebhookByToken(token string) (*portainer.Webhook, error)
func(obj interface{}) (interface{}, error) {
webhook, ok := obj.(*portainer.Webhook)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Webhook object")
logrus.WithField("obj", obj).Errorf("Failed to convert to Webhook object")
return nil, fmt.Errorf("Failed to convert to Webhook object: %s", obj)
}
if webhook.Token == token {
w = webhook
return nil, stop
}
return &portainer.Webhook{}, nil
})
if err == stop {
return w, nil
}
if err == nil {
return nil, errors.ErrObjectNotFound
}
+14 -20
View File
@@ -6,7 +6,7 @@ import (
"path"
"time"
"github.com/rs/zerolog/log"
plog "github.com/portainer/portainer/api/datastore/log"
)
var backupDefaults = struct {
@@ -17,6 +17,8 @@ var backupDefaults = struct {
"common",
}
var backupLog = plog.NewScopedLog("database, backup")
//
// Backup Helpers
//
@@ -27,7 +29,7 @@ func (store *Store) createBackupFolders() {
commonDir := store.commonBackupDir()
if exists, _ := store.fileService.FileExists(commonDir); !exists {
if err := os.MkdirAll(commonDir, 0700); err != nil {
log.Error().Err(err).Msg("error while creating common backup folder")
backupLog.Error("Error while creating common backup folder", err)
}
}
}
@@ -41,13 +43,11 @@ func (store *Store) commonBackupDir() string {
}
func (store *Store) copyDBFile(from string, to string) error {
log.Info().Str("from", from).Str("to", to).Msg("copying DB file")
backupLog.Info(fmt.Sprintf("Copying db file from %s to %s", from, to))
err := store.fileService.Copy(from, to, true)
if err != nil {
log.Error().Err(err).Msg("failed")
backupLog.Error("Failed", err)
}
return err
}
@@ -99,8 +99,7 @@ func (store *Store) setupOptions(options *BackupOptions) *BackupOptions {
// BackupWithOptions backup current database with options
func (store *Store) backupWithOptions(options *BackupOptions) (string, error) {
log.Info().Msg("creating DB backup")
backupLog.Info("creating db backup")
store.createBackupFolders()
options = store.setupOptions(options)
@@ -123,7 +122,6 @@ func (store *Store) backupWithOptions(options *BackupOptions) (string, error) {
err,
)
}
return options.BackupPath, nil
}
@@ -137,19 +135,17 @@ func (store *Store) restoreWithOptions(options *BackupOptions) error {
// Check if backup file exist before restoring
_, err := os.Stat(options.BackupPath)
if os.IsNotExist(err) {
log.Error().Str("path", options.BackupPath).Err(err).Msg("backup file to restore does not exist %s")
backupLog.Error(fmt.Sprintf("Backup file to restore does not exist %s", options.BackupPath), err)
return err
}
err = store.Close()
if err != nil {
log.Error().Err(err).Msg("error while closing store before restore")
backupLog.Error("Error while closing store before restore", err)
return err
}
log.Info().Msg("restoring DB backup")
backupLog.Info("Restoring db backup")
err = store.copyDBFile(options.BackupPath, store.databasePath())
if err != nil {
return err
@@ -161,22 +157,20 @@ func (store *Store) restoreWithOptions(options *BackupOptions) error {
// RemoveWithOptions removes backup database based on supplied options
func (store *Store) removeWithOptions(options *BackupOptions) error {
log.Info().Msg("removing DB backup")
backupLog.Info("Removing db backup")
options = store.setupOptions(options)
_, err := os.Stat(options.BackupPath)
if os.IsNotExist(err) {
log.Error().Str("path", options.BackupPath).Err(err).Msg("backup file to remove does not exist")
backupLog.Error(fmt.Sprintf("Backup file to remove does not exist %s", options.BackupPath), err)
return err
}
log.Info().Str("path", options.BackupPath).Msg("removing DB file")
backupLog.Info(fmt.Sprintf("Removing db file at %s", options.BackupPath))
err = os.Remove(options.BackupPath)
if err != nil {
log.Error().Err(err).Msg("failed")
backupLog.Error("Failed", err)
return err
}
+4 -4
View File
@@ -10,7 +10,7 @@ import (
)
func TestCreateBackupFolders(t *testing.T) {
_, store, teardown := MustNewTestStore(t, false, true)
_, store, teardown := MustNewTestStore(false, true)
defer teardown()
connection := store.GetConnection()
@@ -27,7 +27,7 @@ func TestCreateBackupFolders(t *testing.T) {
}
func TestStoreCreation(t *testing.T) {
_, store, teardown := MustNewTestStore(t, true, true)
_, store, teardown := MustNewTestStore(true, true)
defer teardown()
if store == nil {
@@ -40,7 +40,7 @@ func TestStoreCreation(t *testing.T) {
}
func TestBackup(t *testing.T) {
_, store, teardown := MustNewTestStore(t, true, true)
_, store, teardown := MustNewTestStore(true, true)
connection := store.GetConnection()
defer teardown()
@@ -67,7 +67,7 @@ func TestBackup(t *testing.T) {
}
func TestRemoveWithOptions(t *testing.T) {
_, store, teardown := MustNewTestStore(t, true, true)
_, store, teardown := MustNewTestStore(true, true)
defer teardown()
t.Run("successfully removes file if existent", func(t *testing.T) {
+10 -15
View File
@@ -9,8 +9,7 @@ import (
portainer "github.com/portainer/portainer/api"
portainerErrors "github.com/portainer/portainer/api/dataservices/errors"
"github.com/rs/zerolog/log"
"github.com/sirupsen/logrus"
)
func (store *Store) version() (int, error) {
@@ -74,8 +73,7 @@ func (store *Store) Open() (newStore bool, err error) {
}
if version > 0 {
log.Debug().Int("version", version).Msg("opened existing store")
logrus.WithField("version", version).Infof("Opened existing store")
return false, nil
}
@@ -123,21 +121,19 @@ func (store *Store) encryptDB() error {
// The DB is not currently encrypted. First save the encrypted db filename
oldFilename := store.connection.GetDatabaseFilePath()
log.Info().Msg("encrypting database")
logrus.Infof("Encrypting database")
// export file path for backup
exportFilename := path.Join(store.databasePath() + "." + fmt.Sprintf("backup-%d.json", time.Now().Unix()))
log.Info().Str("filename", exportFilename).Msg("exporting database backup")
logrus.Infof("Exporting database backup to %s", exportFilename)
err = store.Export(exportFilename)
if err != nil {
log.Error().Str("filename", exportFilename).Err(err).Msg("failed to export")
logrus.WithError(err).Debugf("Failed to export to %s", exportFilename)
return err
}
log.Info().Msg("database backup exported")
logrus.Infof("Database backup exported")
// Close existing un-encrypted db so that we can delete the file later
store.connection.Close()
@@ -156,23 +152,22 @@ func (store *Store) encryptDB() error {
if err != nil {
// Remove the new encrypted file that we failed to import
os.Remove(store.connection.GetDatabaseFilePath())
log.Fatal().Err(portainerErrors.ErrDBImportFailed).Msg("")
logrus.Fatal(portainerErrors.ErrDBImportFailed.Error())
}
err = os.Remove(oldFilename)
if err != nil {
log.Error().Msg("failed to remove the un-encrypted db file")
logrus.Errorf("Failed to remove the un-encrypted db file")
}
err = os.Remove(exportFilename)
if err != nil {
log.Error().Msg("failed to remove the json backup file")
logrus.Errorf("Failed to remove the json backup file")
}
// Close db connection
store.connection.Close()
log.Info().Msg("database successfully encrypted")
logrus.Info("Database successfully encrypted")
return nil
}
+1 -1
View File
@@ -27,7 +27,7 @@ const (
// TestStoreFull an eventually comprehensive set of tests for the Store.
// The idea is what we write to the store, we should read back.
func TestStoreFull(t *testing.T) {
_, store, teardown := MustNewTestStore(t, true, true)
_, store, teardown := MustNewTestStore(true, true)
defer teardown()
testCases := map[string]func(t *testing.T){
+1 -1
View File
@@ -44,7 +44,7 @@ func (store *Store) checkOrCreateDefaultSettings() error {
settings, err := store.SettingsService.Settings()
if store.IsErrObjectNotFound(err) {
defaultSettings := &portainer.Settings{
EnableTelemetry: false,
EnableTelemetry: true,
AuthenticationMethod: portainer.AuthenticationInternal,
BlackListedLabels: make([]portainer.Pair, 0),
InternalAuthSettings: portainer.InternalAuthSettings{
+51
View File
@@ -0,0 +1,51 @@
package log
import (
"fmt"
"log"
)
const (
INFO = "INFO"
ERROR = "ERROR"
DEBUG = "DEBUG"
FATAL = "FATAL"
)
type ScopedLog struct {
scope string
}
func NewScopedLog(scope string) *ScopedLog {
return &ScopedLog{scope: scope}
}
func (slog *ScopedLog) print(kind string, message string) {
log.Printf("[%s] [%s] %s", kind, slog.scope, message)
}
func (slog *ScopedLog) Debug(message string) {
slog.print(DEBUG, fmt.Sprintf("[message: %s]", message))
}
func (slog *ScopedLog) Debugf(message string, vars ...interface{}) {
message = fmt.Sprintf(message, vars...)
slog.print(DEBUG, fmt.Sprintf("[message: %s]", message))
}
func (slog *ScopedLog) Info(message string) {
slog.print(INFO, fmt.Sprintf("[message: %s]", message))
}
func (slog *ScopedLog) Infof(message string, vars ...interface{}) {
message = fmt.Sprintf(message, vars...)
slog.print(INFO, fmt.Sprintf("[message: %s]", message))
}
func (slog *ScopedLog) Error(message string, err error) {
slog.print(ERROR, fmt.Sprintf("[message: %s] [error: %s]", message, err))
}
func (slog *ScopedLog) NotImplemented(method string) {
log.Fatalf("[%s] [%s] [%s]", FATAL, slog.scope, fmt.Sprintf("%s is not yet implemented", method))
}
+18 -22
View File
@@ -4,18 +4,21 @@ import (
"fmt"
"runtime/debug"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/cli"
"github.com/portainer/portainer/api/dataservices/errors"
plog "github.com/portainer/portainer/api/datastore/log"
"github.com/portainer/portainer/api/datastore/migrator"
"github.com/portainer/portainer/api/internal/authorization"
"github.com/sirupsen/logrus"
werrors "github.com/pkg/errors"
"github.com/rs/zerolog/log"
portainer "github.com/portainer/portainer/api"
)
const beforePortainerVersionUpgradeBackup = "portainer.db.bak"
var migrateLog = plog.NewScopedLog("database, migrate")
func (store *Store) MigrateData() error {
version, err := store.version()
if err != nil {
@@ -40,7 +43,6 @@ func (store *Store) MigrateData() error {
RoleService: store.RoleService,
ScheduleService: store.ScheduleService,
SettingsService: store.SettingsService,
SnapshotService: store.SnapshotService,
StackService: store.StackService,
TagService: store.TagService,
TeamMembershipService: store.TeamMembershipService,
@@ -54,19 +56,20 @@ func (store *Store) MigrateData() error {
// restore on error
err = store.connectionMigrateData(migratorParams)
if err != nil {
log.Error().Err(err).Msg("while DB migration, restoring DB")
logrus.Errorf("While DB migration %v. Restoring DB", err)
// Restore options
options := BackupOptions{
BackupPath: backupPath,
}
err := store.restoreWithOptions(&options)
if err != nil {
log.Fatal().
Str("database_file", store.databasePath()).
Str("backup", options.BackupPath).Err(err).
Msg("failed restoring the backup, Portainer database file needs to restored manually by replacing the database file with a recent backup")
logrus.Fatalf(
"Failed restoring the backup. portainer database file needs to restored manually by "+
"replacing %s database file with recent backup %s. Error %v",
store.databasePath(),
options.BackupPath,
err,
)
}
}
@@ -108,15 +111,10 @@ func (store *Store) connectionMigrateData(migratorParams *migrator.MigratorParam
}
if migrator.Version() < portainer.DBVersion {
log.Info().
Int("migrator_version", migrator.Version()).
Int("db_version", portainer.DBVersion).
Msg("migrating database")
migrateLog.Info(fmt.Sprintf("Migrating database from version %v to %v.\n", migrator.Version(), portainer.DBVersion))
err = store.FailSafeMigrate(migrator)
if err != nil {
log.Error().Err(err).Msg("an error occurred during database migration")
migrateLog.Error("An error occurred during database migration", err)
return err
}
}
@@ -126,19 +124,17 @@ func (store *Store) connectionMigrateData(migratorParams *migrator.MigratorParam
// backupVersion will backup the database or panic if any errors occur
func (store *Store) backupVersion(migrator *migrator.Migrator) error {
log.Info().Msg("backing up database prior to version upgrade")
migrateLog.Info("Backing up database prior to version upgrade...")
options := getBackupRestoreOptions(store.commonBackupDir())
_, err := store.backupWithOptions(options)
if err != nil {
log.Error().Err(err).Msg("an error occurred during database backup")
migrateLog.Error("An error occurred during database backup", err)
removalErr := store.removeWithOptions(options)
if removalErr != nil {
log.Error().Err(err).Msg("an error occurred during store removal prior to backup")
migrateLog.Error("An error occurred during store removal prior to backup", err)
}
return err
}
+16 -17
View File
@@ -5,16 +5,15 @@ import (
"encoding/json"
"fmt"
"io"
"log"
"os"
"path/filepath"
"strings"
"testing"
"github.com/google/go-cmp/cmp"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/database/boltdb"
"github.com/google/go-cmp/cmp"
"github.com/rs/zerolog/log"
)
// testVersion is a helper which tests current store version against wanted version
@@ -54,7 +53,7 @@ func TestMigrateData(t *testing.T) {
}
t.Run("MigrateData for New Store & Re-Open Check", func(t *testing.T) {
newStore, store, teardown := MustNewTestStore(t, false, true)
newStore, store, teardown := MustNewTestStore(false, true)
defer teardown()
if !newStore {
@@ -81,7 +80,7 @@ func TestMigrateData(t *testing.T) {
{version: 21, expectedVersion: portainer.DBVersion},
}
for _, tc := range tests {
_, store, teardown := MustNewTestStore(t, true, true)
_, store, teardown := MustNewTestStore(true, true)
defer teardown()
// Setup data
@@ -106,7 +105,7 @@ func TestMigrateData(t *testing.T) {
}
t.Run("Error in MigrateData should restore backup before MigrateData", func(t *testing.T) {
_, store, teardown := MustNewTestStore(t, false, true)
_, store, teardown := MustNewTestStore(false, true)
defer teardown()
version := 17
@@ -118,7 +117,7 @@ func TestMigrateData(t *testing.T) {
})
t.Run("MigrateData should create backup file upon update", func(t *testing.T) {
_, store, teardown := MustNewTestStore(t, false, true)
_, store, teardown := MustNewTestStore(false, true)
defer teardown()
store.VersionService.StoreDBVersion(0)
@@ -132,7 +131,7 @@ func TestMigrateData(t *testing.T) {
})
t.Run("MigrateData should fail to create backup if database file is set to updating", func(t *testing.T) {
_, store, teardown := MustNewTestStore(t, false, true)
_, store, teardown := MustNewTestStore(false, true)
defer teardown()
store.VersionService.StoreIsUpdating(true)
@@ -147,7 +146,7 @@ func TestMigrateData(t *testing.T) {
})
t.Run("MigrateData should not create backup on startup if portainer version matches db", func(t *testing.T) {
_, store, teardown := MustNewTestStore(t, false, true)
_, store, teardown := MustNewTestStore(false, true)
defer teardown()
store.MigrateData()
@@ -158,48 +157,48 @@ func TestMigrateData(t *testing.T) {
t.Errorf("Backup file should not exist for dirty database; file=%s", options.BackupPath)
}
})
}
func Test_getBackupRestoreOptions(t *testing.T) {
_, store, teardown := MustNewTestStore(t, false, true)
_, store, teardown := MustNewTestStore(false, true)
defer teardown()
options := getBackupRestoreOptions(store.commonBackupDir())
wantDir := store.commonBackupDir()
if !strings.HasSuffix(options.BackupDir, wantDir) {
log.Fatal().Str("got", options.BackupDir).Str("want", wantDir).Msg("incorrect backup dir")
log.Fatalf("incorrect backup dir; got=%s, want=%s", options.BackupDir, wantDir)
}
wantFilename := "portainer.db.bak"
if options.BackupFileName != wantFilename {
log.Fatal().Str("got", options.BackupFileName).Str("want", wantFilename).Msg("incorrect backup file")
log.Fatalf("incorrect backup file; got=%s, want=%s", options.BackupFileName, wantFilename)
}
}
func TestRollback(t *testing.T) {
t.Run("Rollback should restore upgrade after backup", func(t *testing.T) {
version := 21
_, store, teardown := MustNewTestStore(t, false, true)
_, store, teardown := MustNewTestStore(false, true)
defer teardown()
store.VersionService.StoreDBVersion(version)
_, err := store.backupWithOptions(getBackupRestoreOptions(store.commonBackupDir()))
if err != nil {
log.Fatal().Err(err).Msg("")
log.Fatal(err)
}
// Change the current edition
err = store.VersionService.StoreDBVersion(version + 10)
if err != nil {
log.Fatal().Err(err).Msg("")
log.Fatal(err)
}
err = store.Rollback(true)
if err != nil {
t.Logf("Rollback failed: %s", err)
t.Fail()
return
}
@@ -227,7 +226,7 @@ func migrateDBTestHelper(t *testing.T, srcPath, wantPath string) error {
}
// Parse source json to db.
_, store, teardown := MustNewTestStore(t, true, false)
_, store, teardown := MustNewTestStore(true, false)
defer teardown()
err = importJSON(t, bytes.NewReader(srcJSON), store)
if err != nil {
+1 -1
View File
@@ -33,7 +33,7 @@ func setup(store *Store) error {
}
func TestMigrateSettings(t *testing.T) {
_, store, teardown := MustNewTestStore(t, false, true)
_, store, teardown := MustNewTestStore(false, true)
defer teardown()
err := setup(store)
+1 -1
View File
@@ -10,7 +10,7 @@ import (
)
func TestMigrateStackEntryPoint(t *testing.T) {
_, store, teardown := MustNewTestStore(t, false, true)
_, store, teardown := MustNewTestStore(false, true)
defer teardown()
stackService := store.Stack()
+4 -17
View File
@@ -5,10 +5,8 @@ import (
"reflect"
"runtime"
portainer "github.com/portainer/portainer/api"
werrors "github.com/pkg/errors"
"github.com/rs/zerolog/log"
portainer "github.com/portainer/portainer/api"
)
type migration struct {
@@ -105,15 +103,6 @@ func (m *Migrator) Migrate() error {
// Portainer 2.14
newMigration(50, m.migrateDBVersionToDB50),
// Portainer 2.15
newMigration(60, m.migrateDBVersionToDB60),
// Portainer 2.16
newMigration(70, m.migrateDBVersionToDB70),
// Portainer 2.16.1
newMigration(71, m.migrateDBVersionToDB71),
}
var lastDbVersion int
@@ -122,7 +111,7 @@ func (m *Migrator) Migrate() error {
// Print the next line only when the version changes
if migration.dbversion > lastDbVersion {
log.Info().Int("to_version", migration.dbversion).Msg("migrating DB")
migrateLog.Infof("Migrating DB to version %d", migration.dbversion)
}
err := migration.migrate()
@@ -133,14 +122,12 @@ func (m *Migrator) Migrate() error {
lastDbVersion = migration.dbversion
}
log.Info().Int("version", portainer.DBVersion).Msg("setting DB version")
migrateLog.Infof("Setting DB version to %d", portainer.DBVersion)
err = m.versionService.StoreDBVersion(portainer.DBVersion)
if err != nil {
return migrationError(err, "StoreDBVersion")
}
log.Info().Int("version", portainer.DBVersion).Msg("updated DB version")
migrateLog.Infof("Updated DB version to %d", portainer.DBVersion)
// reset DB updating status
return m.versionService.StoreIsUpdating(false)
+4 -10
View File
@@ -2,13 +2,10 @@ package migrator
import (
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
)
func (m *Migrator) updateUsersToDBVersion18() error {
log.Info().Msg("updating users")
migrateLog.Info("- updating users")
legacyUsers, err := m.userService.Users()
if err != nil {
return err
@@ -43,8 +40,7 @@ func (m *Migrator) updateUsersToDBVersion18() error {
}
func (m *Migrator) updateEndpointsToDBVersion18() error {
log.Info().Msg("updating endpoints")
migrateLog.Info("- updating endpoints")
legacyEndpoints, err := m.endpointService.Endpoints()
if err != nil {
return err
@@ -75,8 +71,7 @@ func (m *Migrator) updateEndpointsToDBVersion18() error {
}
func (m *Migrator) updateEndpointGroupsToDBVersion18() error {
log.Info().Msg("updating endpoint groups")
migrateLog.Info("- updating endpoint groups")
legacyEndpointGroups, err := m.endpointGroupService.EndpointGroups()
if err != nil {
return err
@@ -107,8 +102,7 @@ func (m *Migrator) updateEndpointGroupsToDBVersion18() error {
}
func (m *Migrator) updateRegistriesToDBVersion18() error {
log.Info().Msg("updating registries")
migrateLog.Info("- updating registries")
legacyRegistries, err := m.registryService.Registries()
if err != nil {
return err
@@ -1,14 +1,9 @@
package migrator
import (
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
)
import portainer "github.com/portainer/portainer/api"
func (m *Migrator) updateSettingsToDBVersion19() error {
log.Info().Msg("updating settings")
migrateLog.Info("- updating settings")
legacySettings, err := m.settingsService.Settings()
if err != nil {
return err
@@ -2,15 +2,12 @@ package migrator
import (
"strings"
"github.com/rs/zerolog/log"
)
const scheduleScriptExecutionJobType = 1
func (m *Migrator) updateUsersToDBVersion20() error {
log.Info().Msg("updating user authentication")
migrateLog.Info("- updating user authentication")
return m.authorizationService.UpdateUsersAuthorizations()
}
@@ -26,8 +23,7 @@ func (m *Migrator) updateSettingsToDBVersion20() error {
}
func (m *Migrator) updateSchedulesToDBVersion20() error {
log.Info().Msg("updating schedules")
migrateLog.Info("- updating schedules")
legacySchedules, err := m.scheduleService.Schedules()
if err != nil {
return err
@@ -3,13 +3,10 @@ package migrator
import (
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/internal/authorization"
"github.com/rs/zerolog/log"
)
func (m *Migrator) updateResourceControlsToDBVersion22() error {
log.Info().Msg("updating resource controls")
migrateLog.Info("- updating resource controls")
legacyResourceControls, err := m.resourceControlService.ResourceControls()
if err != nil {
return err
@@ -28,8 +25,7 @@ func (m *Migrator) updateResourceControlsToDBVersion22() error {
}
func (m *Migrator) updateUsersAndRolesToDBVersion22() error {
log.Info().Msg("updating users and roles")
migrateLog.Info("- updating users and roles")
legacyUsers, err := m.userService.Users()
if err != nil {
return err
+3 -10
View File
@@ -1,14 +1,9 @@
package migrator
import (
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
)
import portainer "github.com/portainer/portainer/api"
func (m *Migrator) updateTagsToDBVersion23() error {
log.Info().Msg("updating tags")
migrateLog.Info("- Updating tags")
tags, err := m.tagService.Tags()
if err != nil {
return err
@@ -26,8 +21,7 @@ func (m *Migrator) updateTagsToDBVersion23() error {
}
func (m *Migrator) updateEndpointsAndEndpointGroupsToDBVersion23() error {
log.Info().Msg("updating endpoints and endpoint groups")
migrateLog.Info("- updating endpoints and endpoint groups")
tags, err := m.tagService.Tags()
if err != nil {
return err
@@ -96,6 +90,5 @@ func (m *Migrator) updateEndpointsAndEndpointGroupsToDBVersion23() error {
return err
}
}
return nil
}
@@ -1,13 +1,9 @@
package migrator
import (
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
)
import portainer "github.com/portainer/portainer/api"
func (m *Migrator) updateSettingsToDB24() error {
log.Info().Msg("updating Settings")
migrateLog.Info("- updating Settings")
legacySettings, err := m.settingsService.Settings()
if err != nil {
@@ -22,8 +18,7 @@ func (m *Migrator) updateSettingsToDB24() error {
}
func (m *Migrator) updateStacksToDB24() error {
log.Info().Msg("updating stacks")
migrateLog.Info("- updating stacks")
stacks, err := m.stackService.Stacks()
if err != nil {
return err
@@ -2,12 +2,10 @@ package migrator
import (
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
)
func (m *Migrator) updateSettingsToDB25() error {
log.Info().Msg("updating settings")
migrateLog.Info("- updating settings")
legacySettings, err := m.settingsService.Settings()
if err != nil {
@@ -2,13 +2,10 @@ package migrator
import (
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
)
func (m *Migrator) updateEndpointSettingsToDB25() error {
log.Info().Msg("updating endpoint settings")
migrateLog.Info("- updating endpoint settings")
settings, err := m.settingsService.Settings()
if err != nil {
return err
@@ -4,13 +4,10 @@ import (
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices/errors"
"github.com/portainer/portainer/api/internal/stackutils"
"github.com/rs/zerolog/log"
)
func (m *Migrator) updateStackResourceControlToDB27() error {
log.Info().Msg("updating stack resource controls")
migrateLog.Info("- updating stack resource controls")
resourceControls, err := m.resourceControlService.ResourceControls()
if err != nil {
return err
@@ -1,11 +1,12 @@
package migrator
import "github.com/rs/zerolog/log"
func (m *Migrator) migrateDBVersionToDB30() error {
log.Info().Msg("updating legacy settings")
migrateLog.Info("- updating legacy settings")
if err := m.MigrateSettingsToDB30(); err != nil {
return err
}
return m.MigrateSettingsToDB30()
return nil
}
// so setting to false and "", is what would happen without this code
+16 -24
View File
@@ -2,14 +2,14 @@ package migrator
import (
"fmt"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices/errors"
"github.com/portainer/portainer/api/internal/endpointutils"
snapshotutils "github.com/portainer/portainer/api/internal/snapshot"
"log"
"github.com/docker/docker/api/types/volume"
"github.com/rs/zerolog/log"
"github.com/portainer/portainer/api/dataservices/errors"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/internal/endpointutils"
snapshotutils "github.com/portainer/portainer/api/internal/snapshot"
)
func (m *Migrator) migrateDBVersionToDB32() error {
@@ -39,8 +39,7 @@ func (m *Migrator) migrateDBVersionToDB32() error {
}
func (m *Migrator) updateRegistriesToDB32() error {
log.Info().Msg("updating registries")
migrateLog.Info("- updating registries")
registries, err := m.registryService.Registries()
if err != nil {
return err
@@ -83,8 +82,7 @@ func (m *Migrator) updateRegistriesToDB32() error {
}
func (m *Migrator) updateDockerhubToDB32() error {
log.Info().Msg("updating dockerhub")
migrateLog.Info("- updating dockerhub")
dockerhub, err := m.dockerhubService.DockerHub()
if err == errors.ErrObjectNotFound {
return nil
@@ -173,8 +171,7 @@ func (m *Migrator) updateDockerhubToDB32() error {
}
func (m *Migrator) updateVolumeResourceControlToDB32() error {
log.Info().Msg("updating resource controls")
migrateLog.Info("- updating resource controls")
endpoints, err := m.endpointService.Endpoints()
if err != nil {
return fmt.Errorf("failed fetching environments: %w", err)
@@ -202,7 +199,7 @@ func (m *Migrator) updateVolumeResourceControlToDB32() error {
totalSnapshots := len(endpoint.Snapshots)
if totalSnapshots == 0 {
log.Debug().Msg("no snapshot found")
log.Println("[DEBUG] [volume migration] [message: no snapshot found]")
continue
}
@@ -210,13 +207,13 @@ func (m *Migrator) updateVolumeResourceControlToDB32() error {
endpointDockerID, err := snapshotutils.FetchDockerID(snapshot)
if err != nil {
log.Warn().Err(err).Msg("failed fetching environment docker id")
log.Printf("[WARN] [database,migrator,v31] [message: failed fetching environment docker id] [err: %s]", err)
continue
}
volumesData := snapshot.SnapshotRaw.Volumes
if volumesData.Volumes == nil {
log.Debug().Msg("no volume data found")
log.Println("[DEBUG] [volume migration] [message: no volume data found]")
continue
}
@@ -227,18 +224,17 @@ func (m *Migrator) updateVolumeResourceControlToDB32() error {
for _, resourceControl := range volumeResourceControls {
if newResourceID, ok := toUpdate[resourceControl.ID]; ok {
resourceControl.ResourceID = newResourceID
err := m.resourceControlService.UpdateResourceControl(resourceControl.ID, resourceControl)
if err != nil {
return fmt.Errorf("failed updating resource control %d: %w", resourceControl.ID, err)
}
} else {
err := m.resourceControlService.DeleteResourceControl(resourceControl.ID)
if err != nil {
return fmt.Errorf("failed deleting resource control %d: %w", resourceControl.ID, err)
}
log.Debug().Str("resource_id", resourceControl.ResourceID).Msg("legacy resource control has been deleted")
log.Printf("[DEBUG] [volume migration] [message: legacy resource control(%s) has been deleted]", resourceControl.ResourceID)
}
}
@@ -261,25 +257,21 @@ func findResourcesToUpdateForDB32(dockerID string, volumesData volume.VolumeList
}
func (m *Migrator) kubeconfigExpiryToDB32() error {
log.Info().Msg("updating kubeconfig expiry")
migrateLog.Info("- updating kubeconfig expiry")
settings, err := m.settingsService.Settings()
if err != nil {
return err
}
settings.KubeconfigExpiry = portainer.DefaultKubeconfigExpiry
return m.settingsService.UpdateSettings(settings)
}
func (m *Migrator) helmRepositoryURLToDB32() error {
log.Info().Msg("setting default helm repository URL")
migrateLog.Info("- setting default helm repository URL")
settings, err := m.settingsService.Settings()
if err != nil {
return err
}
settings.HelmRepositoryURL = portainer.DefaultHelmRepositoryURL
return m.settingsService.UpdateSettings(settings)
}
@@ -2,14 +2,15 @@ package migrator
import (
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
)
func (m *Migrator) migrateDBVersionToDB33() error {
log.Info().Msg("updating settings")
migrateLog.Info("- updating settings")
if err := m.migrateSettingsToDB33(); err != nil {
return err
}
return m.migrateSettingsToDB33()
return nil
}
func (m *Migrator) migrateSettingsToDB33() error {
@@ -18,8 +19,7 @@ func (m *Migrator) migrateSettingsToDB33() error {
return err
}
log.Info().Msg("setting default kubectl shell image")
migrateLog.Info("- setting default kubectl shell image")
settings.KubectlShellImage = portainer.DefaultKubectlShellImage
return m.settingsService.UpdateSettings(settings)
}
@@ -2,14 +2,16 @@ package migrator
import (
"github.com/portainer/portainer/api/dataservices"
"github.com/rs/zerolog/log"
)
func (m *Migrator) migrateDBVersionToDB34() error {
log.Info().Msg("updating stacks")
migrateLog.Info("- updating stacks")
err := MigrateStackEntryPoint(m.stackService)
if err != nil {
return err
}
return MigrateStackEntryPoint(m.stackService)
return nil
}
// MigrateStackEntryPoint exported for testing (blah.)
@@ -18,18 +20,15 @@ func MigrateStackEntryPoint(stackService dataservices.StackService) error {
if err != nil {
return err
}
for i := range stacks {
stack := &stacks[i]
if stack.GitConfig == nil {
continue
}
stack.GitConfig.ConfigFilePath = stack.EntryPoint
if err := stackService.UpdateStack(stack.ID, stack); err != nil {
return err
}
}
return nil
}
@@ -1,12 +1,12 @@
package migrator
import "github.com/rs/zerolog/log"
func (m *Migrator) migrateDBVersionToDB35() error {
// These should have been migrated already, but due to an earlier bug and a bunch of duplicates,
// calling it again will now fix the issue as the function has been repaired.
log.Info().Msg("updating dockerhub registries")
return m.updateDockerhubToDB32()
migrateLog.Info("- updating dockerhub registries")
err := m.updateDockerhubToDB32()
if err != nil {
return err
}
return nil
}
@@ -3,14 +3,15 @@ package migrator
import (
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/internal/authorization"
"github.com/rs/zerolog/log"
)
func (m *Migrator) migrateDBVersionToDB36() error {
log.Info().Msg("updating user authorizations")
migrateLog.Info("Updating user authorizations")
if err := m.migrateUsersToDB36(); err != nil {
return err
}
return m.migrateUsersToDB36()
return nil
}
func (m *Migrator) migrateUsersToDB36() error {
@@ -1,18 +1,17 @@
package migrator
import (
"github.com/portainer/portainer/api/internal/endpointutils"
"github.com/rs/zerolog/log"
)
import "github.com/portainer/portainer/api/internal/endpointutils"
func (m *Migrator) migrateDBVersionToDB40() error {
return m.trustCurrentEdgeEndpointsDB40()
if err := m.trustCurrentEdgeEndpointsDB40(); err != nil {
return err
}
return nil
}
func (m *Migrator) trustCurrentEdgeEndpointsDB40() error {
log.Info().Msg("trusting current edge endpoints")
migrateLog.Info("- trusting current edge endpoints")
endpoints, err := m.endpointService.Endpoints()
if err != nil {
return err
@@ -2,7 +2,6 @@ package migrator
import (
"github.com/pkg/errors"
"github.com/rs/zerolog/log"
)
func (m *Migrator) migrateDBVersionToDB50() error {
@@ -10,8 +9,7 @@ func (m *Migrator) migrateDBVersionToDB50() error {
}
func (m *Migrator) migratePasswordLengthSettings() error {
log.Info().Msg("updating required password length")
migrateLog.Info("Updating required password length")
s, err := m.settingsService.Settings()
if err != nil {
return errors.Wrap(err, "unable to retrieve settings")
@@ -1,31 +0,0 @@
package migrator
import (
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
)
func (m *Migrator) migrateDBVersionToDB60() error {
return m.addGpuInputFieldDB60()
}
func (m *Migrator) addGpuInputFieldDB60() error {
log.Info().Msg("add gpu input field")
endpoints, err := m.endpointService.Endpoints()
if err != nil {
return err
}
for _, endpoint := range endpoints {
endpoint.Gpus = []portainer.Pair{}
err = m.endpointService.UpdateEndpoint(endpoint.ID, &endpoint)
if err != nil {
return err
}
}
return nil
}
@@ -1,70 +0,0 @@
package migrator
import (
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
)
func (m *Migrator) migrateDBVersionToDB70() error {
log.Info().Msg("- add IngressAvailabilityPerNamespace field")
if err := m.updateIngressFieldsForEnvDB70(); err != nil {
return err
}
endpoints, err := m.endpointService.Endpoints()
if err != nil {
return err
}
for _, endpoint := range endpoints {
// copy snapshots to new object
log.Info().Msg("moving snapshots from endpoint to new object")
snapshot := portainer.Snapshot{EndpointID: endpoint.ID}
if len(endpoint.Snapshots) > 0 {
snapshot.Docker = &endpoint.Snapshots[len(endpoint.Snapshots)-1]
}
if len(endpoint.Kubernetes.Snapshots) > 0 {
snapshot.Kubernetes = &endpoint.Kubernetes.Snapshots[len(endpoint.Kubernetes.Snapshots)-1]
}
// save new object
err = m.snapshotService.Create(&snapshot)
if err != nil {
return err
}
// set to nil old fields
log.Info().Msg("deleting snapshot from endpoint")
endpoint.Snapshots = []portainer.DockerSnapshot{}
endpoint.Kubernetes.Snapshots = []portainer.KubernetesSnapshot{}
// update endpoint
err = m.endpointService.UpdateEndpoint(endpoint.ID, &endpoint)
if err != nil {
return err
}
}
return nil
}
func (m *Migrator) updateIngressFieldsForEnvDB70() error {
endpoints, err := m.endpointService.Endpoints()
if err != nil {
return err
}
for _, endpoint := range endpoints {
endpoint.Kubernetes.Configuration.IngressAvailabilityPerNamespace = true
endpoint.Kubernetes.Configuration.AllowNoneIngressClass = false
endpoint.PostInitMigrations.MigrateIngresses = true
err = m.endpointService.UpdateEndpoint(endpoint.ID, &endpoint)
if err != nil {
return err
}
}
return nil
}
@@ -1,36 +0,0 @@
package migrator
import (
"github.com/portainer/portainer/api/dataservices/errors"
"github.com/rs/zerolog/log"
)
func (m *Migrator) migrateDBVersionToDB71() error {
log.Info().Msg("removing orphaned snapshots")
snapshots, err := m.snapshotService.Snapshots()
if err != nil {
return err
}
for _, s := range snapshots {
_, err := m.endpointService.Endpoint(s.EndpointID)
if err == nil {
log.Debug().Int("endpoint_id", int(s.EndpointID)).Msg("keeping snapshot")
continue
} else if err != errors.ErrObjectNotFound {
log.Debug().Int("endpoint_id", int(s.EndpointID)).Err(err).Msg("database error")
return err
}
log.Debug().Int("endpoint_id", int(s.EndpointID)).Msg("removing snapshot")
err = m.snapshotService.DeleteSnapshot(s.EndpointID)
if err != nil {
return err
}
}
return nil
}
+3 -4
View File
@@ -13,15 +13,17 @@ import (
"github.com/portainer/portainer/api/dataservices/role"
"github.com/portainer/portainer/api/dataservices/schedule"
"github.com/portainer/portainer/api/dataservices/settings"
"github.com/portainer/portainer/api/dataservices/snapshot"
"github.com/portainer/portainer/api/dataservices/stack"
"github.com/portainer/portainer/api/dataservices/tag"
"github.com/portainer/portainer/api/dataservices/teammembership"
"github.com/portainer/portainer/api/dataservices/user"
"github.com/portainer/portainer/api/dataservices/version"
plog "github.com/portainer/portainer/api/datastore/log"
"github.com/portainer/portainer/api/internal/authorization"
)
var migrateLog = plog.NewScopedLog("database, migrate")
type (
// Migrator defines a service to migrate data after a Portainer version update.
Migrator struct {
@@ -36,7 +38,6 @@ type (
roleService *role.Service
scheduleService *schedule.Service
settingsService *settings.Service
snapshotService *snapshot.Service
stackService *stack.Service
tagService *tag.Service
teamMembershipService *teammembership.Service
@@ -60,7 +61,6 @@ type (
RoleService *role.Service
ScheduleService *schedule.Service
SettingsService *settings.Service
SnapshotService *snapshot.Service
StackService *stack.Service
TagService *tag.Service
TeamMembershipService *teammembership.Service
@@ -86,7 +86,6 @@ func NewMigrator(parameters *MigratorParameters) *Migrator {
roleService: parameters.RoleService,
scheduleService: parameters.ScheduleService,
settingsService: parameters.SettingsService,
snapshotService: parameters.SnapshotService,
tagService: parameters.TagService,
teamMembershipService: parameters.TeamMembershipService,
stackService: parameters.StackService,
+29 -68
View File
@@ -2,7 +2,6 @@ package datastore
import (
"encoding/json"
"fmt"
"io/ioutil"
"strconv"
@@ -14,7 +13,6 @@ import (
"github.com/portainer/portainer/api/dataservices/edgegroup"
"github.com/portainer/portainer/api/dataservices/edgejob"
"github.com/portainer/portainer/api/dataservices/edgestack"
"github.com/portainer/portainer/api/dataservices/edgeupdateschedule"
"github.com/portainer/portainer/api/dataservices/endpoint"
"github.com/portainer/portainer/api/dataservices/endpointgroup"
"github.com/portainer/portainer/api/dataservices/endpointrelation"
@@ -26,7 +24,6 @@ import (
"github.com/portainer/portainer/api/dataservices/role"
"github.com/portainer/portainer/api/dataservices/schedule"
"github.com/portainer/portainer/api/dataservices/settings"
"github.com/portainer/portainer/api/dataservices/snapshot"
"github.com/portainer/portainer/api/dataservices/ssl"
"github.com/portainer/portainer/api/dataservices/stack"
"github.com/portainer/portainer/api/dataservices/tag"
@@ -36,8 +33,7 @@ import (
"github.com/portainer/portainer/api/dataservices/user"
"github.com/portainer/portainer/api/dataservices/version"
"github.com/portainer/portainer/api/dataservices/webhook"
"github.com/rs/zerolog/log"
"github.com/sirupsen/logrus"
)
// Store defines the implementation of portainer.DataStore using
@@ -50,7 +46,6 @@ type Store struct {
DockerHubService *dockerhub.Service
EdgeGroupService *edgegroup.Service
EdgeJobService *edgejob.Service
EdgeUpdateScheduleService *edgeupdateschedule.Service
EdgeStackService *edgestack.Service
EndpointGroupService *endpointgroup.Service
EndpointService *endpoint.Service
@@ -64,7 +59,6 @@ type Store struct {
APIKeyRepositoryService *apikeyrepository.Service
ScheduleService *schedule.Service
SettingsService *settings.Service
SnapshotService *snapshot.Service
SSLSettingsService *ssl.Service
StackService *stack.Service
TagService *tag.Service
@@ -95,12 +89,6 @@ func (store *Store) initServices() error {
}
store.DockerHubService = dockerhubService
edgeUpdateScheduleService, err := edgeupdateschedule.NewService(store.connection)
if err != nil {
return err
}
store.EdgeUpdateScheduleService = edgeUpdateScheduleService
edgeStackService, err := edgestack.NewService(store.connection)
if err != nil {
return err
@@ -173,12 +161,6 @@ func (store *Store) initServices() error {
}
store.SettingsService = settingsService
snapshotService, err := snapshot.NewService(store.connection)
if err != nil {
return err
}
store.SnapshotService = snapshotService
sslSettingsService, err := ssl.NewService(store.connection)
if err != nil {
return err
@@ -263,11 +245,6 @@ func (store *Store) EdgeJob() dataservices.EdgeJobService {
return store.EdgeJobService
}
// EdgeUpdateSchedule gives access to the EdgeUpdateSchedule data management layer
func (store *Store) EdgeUpdateSchedule() dataservices.EdgeUpdateScheduleService {
return store.EdgeUpdateScheduleService
}
// EdgeStack gives access to the EdgeStack data management layer
func (store *Store) EdgeStack() dataservices.EdgeStackService {
return store.EdgeStackService
@@ -323,10 +300,6 @@ func (store *Store) Settings() dataservices.SettingsService {
return store.SettingsService
}
func (store *Store) Snapshot() dataservices.SnapshotService {
return store.SnapshotService
}
// SSLSettings gives access to the SSL Settings data management layer
func (store *Store) SSLSettings() dataservices.SSLSettingsService {
return store.SSLSettingsService
@@ -387,7 +360,6 @@ type storeExport struct {
Role []portainer.Role `json:"roles,omitempty"`
Schedules []portainer.Schedule `json:"schedules,omitempty"`
Settings portainer.Settings `json:"settings,omitempty"`
Snapshot []portainer.Snapshot `json:"snapshots,omitempty"`
SSLSettings portainer.SSLSettings `json:"ssl,omitempty"`
Stack []portainer.Stack `json:"stacks,omitempty"`
Tag []portainer.Tag `json:"tags,omitempty"`
@@ -406,7 +378,7 @@ func (store *Store) Export(filename string) (err error) {
if c, err := store.CustomTemplate().CustomTemplates(); err != nil {
if !store.IsErrObjectNotFound(err) {
log.Error().Err(err).Msg("exporting Custom Templates")
logrus.WithError(err).Errorf("Exporting Custom Templates")
}
} else {
backup.CustomTemplate = c
@@ -414,7 +386,7 @@ func (store *Store) Export(filename string) (err error) {
if e, err := store.EdgeGroup().EdgeGroups(); err != nil {
if !store.IsErrObjectNotFound(err) {
log.Error().Err(err).Msg("exporting Edge Groups")
logrus.WithError(err).Errorf("Exporting Edge Groups")
}
} else {
backup.EdgeGroup = e
@@ -422,7 +394,7 @@ func (store *Store) Export(filename string) (err error) {
if e, err := store.EdgeJob().EdgeJobs(); err != nil {
if !store.IsErrObjectNotFound(err) {
log.Error().Err(err).Msg("exporting Edge Jobs")
logrus.WithError(err).Errorf("Exporting Edge Jobs")
}
} else {
backup.EdgeJob = e
@@ -430,7 +402,7 @@ func (store *Store) Export(filename string) (err error) {
if e, err := store.EdgeStack().EdgeStacks(); err != nil {
if !store.IsErrObjectNotFound(err) {
log.Error().Err(err).Msg("exporting Edge Stacks")
logrus.WithError(err).Errorf("Exporting Edge Stacks")
}
} else {
backup.EdgeStack = e
@@ -438,7 +410,7 @@ func (store *Store) Export(filename string) (err error) {
if e, err := store.Endpoint().Endpoints(); err != nil {
if !store.IsErrObjectNotFound(err) {
log.Error().Err(err).Msg("exporting Endpoints")
logrus.WithError(err).Errorf("Exporting Endpoints")
}
} else {
backup.Endpoint = e
@@ -446,7 +418,7 @@ func (store *Store) Export(filename string) (err error) {
if e, err := store.EndpointGroup().EndpointGroups(); err != nil {
if !store.IsErrObjectNotFound(err) {
log.Error().Err(err).Msg("exporting Endpoint Groups")
logrus.WithError(err).Errorf("Exporting Endpoint Groups")
}
} else {
backup.EndpointGroup = e
@@ -454,7 +426,7 @@ func (store *Store) Export(filename string) (err error) {
if r, err := store.EndpointRelation().EndpointRelations(); err != nil {
if !store.IsErrObjectNotFound(err) {
log.Error().Err(err).Msg("exporting Endpoint Relations")
logrus.WithError(err).Errorf("Exporting Endpoint Relations")
}
} else {
backup.EndpointRelation = r
@@ -462,7 +434,7 @@ func (store *Store) Export(filename string) (err error) {
if r, err := store.ExtensionService.Extensions(); err != nil {
if !store.IsErrObjectNotFound(err) {
log.Error().Err(err).Msg("exporting Extensions")
logrus.WithError(err).Errorf("Exporting Extensions")
}
} else {
backup.Extensions = r
@@ -470,7 +442,7 @@ func (store *Store) Export(filename string) (err error) {
if r, err := store.HelmUserRepository().HelmUserRepositories(); err != nil {
if !store.IsErrObjectNotFound(err) {
log.Error().Err(err).Msg("exporting Helm User Repositories")
logrus.WithError(err).Errorf("Exporting Helm User Repositories")
}
} else {
backup.HelmUserRepository = r
@@ -478,7 +450,7 @@ func (store *Store) Export(filename string) (err error) {
if r, err := store.Registry().Registries(); err != nil {
if !store.IsErrObjectNotFound(err) {
log.Error().Err(err).Msg("exporting Registries")
logrus.WithError(err).Errorf("Exporting Registries")
}
} else {
backup.Registry = r
@@ -486,7 +458,7 @@ func (store *Store) Export(filename string) (err error) {
if c, err := store.ResourceControl().ResourceControls(); err != nil {
if !store.IsErrObjectNotFound(err) {
log.Error().Err(err).Msg("exporting Resource Controls")
logrus.WithError(err).Errorf("Exporting Resource Controls")
}
} else {
backup.ResourceControl = c
@@ -494,7 +466,7 @@ func (store *Store) Export(filename string) (err error) {
if role, err := store.Role().Roles(); err != nil {
if !store.IsErrObjectNotFound(err) {
log.Error().Err(err).Msg("exporting Roles")
logrus.WithError(err).Errorf("Exporting Roles")
}
} else {
backup.Role = role
@@ -502,7 +474,7 @@ func (store *Store) Export(filename string) (err error) {
if r, err := store.ScheduleService.Schedules(); err != nil {
if !store.IsErrObjectNotFound(err) {
log.Error().Err(err).Msg("exporting Schedules")
logrus.WithError(err).Errorf("Exporting Schedules")
}
} else {
backup.Schedules = r
@@ -510,23 +482,15 @@ func (store *Store) Export(filename string) (err error) {
if settings, err := store.Settings().Settings(); err != nil {
if !store.IsErrObjectNotFound(err) {
log.Error().Err(err).Msg("exporting Settings")
logrus.WithError(err).Errorf("Exporting Settings")
}
} else {
backup.Settings = *settings
}
if snapshot, err := store.Snapshot().Snapshots(); err != nil {
if !store.IsErrObjectNotFound(err) {
log.Err(err).Msg("Exporting Snapshots")
}
} else {
backup.Snapshot = snapshot
}
if settings, err := store.SSLSettings().Settings(); err != nil {
if !store.IsErrObjectNotFound(err) {
log.Error().Err(err).Msg("exporting SSL Settings")
logrus.WithError(err).Errorf("Exporting SSL Settings")
}
} else {
backup.SSLSettings = *settings
@@ -534,7 +498,7 @@ func (store *Store) Export(filename string) (err error) {
if t, err := store.Stack().Stacks(); err != nil {
if !store.IsErrObjectNotFound(err) {
log.Error().Err(err).Msg("exporting Stacks")
logrus.WithError(err).Errorf("Exporting Stacks")
}
} else {
backup.Stack = t
@@ -542,7 +506,7 @@ func (store *Store) Export(filename string) (err error) {
if t, err := store.Tag().Tags(); err != nil {
if !store.IsErrObjectNotFound(err) {
log.Error().Err(err).Msg("exporting Tags")
logrus.WithError(err).Errorf("Exporting Tags")
}
} else {
backup.Tag = t
@@ -550,7 +514,7 @@ func (store *Store) Export(filename string) (err error) {
if t, err := store.TeamMembership().TeamMemberships(); err != nil {
if !store.IsErrObjectNotFound(err) {
log.Error().Err(err).Msg("exporting Team Memberships")
logrus.WithError(err).Errorf("Exporting Team Memberships")
}
} else {
backup.TeamMembership = t
@@ -558,7 +522,7 @@ func (store *Store) Export(filename string) (err error) {
if t, err := store.Team().Teams(); err != nil {
if !store.IsErrObjectNotFound(err) {
log.Error().Err(err).Msg("exporting Teams")
logrus.WithError(err).Errorf("Exporting Teams")
}
} else {
backup.Team = t
@@ -566,7 +530,7 @@ func (store *Store) Export(filename string) (err error) {
if info, err := store.TunnelServer().Info(); err != nil {
if !store.IsErrObjectNotFound(err) {
log.Error().Err(err).Msg("exporting Tunnel Server")
logrus.WithError(err).Errorf("Exporting Tunnel Server")
}
} else {
backup.TunnelServer = *info
@@ -574,7 +538,7 @@ func (store *Store) Export(filename string) (err error) {
if users, err := store.User().Users(); err != nil {
if !store.IsErrObjectNotFound(err) {
log.Error().Err(err).Msg("exporting Users")
logrus.WithError(err).Errorf("Exporting Users")
}
} else {
backup.User = users
@@ -582,7 +546,7 @@ func (store *Store) Export(filename string) (err error) {
if webhooks, err := store.Webhook().Webhooks(); err != nil {
if !store.IsErrObjectNotFound(err) {
log.Error().Err(err).Msg("exporting Webhooks")
logrus.WithError(err).Errorf("Exporting Webhooks")
}
} else {
backup.Webhook = webhooks
@@ -590,7 +554,7 @@ func (store *Store) Export(filename string) (err error) {
v, err := store.Version().DBVersion()
if err != nil && !store.IsErrObjectNotFound(err) {
log.Error().Err(err).Msg("exporting DB version")
logrus.WithError(err).Errorf("Exporting DB version")
}
instance, _ := store.Version().InstanceID()
backup.Version = map[string]string{
@@ -600,7 +564,7 @@ func (store *Store) Export(filename string) (err error) {
backup.Metadata, err = store.connection.BackupMetadata()
if err != nil {
log.Error().Err(err).Msg("exporting Metadata")
logrus.WithError(err).Errorf("Exporting Metadata")
}
b, err := json.MarshalIndent(backup, "", " ")
@@ -611,6 +575,7 @@ func (store *Store) Export(filename string) (err error) {
}
func (store *Store) Import(filename string) (err error) {
backup := storeExport{}
s, err := ioutil.ReadFile(filename)
@@ -626,13 +591,13 @@ func (store *Store) Import(filename string) (err error) {
if dbversion, ok := backup.Version["DB_VERSION"]; ok {
if v, err := strconv.Atoi(dbversion); err == nil {
if err := store.Version().StoreDBVersion(v); err != nil {
log.Error().Err(err).Msg("DB_VERSION import issue")
logrus.WithError(err).Errorf("DB_VERSION import issue")
}
}
}
if instanceID, ok := backup.Version["INSTANCE_ID"]; ok {
if err := store.Version().StoreInstanceID(instanceID); err != nil {
log.Error().Err(err).Msg("INSTANCE_ID import issue")
logrus.WithError(err).Errorf("INSTANCE_ID import issue")
}
}
@@ -683,10 +648,6 @@ func (store *Store) Import(filename string) (err error) {
store.Settings().UpdateSettings(&backup.Settings)
store.SSLSettings().UpdateSettings(&backup.SSLSettings)
for _, v := range backup.Snapshot {
store.Snapshot().UpdateSnapshot(&v)
}
for _, v := range backup.Stack {
store.Stack().UpdateStack(v.ID, &v)
}
@@ -707,7 +668,7 @@ func (store *Store) Import(filename string) (err error) {
for _, user := range backup.User {
if err := store.User().UpdateUser(user.ID, &user); err != nil {
log.Debug().Str("user", fmt.Sprintf("%+v", user)).Err(err).Msg("user: failed to Update Database")
logrus.WithField("user", user).WithError(err).Errorf("User: Failed to Update Database")
}
}
+121 -142
View File
@@ -27,9 +27,6 @@
],
"endpoints": [
{
"Agent": {
"Version": ""
},
"AuthorizedTeams": null,
"AuthorizedUsers": null,
"AzureCredentials": {
@@ -46,29 +43,21 @@
},
"EdgeCheckinInterval": 0,
"EdgeKey": "",
"Gpus": [],
"GroupId": 1,
"Id": 1,
"IsEdgeDevice": false,
"Kubernetes": {
"Configuration": {
"AllowNoneIngressClass": false,
"EnableResourceOverCommit": false,
"IngressAvailabilityPerNamespace": true,
"IngressClasses": null,
"ResourceOverCommitPercentage": 0,
"RestrictDefaultNamespace": false,
"StorageClasses": null,
"UseLoadBalancer": false,
"UseServerMetrics": false
},
"Snapshots": []
"Snapshots": null
},
"LastCheckInDate": 0,
"Name": "local",
"PostInitMigrations": {
"MigrateIngresses": true
},
"PublicURL": "",
"QueryDate": 0,
"SecuritySettings": {
@@ -82,7 +71,125 @@
"allowVolumeBrowserForRegularUsers": false,
"enableHostManagementFeatures": false
},
"Snapshots": [],
"Snapshots": [
{
"DockerSnapshotRaw": {
"Containers": null,
"Images": null,
"Info": {
"Architecture": "",
"BridgeNfIp6tables": false,
"BridgeNfIptables": false,
"CPUSet": false,
"CPUShares": false,
"CgroupDriver": "",
"ContainerdCommit": {
"Expected": "",
"ID": ""
},
"Containers": 0,
"ContainersPaused": 0,
"ContainersRunning": 0,
"ContainersStopped": 0,
"CpuCfsPeriod": false,
"CpuCfsQuota": false,
"Debug": false,
"DefaultRuntime": "",
"DockerRootDir": "",
"Driver": "",
"DriverStatus": null,
"ExperimentalBuild": false,
"GenericResources": null,
"HttpProxy": "",
"HttpsProxy": "",
"ID": "",
"IPv4Forwarding": false,
"Images": 0,
"IndexServerAddress": "",
"InitBinary": "",
"InitCommit": {
"Expected": "",
"ID": ""
},
"Isolation": "",
"KernelMemory": false,
"KernelMemoryTCP": false,
"KernelVersion": "",
"Labels": null,
"LiveRestoreEnabled": false,
"LoggingDriver": "",
"MemTotal": 0,
"MemoryLimit": false,
"NCPU": 0,
"NEventsListener": 0,
"NFd": 0,
"NGoroutines": 0,
"Name": "",
"NoProxy": "",
"OSType": "",
"OSVersion": "",
"OomKillDisable": false,
"OperatingSystem": "",
"PidsLimit": false,
"Plugins": {
"Authorization": null,
"Log": null,
"Network": null,
"Volume": null
},
"RegistryConfig": null,
"RuncCommit": {
"Expected": "",
"ID": ""
},
"Runtimes": null,
"SecurityOptions": null,
"ServerVersion": "",
"SwapLimit": false,
"Swarm": {
"ControlAvailable": false,
"Error": "",
"LocalNodeState": "",
"NodeAddr": "",
"NodeID": "",
"RemoteManagers": null
},
"SystemTime": "",
"Warnings": null
},
"Networks": null,
"Version": {
"ApiVersion": "",
"Arch": "",
"GitCommit": "",
"GoVersion": "",
"Os": "",
"Platform": {
"Name": ""
},
"Version": ""
},
"Volumes": {
"Volumes": null,
"Warnings": null
}
},
"DockerVersion": "20.10.13",
"HealthyContainerCount": 0,
"ImageCount": 9,
"NodeCount": 0,
"RunningContainerCount": 5,
"ServiceCount": 0,
"StackCount": 2,
"StoppedContainerCount": 0,
"Swarm": false,
"Time": 1648610112,
"TotalCPU": 8,
"TotalMemory": 25098706944,
"UnhealthyContainerCount": 0,
"VolumeCount": 10
}
],
"Status": 1,
"TLSConfig": {
"TLS": false,
@@ -662,131 +769,6 @@
"mpsUser": ""
}
},
"snapshots": [
{
"Docker": {
"DockerSnapshotRaw": {
"Containers": null,
"Images": null,
"Info": {
"Architecture": "",
"BridgeNfIp6tables": false,
"BridgeNfIptables": false,
"CPUSet": false,
"CPUShares": false,
"CgroupDriver": "",
"ContainerdCommit": {
"Expected": "",
"ID": ""
},
"Containers": 0,
"ContainersPaused": 0,
"ContainersRunning": 0,
"ContainersStopped": 0,
"CpuCfsPeriod": false,
"CpuCfsQuota": false,
"Debug": false,
"DefaultRuntime": "",
"DockerRootDir": "",
"Driver": "",
"DriverStatus": null,
"ExperimentalBuild": false,
"GenericResources": null,
"HttpProxy": "",
"HttpsProxy": "",
"ID": "",
"IPv4Forwarding": false,
"Images": 0,
"IndexServerAddress": "",
"InitBinary": "",
"InitCommit": {
"Expected": "",
"ID": ""
},
"Isolation": "",
"KernelMemory": false,
"KernelMemoryTCP": false,
"KernelVersion": "",
"Labels": null,
"LiveRestoreEnabled": false,
"LoggingDriver": "",
"MemTotal": 0,
"MemoryLimit": false,
"NCPU": 0,
"NEventsListener": 0,
"NFd": 0,
"NGoroutines": 0,
"Name": "",
"NoProxy": "",
"OSType": "",
"OSVersion": "",
"OomKillDisable": false,
"OperatingSystem": "",
"PidsLimit": false,
"Plugins": {
"Authorization": null,
"Log": null,
"Network": null,
"Volume": null
},
"RegistryConfig": null,
"RuncCommit": {
"Expected": "",
"ID": ""
},
"Runtimes": null,
"SecurityOptions": null,
"ServerVersion": "",
"SwapLimit": false,
"Swarm": {
"ControlAvailable": false,
"Error": "",
"LocalNodeState": "",
"NodeAddr": "",
"NodeID": "",
"RemoteManagers": null
},
"SystemTime": "",
"Warnings": null
},
"Networks": null,
"Version": {
"ApiVersion": "",
"Arch": "",
"GitCommit": "",
"GoVersion": "",
"Os": "",
"Platform": {
"Name": ""
},
"Version": ""
},
"Volumes": {
"Volumes": null,
"Warnings": null
}
},
"DockerVersion": "20.10.13",
"GpuUseAll": false,
"GpuUseList": null,
"HealthyContainerCount": 0,
"ImageCount": 9,
"NodeCount": 0,
"RunningContainerCount": 5,
"ServiceCount": 0,
"StackCount": 2,
"StoppedContainerCount": 0,
"Swarm": false,
"Time": 1648610112,
"TotalCPU": 8,
"TotalMemory": 25098706944,
"UnhealthyContainerCount": 0,
"VolumeCount": 10
},
"EndpointId": 1,
"Kubernetes": null
}
],
"ssl": {
"certPath": "",
"httpEnabled": true,
@@ -808,7 +790,6 @@
"IsComposeFormat": false,
"Name": "alpine",
"Namespace": "",
"Option": null,
"ProjectPath": "/home/prabhat/portainer/data/ce1.25/compose/2",
"ResourceControl": null,
"Status": 1,
@@ -831,7 +812,6 @@
"IsComposeFormat": false,
"Name": "redis",
"Namespace": "",
"Option": null,
"ProjectPath": "/home/prabhat/portainer/data/ce1.25/compose/5",
"ResourceControl": null,
"Status": 1,
@@ -854,7 +834,6 @@
"IsComposeFormat": false,
"Name": "nginx",
"Namespace": "",
"Option": null,
"ProjectPath": "/home/prabhat/portainer/data/ce1.25/compose/6",
"ResourceControl": null,
"Status": 1,
@@ -931,7 +910,7 @@
],
"version": {
"DB_UPDATING": "false",
"DB_VERSION": "71",
"DB_VERSION": "52",
"INSTANCE_ID": "null"
}
}
+21 -17
View File
@@ -1,14 +1,15 @@
package datastore
import (
"testing"
"io/ioutil"
"log"
"os"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/database"
"github.com/portainer/portainer/api/filesystem"
"github.com/pkg/errors"
"github.com/rs/zerolog/log"
"github.com/portainer/portainer/api/filesystem"
)
var errTempDir = errors.New("can't create a temp dir")
@@ -17,22 +18,25 @@ func (store *Store) GetConnection() portainer.Connection {
return store.connection
}
func MustNewTestStore(t *testing.T, init, secure bool) (bool, *Store, func()) {
newStore, store, teardown, err := NewTestStore(t, init, secure)
func MustNewTestStore(init, secure bool) (bool, *Store, func()) {
newStore, store, teardown, err := NewTestStore(init, secure)
if err != nil {
if !errors.Is(err, errTempDir) {
teardown()
}
log.Fatal().Err(err).Msg("")
log.Fatal(err)
}
return newStore, store, teardown
}
func NewTestStore(t *testing.T, init, secure bool) (bool, *Store, func(), error) {
func NewTestStore(init, secure bool) (bool, *Store, func(), error) {
// Creates unique temp directory in a concurrency friendly manner.
storePath := t.TempDir()
storePath, err := ioutil.TempDir("", "test-store")
if err != nil {
return false, nil, nil, errors.Wrap(errTempDir, err.Error())
}
fileService, err := filesystem.NewService(storePath, "")
if err != nil {
return false, nil, nil, err
@@ -47,15 +51,12 @@ func NewTestStore(t *testing.T, init, secure bool) (bool, *Store, func(), error)
if err != nil {
panic(err)
}
store := NewStore(storePath, fileService, connection)
newStore, err := store.Open()
if err != nil {
return newStore, nil, nil, err
}
log.Debug().Msg("opened")
if init {
err = store.Init()
if err != nil {
@@ -63,8 +64,6 @@ func NewTestStore(t *testing.T, init, secure bool) (bool, *Store, func(), error)
}
}
log.Debug().Msg("initialised")
if newStore {
// from MigrateData
store.VersionService.StoreDBVersion(portainer.DBVersion)
@@ -74,15 +73,20 @@ func NewTestStore(t *testing.T, init, secure bool) (bool, *Store, func(), error)
}
teardown := func() {
teardown(store)
teardown(store, storePath)
}
return newStore, store, teardown, nil
}
func teardown(store *Store) {
func teardown(store *Store, storePath string) {
err := store.Close()
if err != nil {
log.Fatal().Err(err).Msg("")
log.Fatalln(err)
}
err = os.RemoveAll(storePath)
if err != nil {
log.Fatalln(err)
}
}
+4 -4
View File
@@ -1,11 +1,11 @@
package demo
import (
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
"log"
"github.com/pkg/errors"
"github.com/rs/zerolog/log"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
)
type EnvironmentDetails struct {
@@ -27,7 +27,7 @@ func (service *Service) Details() EnvironmentDetails {
}
func (service *Service) Init(store dataservices.DataStore, cryptoService portainer.CryptoService) error {
log.Info().Msg("starting demo environment")
log.Print("[INFO] [main] Starting demo environment")
isClean, err := isCleanStore(store)
if err != nil {
-9
View File
@@ -60,15 +60,6 @@ func initDemoLocalEndpoint(store dataservices.DataStore) (portainer.EndpointID,
}
err := store.Endpoint().Create(localEndpoint)
if err != nil {
return id, errors.WithMessage(err, "failed creating local endpoint")
}
err = store.Snapshot().Create(&portainer.Snapshot{EndpointID: id})
if err != nil {
return id, errors.WithMessage(err, "failed creating snapshot")
}
return id, errors.WithMessage(err, "failed creating local endpoint")
}
+10 -44
View File
@@ -2,16 +2,14 @@ package docker
import (
"context"
"log"
"strings"
"time"
portainer "github.com/portainer/portainer/api"
"github.com/docker/docker/api/types"
_container "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/client"
"github.com/rs/zerolog/log"
"github.com/portainer/portainer/api"
)
// Snapshotter represents a service used to create environment(endpoint) snapshots
@@ -49,44 +47,44 @@ func snapshot(cli *client.Client, endpoint *portainer.Endpoint) (*portainer.Dock
err = snapshotInfo(snapshot, cli)
if err != nil {
log.Warn().Str("environment", endpoint.Name).Err(err).Msg("unable to snapshot engine information")
log.Printf("[WARN] [docker,snapshot] [message: unable to snapshot engine information] [environment: %s] [err: %s]", endpoint.Name, err)
}
if snapshot.Swarm {
err = snapshotSwarmServices(snapshot, cli)
if err != nil {
log.Warn().Str("environment", endpoint.Name).Err(err).Msg("unable to snapshot Swarm services")
log.Printf("[WARN] [docker,snapshot] [message: unable to snapshot Swarm services] [environment: %s] [err: %s]", endpoint.Name, err)
}
err = snapshotNodes(snapshot, cli)
if err != nil {
log.Warn().Str("environment", endpoint.Name).Err(err).Msg("unable to snapshot Swarm nodes")
log.Printf("[WARN] [docker,snapshot] [message: unable to snapshot Swarm nodes] [environment: %s] [err: %s]", endpoint.Name, err)
}
}
err = snapshotContainers(snapshot, cli)
if err != nil {
log.Warn().Str("environment", endpoint.Name).Err(err).Msg("unable to snapshot containers")
log.Printf("[WARN] [docker,snapshot] [message: unable to snapshot containers] [environment: %s] [err: %s]", endpoint.Name, err)
}
err = snapshotImages(snapshot, cli)
if err != nil {
log.Warn().Str("environment", endpoint.Name).Err(err).Msg("unable to snapshot images")
log.Printf("[WARN] [docker,snapshot] [message: unable to snapshot images] [environment: %s] [err: %s]", endpoint.Name, err)
}
err = snapshotVolumes(snapshot, cli)
if err != nil {
log.Warn().Str("environment", endpoint.Name).Err(err).Msg("unable to snapshot volumes")
log.Printf("[WARN] [docker,snapshot] [message: unable to snapshot volumes] [environment: %s] [err: %s]", endpoint.Name, err)
}
err = snapshotNetworks(snapshot, cli)
if err != nil {
log.Warn().Str("environment", endpoint.Name).Err(err).Msg("unable to snapshot networks")
log.Printf("[WARN] [docker,snapshot] [message: unable to snapshot networks] [environment: %s] [err: %s]", endpoint.Name, err)
}
err = snapshotVersion(snapshot, cli)
if err != nil {
log.Warn().Str("environment", endpoint.Name).Err(err).Msg("unable to snapshot engine version")
log.Printf("[WARN] [docker,snapshot] [message: unable to snapshot engine version] [environment: %s] [err: %s]", endpoint.Name, err)
}
snapshot.Time = time.Now().Unix()
@@ -156,35 +154,11 @@ func snapshotContainers(snapshot *portainer.DockerSnapshot, cli *client.Client)
healthyContainers := 0
unhealthyContainers := 0
stacks := make(map[string]struct{})
gpuUseSet := make(map[string]struct{})
gpuUseAll := false
for _, container := range containers {
if container.State == "exited" {
stoppedContainers++
} else if container.State == "running" {
runningContainers++
// snapshot GPUs
response, err := cli.ContainerInspect(context.Background(), container.ID)
if err != nil {
return err
}
var gpuOptions *_container.DeviceRequest = nil
for _, deviceRequest := range response.HostConfig.Resources.DeviceRequests {
if deviceRequest.Driver == "nvidia" || deviceRequest.Capabilities[0][0] == "gpu" {
gpuOptions = &deviceRequest
}
}
if gpuOptions != nil {
if gpuOptions.Count == -1 {
gpuUseAll = true
}
for _, id := range gpuOptions.DeviceIDs {
gpuUseSet[id] = struct{}{}
}
}
}
if strings.Contains(container.Status, "(healthy)") {
@@ -200,14 +174,6 @@ func snapshotContainers(snapshot *portainer.DockerSnapshot, cli *client.Client)
}
}
gpuUseList := make([]string, 0, len(gpuUseSet))
for gpuUse := range gpuUseSet {
gpuUseList = append(gpuUseList, gpuUse)
}
snapshot.GpuUseAll = gpuUseAll
snapshot.GpuUseList = gpuUseList
snapshot.RunningContainerCount = runningContainers
snapshot.StoppedContainerCount = stoppedContainers
snapshot.HealthyContainerCount = healthyContainers
-102
View File
@@ -1,102 +0,0 @@
package edgetypes
import portainer "github.com/portainer/portainer/api"
const (
// PortainerAgentUpdateScheduleIDHeader represents the name of the header containing the update schedule id
PortainerAgentUpdateScheduleIDHeader = "X-Portainer-Update-Schedule-ID"
// PortainerAgentUpdateStatusHeader is the name of the header that will have the update status
PortainerAgentUpdateStatusHeader = "X-Portainer-Update-Status"
// PortainerAgentUpdateErrorHeader is the name of the header that will have the update error
PortainerAgentUpdateErrorHeader = "X-Portainer-Update-Error"
)
type (
// UpdateScheduleID represents an Edge schedule identifier
UpdateScheduleID int
// UpdateSchedule represents a schedule for update/rollback of edge devices
UpdateSchedule struct {
// EdgeUpdateSchedule Identifier
ID UpdateScheduleID `json:"id" example:"1"`
// Name of the schedule
Name string `json:"name" example:"Update Schedule"`
// Type of the schedule
Time int64 `json:"time" example:"1564897200"`
// EdgeGroups to be updated
GroupIDs []portainer.EdgeGroupID `json:"groupIds" example:"1"`
// Type of the update (1 - update, 2 - rollback)
Type UpdateScheduleType `json:"type" example:"1" enums:"1,2"`
// Status of the schedule, grouped by environment id
Status map[portainer.EndpointID]UpdateScheduleStatus `json:"status"`
// Created timestamp
Created int64 `json:"created" example:"1564897200"`
// Created by user id
CreatedBy portainer.UserID `json:"createdBy" example:"1"`
}
// UpdateScheduleType represents type of an Edge update schedule
UpdateScheduleType int
// UpdateScheduleStatus represents status of an Edge update schedule
UpdateScheduleStatus struct {
// Status of the schedule (0 - pending, 1 - failed, 2 - success)
Status UpdateScheduleStatusType `json:"status" example:"1" enums:"1,2,3"`
// Error message if status is failed
Error string `json:"error" example:""`
// Target version of the edge agent
TargetVersion string `json:"targetVersion" example:"1"`
// Current version of the edge agent
CurrentVersion string `json:"currentVersion" example:"1"`
}
// UpdateScheduleStatusType represents status type of an Edge update schedule
UpdateScheduleStatusType int
VersionUpdateRequest struct {
// Target version
Version string
// Scheduled time
ScheduledTime int64
// If need to update
Active bool
// Update schedule ID
ScheduleID UpdateScheduleID
}
// VersionUpdateStatus represents the status of an agent version update
VersionUpdateStatus struct {
Status UpdateScheduleStatusType
ScheduleID UpdateScheduleID
Error string
}
// EndpointUpdateScheduleRelation represents the relation between an environment(endpoint) and an update schedule
EndpointUpdateScheduleRelation struct {
EnvironmentID portainer.EndpointID `json:"environmentId"`
ScheduleID UpdateScheduleID `json:"scheduleId"`
TargetVersion string `json:"targetVersion"`
Status UpdateScheduleStatusType `json:"status"`
Error string `json:"error"`
Type UpdateScheduleType `json:"type"`
ScheduledTime int64 `json:"scheduledTime"`
}
)
const (
_ UpdateScheduleType = iota
// UpdateScheduleUpdate represents an edge device scheduled for an update
UpdateScheduleUpdate
// UpdateScheduleRollback represents an edge device scheduled for a rollback
UpdateScheduleRollback
)
const (
// UpdateScheduleStatusPending represents a pending edge update schedule
UpdateScheduleStatusPending UpdateScheduleStatusType = iota
// UpdateScheduleStatusError represents a failed edge update schedule
UpdateScheduleStatusError
// UpdateScheduleStatusSuccess represents a successful edge update schedule
UpdateScheduleStatusSuccess
)
+183 -46
View File
@@ -6,6 +6,7 @@ import (
"io"
"os"
"path"
"regexp"
"strings"
"github.com/pkg/errors"
@@ -13,6 +14,7 @@ import (
libstack "github.com/portainer/docker-compose-wrapper"
"github.com/portainer/docker-compose-wrapper/compose"
"github.com/docker/cli/cli/compose/loader"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/http/proxy"
"github.com/portainer/portainer/api/http/proxy/factory"
@@ -54,13 +56,13 @@ func (manager *ComposeStackManager) Up(ctx context.Context, stack *portainer.Sta
defer proxy.Close()
}
envFile, err := createEnvFile(stack)
envFilePath, err := createEnvFile(stack)
if err != nil {
return errors.Wrap(err, "failed to create env file")
}
filePaths := stackutils.GetStackFilePaths(stack)
err = manager.deployer.Deploy(ctx, stack.ProjectPath, url, stack.Name, filePaths, envFile, forceRereate)
err = manager.deployer.Deploy(ctx, stack.ProjectPath, url, stack.Name, filePaths, envFilePath, forceRereate)
return errors.Wrap(err, "failed to deploy a stack")
}
@@ -74,36 +76,13 @@ func (manager *ComposeStackManager) Down(ctx context.Context, stack *portainer.S
defer proxy.Close()
}
envFile, err := createEnvFile(stack)
if err != nil {
return errors.Wrap(err, "failed to create env file")
}
filePaths := stackutils.GetStackFilePaths(stack)
err = manager.deployer.Remove(ctx, stack.ProjectPath, url, stack.Name, filePaths, envFile)
return errors.Wrap(err, "failed to remove a stack")
}
// Pull an image associated with a service defined in a docker-compose.yml or docker-stack.yml file,
// but does not start containers based on those images.
func (manager *ComposeStackManager) Pull(ctx context.Context, stack *portainer.Stack, endpoint *portainer.Endpoint) error {
url, proxy, err := manager.fetchEndpointProxy(endpoint)
if err != nil {
if err := updateNetworkEnvFile(stack); err != nil {
return err
}
if proxy != nil {
defer proxy.Close()
}
envFile, err := createEnvFile(stack)
if err != nil {
return errors.Wrap(err, "failed to create env file")
}
filePaths := stackutils.GetStackFilePaths(stack)
err = manager.deployer.Pull(ctx, stack.ProjectPath, url, stack.Name, filePaths, envFile)
return errors.Wrap(err, "failed to pull images of the stack")
err = manager.deployer.Remove(ctx, stack.ProjectPath, url, stack.Name, filePaths, "")
return errors.Wrap(err, "failed to remove a stack")
}
// NormalizeStackName returns a new stack name with unsupported characters replaced
@@ -124,42 +103,200 @@ func (manager *ComposeStackManager) fetchEndpointProxy(endpoint *portainer.Endpo
return fmt.Sprintf("tcp://127.0.0.1:%d", proxy.Port), proxy, nil
}
// createEnvFile creates a file that would hold both "in-place" and default environment variables.
// It will return the name of the file if the stack has "in-place" env vars, otherwise empty string.
func createEnvFile(stack *portainer.Stack) (string, error) {
// workaround for EE-1862. It will have to be removed when
// docker/compose upgraded to v2.x.
if err := createNetworkEnvFile(stack); err != nil {
return "", errors.Wrap(err, "failed to create network env file")
}
if stack.Env == nil || len(stack.Env) == 0 {
return "", nil
}
envFilePath := path.Join(stack.ProjectPath, "stack.env")
envfile, err := os.OpenFile(envFilePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
return "", err
}
defer envfile.Close()
copyDefaultEnvFile(stack, envfile)
for _, v := range stack.Env {
envfile.WriteString(fmt.Sprintf("%s=%s\n", v.Name, v.Value))
}
envfile.Close()
return "stack.env", nil
}
// copyDefaultEnvFile copies the default .env file if it exists to the provided writer
func copyDefaultEnvFile(stack *portainer.Stack, w io.Writer) {
defaultEnvFile, err := os.Open(path.Join(path.Join(stack.ProjectPath, path.Dir(stack.EntryPoint)), ".env"))
if err != nil {
// If cannot open a default file, then don't need to copy it.
// We could as well stat it and check if it exists, but this is more efficient.
return
func fileNotExist(filePath string) bool {
if _, err := os.Stat(filePath); errors.Is(err, os.ErrNotExist) {
return true
}
defer defaultEnvFile.Close()
if _, err = io.Copy(w, defaultEnvFile); err == nil {
io.WriteString(w, "\n")
}
// If couldn't copy the .env file, then ignore the error and try to continue
return false
}
func updateNetworkEnvFile(stack *portainer.Stack) error {
envFilePath := path.Join(stack.ProjectPath, ".env")
stackFilePath := path.Join(stack.ProjectPath, "stack.env")
if fileNotExist(envFilePath) {
if fileNotExist(stackFilePath) {
return nil
}
flags := os.O_WRONLY | os.O_SYNC | os.O_CREATE
envFile, err := os.OpenFile(envFilePath, flags, 0666)
if err != nil {
return err
}
defer envFile.Close()
stackFile, err := os.Open(stackFilePath)
if err != nil {
return err
}
defer stackFile.Close()
_, err = io.Copy(envFile, stackFile)
return err
}
return nil
}
func createNetworkEnvFile(stack *portainer.Stack) error {
networkNameSet := NewStringSet()
for _, filePath := range stackutils.GetStackFilePaths(stack) {
networkNames, err := extractNetworkNames(filePath)
if err != nil {
return errors.Wrap(err, "failed to extract network name")
}
if networkNames == nil || networkNames.Len() == 0 {
continue
}
networkNameSet.Union(networkNames)
}
for _, s := range networkNameSet.List() {
if _, ok := os.LookupEnv(s); ok {
networkNameSet.Remove(s)
}
}
if networkNameSet.Len() == 0 && stack.Env == nil {
return nil
}
envfile, err := os.OpenFile(path.Join(stack.ProjectPath, ".env"),
os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
return errors.Wrap(err, "failed to open env file")
}
defer envfile.Close()
var scanEnvSettingFunc = func(name string) (string, bool) {
if stack.Env != nil {
for _, v := range stack.Env {
if name == v.Name {
return v.Value, true
}
}
}
return "", false
}
for _, s := range networkNameSet.List() {
if _, ok := scanEnvSettingFunc(s); !ok {
stack.Env = append(stack.Env, portainer.Pair{
Name: s,
Value: "None",
})
}
}
if stack.Env != nil {
for _, v := range stack.Env {
envfile.WriteString(
fmt.Sprintf("%s=%s\n", v.Name, v.Value))
}
}
return nil
}
func extractNetworkNames(filePath string) (StringSet, error) {
if info, err := os.Stat(filePath); errors.Is(err,
os.ErrNotExist) || info.IsDir() {
return nil, nil
}
stackFileContent, err := os.ReadFile(filePath)
if err != nil {
return nil, errors.Wrap(err, "failed to open yaml file")
}
config, err := loader.ParseYAML(stackFileContent)
if err != nil {
// invalid stack file
return nil, errors.Wrap(err, "invalid stack file")
}
var version string
if _, ok := config["version"]; ok {
version, _ = config["version"].(string)
}
var networks map[string]interface{}
if value, ok := config["networks"]; ok {
if value == nil {
return nil, nil
}
if networks, ok = value.(map[string]interface{}); !ok {
return nil, nil
}
} else {
return nil, nil
}
networkContent, err := loader.LoadNetworks(networks, version)
if err != nil {
return nil, nil // skip the error
}
re := regexp.MustCompile(`^\$\{?([^\}]+)\}?$`)
networkNames := NewStringSet()
for _, v := range networkContent {
matched := re.FindAllStringSubmatch(v.Name, -1)
if matched != nil && matched[0] != nil {
if strings.Contains(matched[0][1], ":-") {
continue
}
if strings.Contains(matched[0][1], "?") {
continue
}
if strings.Contains(matched[0][1], "-") {
continue
}
networkNames.Add(matched[0][1])
}
}
if networkNames.Len() == 0 {
return nil, nil
}
return networkNames, nil
}
+2 -3
View File
@@ -3,6 +3,7 @@ package exec
import (
"context"
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
@@ -11,8 +12,6 @@ import (
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/internal/testhelpers"
"github.com/rs/zerolog/log"
)
const composeFile = `version: "3.9"
@@ -78,7 +77,7 @@ func containerExists(containerName string) bool {
out, err := cmd.Output()
if err != nil {
log.Fatal().Err(err).Msg("failed to list containers")
log.Fatalf("failed to list containers: %s", err)
}
return strings.Contains(string(out), containerName)
+46 -12
View File
@@ -65,22 +65,56 @@ func Test_createEnvFile(t *testing.T) {
}
}
func Test_createEnvFile_mergesDefultAndInplaceEnvVars(t *testing.T) {
func Test_createNetworkEnvFile(t *testing.T) {
dir := t.TempDir()
os.WriteFile(path.Join(dir, ".env"), []byte("VAR1=VAL1\nVAR2=VAL2\n"), 0600)
stack := &portainer.Stack{
buf := []byte(`
version: '3.6'
services:
nginx-example:
image: nginx:latest
networks:
default:
name: ${test}
driver: bridge
`)
if err := ioutil.WriteFile(path.Join(dir,
"docker-compose.yml"), buf, 0644); err != nil {
t.Fatalf("Failed to create yaml file: %s", err)
}
stackWithoutEnv := &portainer.Stack{
ProjectPath: dir,
EntryPoint: "docker-compose.yml",
Env: []portainer.Pair{},
}
if err := createNetworkEnvFile(stackWithoutEnv); err != nil {
t.Fatalf("Failed to create network env file: %s", err)
}
content, err := ioutil.ReadFile(path.Join(dir, ".env"))
if err != nil {
t.Fatalf("Failed to read network env file: %s", err)
}
assert.Equal(t, "test=None\n", string(content))
stackWithEnv := &portainer.Stack{
ProjectPath: dir,
EntryPoint: "docker-compose.yml",
Env: []portainer.Pair{
{Name: "VAR1", Value: "NEW_VAL1"},
{Name: "VAR3", Value: "VAL3"},
{Name: "test", Value: "test-value"},
},
}
result, err := createEnvFile(stack)
assert.Equal(t, "stack.env", result)
assert.NoError(t, err)
assert.FileExists(t, path.Join(dir, "stack.env"))
f, _ := os.Open(path.Join(dir, "stack.env"))
content, _ := ioutil.ReadAll(f)
assert.Equal(t, []byte("VAR1=VAL1\nVAR2=VAL2\n\nVAR1=NEW_VAL1\nVAR3=VAL3\n"), content)
if err := createNetworkEnvFile(stackWithEnv); err != nil {
t.Fatalf("Failed to create network env file: %s", err)
}
content, err = ioutil.ReadFile(path.Join(dir, ".env"))
if err != nil {
t.Fatalf("Failed to read network env file: %s", err)
}
assert.Equal(t, "test=test-value\n", string(content))
}
+1 -4
View File
@@ -89,7 +89,7 @@ func (manager *SwarmStackManager) Logout(endpoint *portainer.Endpoint) error {
}
// Deploy executes the docker stack deploy command.
func (manager *SwarmStackManager) Deploy(stack *portainer.Stack, prune bool, pullImage bool, endpoint *portainer.Endpoint) error {
func (manager *SwarmStackManager) Deploy(stack *portainer.Stack, prune bool, endpoint *portainer.Endpoint) error {
filePaths := stackutils.GetStackFilePaths(stack)
command, args, err := manager.prepareDockerCommandAndArgs(manager.binaryPath, manager.configPath, endpoint)
if err != nil {
@@ -101,9 +101,6 @@ func (manager *SwarmStackManager) Deploy(stack *portainer.Stack, prune bool, pul
} else {
args = append(args, "stack", "deploy", "--with-registry-auth")
}
if !pullImage {
args = append(args, "--resolve-image=never")
}
args = configureFilePaths(args, filePaths)
args = append(args, stack.Name)
+18 -7
View File
@@ -11,13 +11,17 @@ import (
)
func Test_copyFile_returnsError_whenSourceDoesNotExist(t *testing.T) {
tmpdir := t.TempDir()
tmpdir, _ := ioutil.TempDir("", "backup")
defer os.RemoveAll(tmpdir)
err := copyFile("does-not-exist", tmpdir)
assert.Error(t, err)
}
func Test_copyFile_shouldMakeAbackup(t *testing.T) {
tmpdir := t.TempDir()
tmpdir, _ := ioutil.TempDir("", "backup")
defer os.RemoveAll(tmpdir)
content := []byte("content")
ioutil.WriteFile(path.Join(tmpdir, "origin"), content, 0600)
@@ -29,7 +33,8 @@ func Test_copyFile_shouldMakeAbackup(t *testing.T) {
}
func Test_CopyDir_shouldCopyAllFilesAndDirectories(t *testing.T) {
destination := t.TempDir()
destination, _ := ioutil.TempDir("", "destination")
defer os.RemoveAll(destination)
err := CopyDir("./testdata/copy_test", destination, true)
assert.NoError(t, err)
@@ -39,7 +44,8 @@ func Test_CopyDir_shouldCopyAllFilesAndDirectories(t *testing.T) {
}
func Test_CopyDir_shouldCopyOnlyDirContents(t *testing.T) {
destination := t.TempDir()
destination, _ := ioutil.TempDir("", "destination")
defer os.RemoveAll(destination)
err := CopyDir("./testdata/copy_test", destination, false)
assert.NoError(t, err)
@@ -49,7 +55,9 @@ func Test_CopyDir_shouldCopyOnlyDirContents(t *testing.T) {
}
func Test_CopyPath_shouldSkipWhenNotExist(t *testing.T) {
tmpdir := t.TempDir()
tmpdir, _ := ioutil.TempDir("", "backup")
defer os.RemoveAll(tmpdir)
err := CopyPath("does-not-exists", tmpdir)
assert.NoError(t, err)
@@ -57,7 +65,9 @@ func Test_CopyPath_shouldSkipWhenNotExist(t *testing.T) {
}
func Test_CopyPath_shouldCopyFile(t *testing.T) {
tmpdir := t.TempDir()
tmpdir, _ := ioutil.TempDir("", "backup")
defer os.RemoveAll(tmpdir)
content := []byte("content")
ioutil.WriteFile(path.Join(tmpdir, "file"), content, 0600)
@@ -71,7 +81,8 @@ func Test_CopyPath_shouldCopyFile(t *testing.T) {
}
func Test_CopyPath_shouldCopyDir(t *testing.T) {
destination := t.TempDir()
destination, _ := ioutil.TempDir("", "destination")
defer os.RemoveAll(destination)
err := CopyPath("./testdata/copy_test", destination)
assert.NoError(t, err)
-77
View File
@@ -234,58 +234,6 @@ func (service *Service) StoreStackFileFromBytes(stackIdentifier, fileName string
return service.wrapFileStore(stackStorePath), nil
}
// UpdateStoreStackFileFromBytes makes stack file backup and updates a new file from bytes.
// It returns the path to the folder where the file is stored.
func (service *Service) UpdateStoreStackFileFromBytes(stackIdentifier, fileName string, data []byte) (string, error) {
stackStorePath := JoinPaths(ComposeStorePath, stackIdentifier)
composeFilePath := JoinPaths(stackStorePath, fileName)
err := service.createBackupFileInStore(composeFilePath)
if err != nil {
return "", err
}
r := bytes.NewReader(data)
err = service.createFileInStore(composeFilePath, r)
if err != nil {
return "", err
}
return service.wrapFileStore(stackStorePath), nil
}
// RemoveStackFileBackup removes the stack file backup in the ComposeStorePath.
func (service *Service) RemoveStackFileBackup(stackIdentifier, fileName string) error {
stackStorePath := JoinPaths(ComposeStorePath, stackIdentifier)
composeFilePath := JoinPaths(stackStorePath, fileName)
return service.removeBackupFileInStore(composeFilePath)
}
// RollbackStackFile rollbacks the stack file backup in the ComposeStorePath.
func (service *Service) RollbackStackFile(stackIdentifier, fileName string) error {
stackStorePath := JoinPaths(ComposeStorePath, stackIdentifier)
composeFilePath := JoinPaths(stackStorePath, fileName)
path := service.wrapFileStore(composeFilePath)
backupPath := fmt.Sprintf("%s.bak", path)
exists, err := service.FileExists(backupPath)
if err != nil {
return err
}
if !exists {
// keep the updated/failed stack file
return nil
}
err = service.Copy(backupPath, path, true)
if err != nil {
return err
}
return os.Remove(backupPath)
}
// GetEdgeStackProjectPath returns the absolute path on the FS for a edge stack based
// on its identifier.
func (service *Service) GetEdgeStackProjectPath(edgeStackIdentifier string) string {
@@ -499,31 +447,6 @@ func (service *Service) createFileInStore(filePath string, r io.Reader) error {
return err
}
// createBackupFileInStore makes a copy in the file store.
func (service *Service) createBackupFileInStore(filePath string) error {
path := service.wrapFileStore(filePath)
backupPath := fmt.Sprintf("%s.bak", path)
return service.Copy(path, backupPath, true)
}
// removeBackupFileInStore removes the copy in the file store.
func (service *Service) removeBackupFileInStore(filePath string) error {
path := service.wrapFileStore(filePath)
backupPath := fmt.Sprintf("%s.bak", path)
exists, err := service.FileExists(backupPath)
if err != nil {
return err
}
if exists {
return os.Remove(backupPath)
}
return nil
}
func (service *Service) createPEMFileInStore(content []byte, fileType, filePath string) error {
path := service.wrapFileStore(filePath)
block := &pem.Block{Type: fileType, Bytes: content}
+1 -1
View File
@@ -43,7 +43,7 @@ func testHelperFileExists_fileExists(t *testing.T, checker func(path string) (bo
}
func testHelperFileExists_fileNotExists(t *testing.T, checker func(path string) (bool, error)) {
filePath := path.Join(t.TempDir(), fmt.Sprintf("%s%d", t.Name(), rand.Int()))
filePath := path.Join(os.TempDir(), fmt.Sprintf("%s%d", t.Name(), rand.Int()))
err := os.RemoveAll(filePath)
assert.NoError(t, err, "RemoveAll should not fail")
+1 -1
View File
@@ -9,7 +9,7 @@ import (
)
func createService(t *testing.T) *Service {
dataStorePath := path.Join(t.TempDir(), t.Name())
dataStorePath := path.Join(os.TempDir(), t.Name())
service, err := NewService(dataStorePath, "")
assert.NoError(t, err, "NewService should not fail")
+30 -225
View File
@@ -2,7 +2,6 @@ package git
import (
"context"
"crypto/tls"
"encoding/json"
"fmt"
"io"
@@ -11,10 +10,7 @@ import (
"net/url"
"os"
"strings"
"time"
"github.com/go-git/go-git/v5/plumbing/transport/client"
githttp "github.com/go-git/go-git/v5/plumbing/transport/http"
"github.com/pkg/errors"
"github.com/portainer/portainer/api/archive"
)
@@ -36,47 +32,20 @@ type azureOptions struct {
username, password string
}
// azureRef abstracts from the response of https://docs.microsoft.com/en-us/rest/api/azure/devops/git/refs/list?view=azure-devops-rest-6.0#refs
type azureRef struct {
Name string `json:"name"`
ObjectID string `json:"objectId"`
}
// azureItem abstracts from the response of https://docs.microsoft.com/en-us/rest/api/azure/devops/git/items/get?view=azure-devops-rest-6.0#download
type azureItem struct {
ObjectID string `json:"objectId"`
CommitId string `json:"commitId"`
Path string `json:"path"`
}
type azureClient struct {
type azureDownloader struct {
client *http.Client
baseUrl string
}
func NewAzureClient() *azureClient {
httpsCli := newHttpClientForAzure()
return &azureClient{
client: httpsCli,
func NewAzureDownloader(client *http.Client) *azureDownloader {
return &azureDownloader{
client: client,
baseUrl: "https://dev.azure.com",
}
}
func newHttpClientForAzure() *http.Client {
httpsCli := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
Proxy: http.ProxyFromEnvironment,
},
Timeout: 300 * time.Second,
}
client.InstallProtocol("https", githttp.NewClient(httpsCli))
return httpsCli
}
func (a *azureClient) download(ctx context.Context, destination string, opt cloneOption) error {
zipFilepath, err := a.downloadZipFromAzureDevOps(ctx, opt)
func (a *azureDownloader) download(ctx context.Context, destination string, options cloneOptions) error {
zipFilepath, err := a.downloadZipFromAzureDevOps(ctx, options)
if err != nil {
return errors.Wrap(err, "failed to download a zip file from Azure DevOps")
}
@@ -90,12 +59,12 @@ func (a *azureClient) download(ctx context.Context, destination string, opt clon
return nil
}
func (a *azureClient) downloadZipFromAzureDevOps(ctx context.Context, opt cloneOption) (string, error) {
config, err := parseUrl(opt.repositoryUrl)
func (a *azureDownloader) downloadZipFromAzureDevOps(ctx context.Context, options cloneOptions) (string, error) {
config, err := parseUrl(options.repositoryUrl)
if err != nil {
return "", errors.WithMessage(err, "failed to parse url")
}
downloadUrl, err := a.buildDownloadUrl(config, opt.referenceName)
downloadUrl, err := a.buildDownloadUrl(config, options.referenceName)
if err != nil {
return "", errors.WithMessage(err, "failed to build download url")
}
@@ -106,8 +75,8 @@ func (a *azureClient) downloadZipFromAzureDevOps(ctx context.Context, opt cloneO
defer zipFile.Close()
req, err := http.NewRequestWithContext(ctx, "GET", downloadUrl, nil)
if opt.username != "" || opt.password != "" {
req.SetBasicAuth(opt.username, opt.password)
if options.username != "" || options.password != "" {
req.SetBasicAuth(options.username, options.password)
} else if config.username != "" || config.password != "" {
req.SetBasicAuth(config.username, config.password)
}
@@ -133,58 +102,53 @@ func (a *azureClient) downloadZipFromAzureDevOps(ctx context.Context, opt cloneO
return zipFile.Name(), nil
}
func (a *azureClient) latestCommitID(ctx context.Context, opt fetchOption) (string, error) {
rootItem, err := a.getRootItem(ctx, opt)
func (a *azureDownloader) latestCommitID(ctx context.Context, options fetchOptions) (string, error) {
config, err := parseUrl(options.repositoryUrl)
if err != nil {
return "", err
}
return rootItem.CommitId, nil
}
func (a *azureClient) getRootItem(ctx context.Context, opt fetchOption) (*azureItem, error) {
config, err := parseUrl(opt.repositoryUrl)
if err != nil {
return nil, errors.WithMessage(err, "failed to parse url")
return "", errors.WithMessage(err, "failed to parse url")
}
rootItemUrl, err := a.buildRootItemUrl(config, opt.referenceName)
rootItemUrl, err := a.buildRootItemUrl(config, options.referenceName)
if err != nil {
return nil, errors.WithMessage(err, "failed to build azure root item url")
return "", errors.WithMessage(err, "failed to build azure root item url")
}
req, err := http.NewRequestWithContext(ctx, "GET", rootItemUrl, nil)
if opt.username != "" || opt.password != "" {
req.SetBasicAuth(opt.username, opt.password)
if options.username != "" || options.password != "" {
req.SetBasicAuth(options.username, options.password)
} else if config.username != "" || config.password != "" {
req.SetBasicAuth(config.username, config.password)
}
if err != nil {
return nil, errors.WithMessage(err, "failed to create a new HTTP request")
return "", errors.WithMessage(err, "failed to create a new HTTP request")
}
resp, err := a.client.Do(req)
if err != nil {
return nil, errors.WithMessage(err, "failed to make an HTTP request")
return "", errors.WithMessage(err, "failed to make an HTTP request")
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, checkAzureStatusCode(fmt.Errorf("failed to get repository root item with a status \"%v\"", resp.Status), resp.StatusCode)
return "", fmt.Errorf("failed to get repository root item with a status \"%v\"", resp.Status)
}
var items struct {
Value []azureItem
Value []struct {
CommitId string `json:"commitId"`
}
}
if err := json.NewDecoder(resp.Body).Decode(&items); err != nil {
return nil, errors.Wrap(err, "could not parse Azure items response")
return "", errors.Wrap(err, "could not parse Azure items response")
}
if len(items.Value) == 0 || items.Value[0].CommitId == "" {
return nil, errors.Errorf("failed to get latest commitID in the repository")
return "", errors.Errorf("failed to get latest commitID in the repository")
}
return &items.Value[0], nil
return items.Value[0].CommitId, nil
}
func parseUrl(rawUrl string) (*azureOptions, error) {
@@ -255,7 +219,7 @@ func parseHttpUrl(rawUrl string) (*azureOptions, error) {
return &opt, nil
}
func (a *azureClient) buildDownloadUrl(config *azureOptions, referenceName string) (string, error) {
func (a *azureDownloader) buildDownloadUrl(config *azureOptions, referenceName string) (string, error) {
rawUrl := fmt.Sprintf("%s/%s/%s/_apis/git/repositories/%s/items",
a.baseUrl,
url.PathEscape(config.organisation),
@@ -282,7 +246,7 @@ func (a *azureClient) buildDownloadUrl(config *azureOptions, referenceName strin
return u.String(), nil
}
func (a *azureClient) buildRootItemUrl(config *azureOptions, referenceName string) (string, error) {
func (a *azureDownloader) buildRootItemUrl(config *azureOptions, referenceName string) (string, error) {
rawUrl := fmt.Sprintf("%s/%s/%s/_apis/git/repositories/%s/items",
a.baseUrl,
url.PathEscape(config.organisation),
@@ -306,49 +270,6 @@ func (a *azureClient) buildRootItemUrl(config *azureOptions, referenceName strin
return u.String(), nil
}
func (a *azureClient) buildRefsUrl(config *azureOptions) (string, error) {
// ref@https://docs.microsoft.com/en-us/rest/api/azure/devops/git/refs/list?view=azure-devops-rest-6.0#gitref
rawUrl := fmt.Sprintf("%s/%s/%s/_apis/git/repositories/%s/refs",
a.baseUrl,
url.PathEscape(config.organisation),
url.PathEscape(config.project),
url.PathEscape(config.repository))
u, err := url.Parse(rawUrl)
if err != nil {
return "", errors.Wrapf(err, "failed to parse list refs url path %s", rawUrl)
}
q := u.Query()
q.Set("api-version", "6.0")
u.RawQuery = q.Encode()
return u.String(), nil
}
func (a *azureClient) buildTreeUrl(config *azureOptions, rootObjectHash string) (string, error) {
// ref@https://docs.microsoft.com/en-us/rest/api/azure/devops/git/trees/get?view=azure-devops-rest-6.0
rawUrl := fmt.Sprintf("%s/%s/%s/_apis/git/repositories/%s/trees/%s",
a.baseUrl,
url.PathEscape(config.organisation),
url.PathEscape(config.project),
url.PathEscape(config.repository),
url.PathEscape(rootObjectHash),
)
u, err := url.Parse(rawUrl)
if err != nil {
return "", errors.Wrapf(err, "failed to parse list tree url path %s", rawUrl)
}
q := u.Query()
// projectId={projectId}&recursive=true&fileName={fileName}&$format={$format}&api-version=6.0
q.Set("recursive", "true")
q.Set("api-version", "6.0")
u.RawQuery = q.Encode()
return u.String(), nil
}
const (
branchPrefix = "refs/heads/"
tagPrefix = "refs/tags/"
@@ -373,119 +294,3 @@ func getVersionType(name string) string {
}
return "commit"
}
func (a *azureClient) listRefs(ctx context.Context, opt baseOption) ([]string, error) {
config, err := parseUrl(opt.repositoryUrl)
if err != nil {
return nil, errors.WithMessage(err, "failed to parse url")
}
listRefsUrl, err := a.buildRefsUrl(config)
if err != nil {
return nil, errors.WithMessage(err, "failed to build list refs url")
}
req, err := http.NewRequestWithContext(ctx, "GET", listRefsUrl, nil)
if opt.username != "" || opt.password != "" {
req.SetBasicAuth(opt.username, opt.password)
} else if config.username != "" || config.password != "" {
req.SetBasicAuth(config.username, config.password)
}
if err != nil {
return nil, errors.WithMessage(err, "failed to create a new HTTP request")
}
resp, err := a.client.Do(req)
if err != nil {
return nil, errors.WithMessage(err, "failed to make an HTTP request")
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, checkAzureStatusCode(fmt.Errorf("failed to list refs with a status \"%v\"", resp.Status), resp.StatusCode)
}
var refs struct {
Value []azureRef
}
if err := json.NewDecoder(resp.Body).Decode(&refs); err != nil {
return nil, errors.Wrap(err, "could not parse Azure refs response")
}
var ret []string
for _, value := range refs.Value {
if value.Name == "HEAD" {
continue
}
ret = append(ret, value.Name)
}
return ret, nil
}
// listFiles list all filenames under the specific repository
func (a *azureClient) listFiles(ctx context.Context, opt fetchOption) ([]string, error) {
rootItem, err := a.getRootItem(ctx, opt)
if err != nil {
return nil, err
}
config, err := parseUrl(opt.repositoryUrl)
if err != nil {
return nil, errors.WithMessage(err, "failed to parse url")
}
listTreeUrl, err := a.buildTreeUrl(config, rootItem.ObjectID)
if err != nil {
return nil, errors.WithMessage(err, "failed to build list tree url")
}
req, err := http.NewRequestWithContext(ctx, "GET", listTreeUrl, nil)
if opt.username != "" || opt.password != "" {
req.SetBasicAuth(opt.username, opt.password)
} else if config.username != "" || config.password != "" {
req.SetBasicAuth(config.username, config.password)
}
if err != nil {
return nil, errors.WithMessage(err, "failed to create a new HTTP request")
}
resp, err := a.client.Do(req)
if err != nil {
return nil, errors.WithMessage(err, "failed to make an HTTP request")
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to list tree url with a status \"%v\"", resp.Status)
}
var tree struct {
TreeEntries []struct {
RelativePath string `json:"relativePath"`
} `json:"treeEntries"`
}
if err := json.NewDecoder(resp.Body).Decode(&tree); err != nil {
return nil, errors.Wrap(err, "could not parse Azure tree response")
}
var allPaths []string
for _, treeEntry := range tree.TreeEntries {
allPaths = append(allPaths, treeEntry.RelativePath)
}
return allPaths, nil
}
func checkAzureStatusCode(err error, code int) error {
if code == http.StatusNotFound {
return ErrIncorrectRepositoryURL
} else if code == http.StatusUnauthorized || code == http.StatusNonAuthoritativeInfo {
return ErrAuthenticationFailure
}
return err
}
+18 -204
View File
@@ -1,26 +1,21 @@
package git
import (
"context"
"fmt"
"os"
"path/filepath"
"testing"
"time"
"github.com/docker/docker/pkg/ioutils"
_ "github.com/joho/godotenv/autoload"
"github.com/stretchr/testify/assert"
)
var (
privateAzureRepoURL = "https://portainer.visualstudio.com/gitops-test/_git/gitops-test"
)
func TestService_ClonePublicRepository_Azure(t *testing.T) {
ensureIntegrationTest(t)
pat := getRequiredValue(t, "AZURE_DEVOPS_PAT")
service := NewService(context.TODO())
service := NewService()
type args struct {
repositoryURLFormat string
@@ -36,7 +31,7 @@ func TestService_ClonePublicRepository_Azure(t *testing.T) {
{
name: "Clone Azure DevOps repo branch",
args: args{
repositoryURLFormat: "https://:%s@portainer.visualstudio.com/gitops-test/_git/gitops-test",
repositoryURLFormat: "https://:%s@portainer.visualstudio.com/Playground/_git/dev_integration",
referenceName: "refs/heads/main",
username: "",
password: pat,
@@ -46,8 +41,8 @@ func TestService_ClonePublicRepository_Azure(t *testing.T) {
{
name: "Clone Azure DevOps repo tag",
args: args{
repositoryURLFormat: "https://:%s@portainer.visualstudio.com/gitops-test/_git/gitops-test",
referenceName: "refs/heads/tags/v1.1",
repositoryURLFormat: "https://:%s@portainer.visualstudio.com/Playground/_git/dev_integration",
referenceName: "refs/tags/v1.1",
username: "",
password: pat,
},
@@ -56,9 +51,11 @@ func TestService_ClonePublicRepository_Azure(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
dst := t.TempDir()
dst, err := ioutils.TempDir("", "clone")
assert.NoError(t, err)
defer os.RemoveAll(dst)
repositoryUrl := fmt.Sprintf(tt.args.repositoryURLFormat, tt.args.password)
err := service.CloneRepository(dst, repositoryUrl, tt.args.referenceName, "", "")
err = service.CloneRepository(dst, repositoryUrl, tt.args.referenceName, "", "")
assert.NoError(t, err)
assert.FileExists(t, filepath.Join(dst, "README.md"))
})
@@ -69,11 +66,14 @@ func TestService_ClonePrivateRepository_Azure(t *testing.T) {
ensureIntegrationTest(t)
pat := getRequiredValue(t, "AZURE_DEVOPS_PAT")
service := NewService(context.TODO())
service := NewService()
dst := t.TempDir()
dst, err := ioutils.TempDir("", "clone")
assert.NoError(t, err)
defer os.RemoveAll(dst)
err := service.CloneRepository(dst, privateAzureRepoURL, "refs/heads/main", "", pat)
repositoryUrl := "https://portainer.visualstudio.com/Playground/_git/dev_integration"
err = service.CloneRepository(dst, repositoryUrl, "refs/heads/main", "", pat)
assert.NoError(t, err)
assert.FileExists(t, filepath.Join(dst, "README.md"))
}
@@ -82,200 +82,14 @@ func TestService_LatestCommitID_Azure(t *testing.T) {
ensureIntegrationTest(t)
pat := getRequiredValue(t, "AZURE_DEVOPS_PAT")
service := NewService(context.TODO())
service := NewService()
id, err := service.LatestCommitID(privateAzureRepoURL, "refs/heads/main", "", pat)
repositoryUrl := "https://portainer.visualstudio.com/Playground/_git/dev_integration"
id, err := service.LatestCommitID(repositoryUrl, "refs/heads/main", "", pat)
assert.NoError(t, err)
assert.NotEmpty(t, id, "cannot guarantee commit id, but it should be not empty")
}
func TestService_ListRefs_Azure(t *testing.T) {
ensureIntegrationTest(t)
accessToken := getRequiredValue(t, "AZURE_DEVOPS_PAT")
username := getRequiredValue(t, "AZURE_DEVOPS_USERNAME")
service := NewService(context.TODO())
refs, err := service.ListRefs(privateAzureRepoURL, username, accessToken, false)
assert.NoError(t, err)
assert.GreaterOrEqual(t, len(refs), 1)
}
func TestService_ListRefs_Azure_Concurrently(t *testing.T) {
ensureIntegrationTest(t)
accessToken := getRequiredValue(t, "AZURE_DEVOPS_PAT")
username := getRequiredValue(t, "AZURE_DEVOPS_USERNAME")
service := newService(context.TODO(), REPOSITORY_CACHE_SIZE, 200*time.Millisecond)
go service.ListRefs(privateAzureRepoURL, username, accessToken, false)
service.ListRefs(privateAzureRepoURL, username, accessToken, false)
time.Sleep(2 * time.Second)
}
func TestService_ListFiles_Azure(t *testing.T) {
ensureIntegrationTest(t)
type expectResult struct {
shouldFail bool
err error
matchedCount int
}
service := newService(context.TODO(), 0, 0)
accessToken := getRequiredValue(t, "AZURE_DEVOPS_PAT")
username := getRequiredValue(t, "AZURE_DEVOPS_USERNAME")
tests := []struct {
name string
args fetchOption
extensions []string
expect expectResult
}{
{
name: "list tree with real repository and head ref but incorrect credential",
args: fetchOption{
baseOption: baseOption{
repositoryUrl: privateAzureRepoURL,
username: "test-username",
password: "test-token",
},
referenceName: "refs/heads/main",
},
extensions: []string{},
expect: expectResult{
shouldFail: true,
err: ErrAuthenticationFailure,
},
},
{
name: "list tree with real repository and head ref but no credential",
args: fetchOption{
baseOption: baseOption{
repositoryUrl: privateAzureRepoURL,
username: "",
password: "",
},
referenceName: "refs/heads/main",
},
extensions: []string{},
expect: expectResult{
shouldFail: true,
err: ErrAuthenticationFailure,
},
},
{
name: "list tree with real repository and head ref",
args: fetchOption{
baseOption: baseOption{
repositoryUrl: privateAzureRepoURL,
username: username,
password: accessToken,
},
referenceName: "refs/heads/main",
},
extensions: []string{},
expect: expectResult{
err: nil,
matchedCount: 19,
},
},
{
name: "list tree with real repository and head ref and existing file extension",
args: fetchOption{
baseOption: baseOption{
repositoryUrl: privateAzureRepoURL,
username: username,
password: accessToken,
},
referenceName: "refs/heads/main",
},
extensions: []string{"yml"},
expect: expectResult{
err: nil,
matchedCount: 2,
},
},
{
name: "list tree with real repository and head ref and non-existing file extension",
args: fetchOption{
baseOption: baseOption{
repositoryUrl: privateAzureRepoURL,
username: username,
password: accessToken,
},
referenceName: "refs/heads/main",
},
extensions: []string{"hcl"},
expect: expectResult{
err: nil,
matchedCount: 2,
},
},
{
name: "list tree with real repository but non-existing ref",
args: fetchOption{
baseOption: baseOption{
repositoryUrl: privateAzureRepoURL,
username: username,
password: accessToken,
},
referenceName: "refs/fake/feature",
},
extensions: []string{},
expect: expectResult{
shouldFail: true,
},
},
{
name: "list tree with fake repository ",
args: fetchOption{
baseOption: baseOption{
repositoryUrl: privateAzureRepoURL + "fake",
username: username,
password: accessToken,
},
referenceName: "refs/fake/feature",
},
extensions: []string{},
expect: expectResult{
shouldFail: true,
err: ErrIncorrectRepositoryURL,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
paths, err := service.ListFiles(tt.args.repositoryUrl, tt.args.referenceName, tt.args.username, tt.args.password, false, tt.extensions)
if tt.expect.shouldFail {
assert.Error(t, err)
if tt.expect.err != nil {
assert.Equal(t, tt.expect.err, err)
}
} else {
assert.NoError(t, err)
if tt.expect.matchedCount > 0 {
assert.Greater(t, len(paths), 0)
}
}
})
}
}
func TestService_ListFiles_Azure_Concurrently(t *testing.T) {
ensureIntegrationTest(t)
accessToken := getRequiredValue(t, "AZURE_DEVOPS_PAT")
username := getRequiredValue(t, "AZURE_DEVOPS_USERNAME")
service := newService(context.TODO(), REPOSITORY_CACHE_SIZE, 200*time.Millisecond)
go service.ListFiles(privateAzureRepoURL, "refs/heads/main", username, accessToken, false, []string{})
service.ListFiles(privateAzureRepoURL, "refs/heads/main", username, accessToken, false, []string{})
time.Sleep(2 * time.Second)
}
func getRequiredValue(t *testing.T, name string) string {
value, ok := os.LookupEnv(name)
if !ok {
+12 -314
View File
@@ -11,7 +11,7 @@ import (
)
func Test_buildDownloadUrl(t *testing.T) {
a := NewAzureClient()
a := NewAzureDownloader(nil)
u, err := a.buildDownloadUrl(&azureOptions{
organisation: "organisation",
project: "project",
@@ -29,7 +29,7 @@ func Test_buildDownloadUrl(t *testing.T) {
}
func Test_buildRootItemUrl(t *testing.T) {
a := NewAzureClient()
a := NewAzureDownloader(nil)
u, err := a.buildRootItemUrl(&azureOptions{
organisation: "organisation",
project: "project",
@@ -45,40 +45,6 @@ func Test_buildRootItemUrl(t *testing.T) {
assert.Equal(t, expectedUrl.Query(), actualUrl.Query())
}
func Test_buildRefsUrl(t *testing.T) {
a := NewAzureClient()
u, err := a.buildRefsUrl(&azureOptions{
organisation: "organisation",
project: "project",
repository: "repository",
})
expectedUrl, _ := url.Parse("https://dev.azure.com/organisation/project/_apis/git/repositories/repository/refs?api-version=6.0")
actualUrl, _ := url.Parse(u)
assert.NoError(t, err)
assert.Equal(t, expectedUrl.Host, actualUrl.Host)
assert.Equal(t, expectedUrl.Scheme, actualUrl.Scheme)
assert.Equal(t, expectedUrl.Path, actualUrl.Path)
assert.Equal(t, expectedUrl.Query(), actualUrl.Query())
}
func Test_buildTreeUrl(t *testing.T) {
a := NewAzureClient()
u, err := a.buildTreeUrl(&azureOptions{
organisation: "organisation",
project: "project",
repository: "repository",
}, "sha1")
expectedUrl, _ := url.Parse("https://dev.azure.com/organisation/project/_apis/git/repositories/repository/trees/sha1?api-version=6.0&recursive=true")
actualUrl, _ := url.Parse(u)
assert.NoError(t, err)
assert.Equal(t, expectedUrl.Host, actualUrl.Host)
assert.Equal(t, expectedUrl.Scheme, actualUrl.Scheme)
assert.Equal(t, expectedUrl.Path, actualUrl.Path)
assert.Equal(t, expectedUrl.Query(), actualUrl.Query())
}
func Test_parseAzureUrl(t *testing.T) {
type args struct {
url string
@@ -234,7 +200,7 @@ func Test_isAzureUrl(t *testing.T) {
func Test_azureDownloader_downloadZipFromAzureDevOps(t *testing.T) {
type args struct {
options baseOption
options cloneOptions
}
type basicAuth struct {
username, password string
@@ -247,7 +213,7 @@ func Test_azureDownloader_downloadZipFromAzureDevOps(t *testing.T) {
{
name: "username, password embedded",
args: args{
options: baseOption{
options: cloneOptions{
repositoryUrl: "https://username:password@dev.azure.com/Organisation/Project/_git/Repository",
},
},
@@ -259,7 +225,7 @@ func Test_azureDownloader_downloadZipFromAzureDevOps(t *testing.T) {
{
name: "username, password embedded, clone options take precedence",
args: args{
options: baseOption{
options: cloneOptions{
repositoryUrl: "https://username:password@dev.azure.com/Organisation/Project/_git/Repository",
username: "u",
password: "p",
@@ -273,7 +239,7 @@ func Test_azureDownloader_downloadZipFromAzureDevOps(t *testing.T) {
{
name: "no credentials",
args: args{
options: baseOption{
options: cloneOptions{
repositoryUrl: "https://dev.azure.com/Organisation/Project/_git/Repository",
},
},
@@ -290,17 +256,11 @@ func Test_azureDownloader_downloadZipFromAzureDevOps(t *testing.T) {
}))
defer server.Close()
a := &azureClient{
a := &azureDownloader{
client: server.Client(),
baseUrl: server.URL,
}
option := cloneOption{
fetchOption: fetchOption{
baseOption: tt.args.options,
},
}
_, err := a.downloadZipFromAzureDevOps(context.Background(), option)
_, err := a.downloadZipFromAzureDevOps(context.Background(), tt.args.options)
assert.Error(t, err)
assert.Equal(t, tt.want, zipRequestAuth)
})
@@ -327,25 +287,22 @@ func Test_azureDownloader_latestCommitID(t *testing.T) {
}))
defer server.Close()
a := &azureClient{
a := &azureDownloader{
client: server.Client(),
baseUrl: server.URL,
}
tests := []struct {
name string
args fetchOption
args fetchOptions
want string
wantErr bool
}{
{
name: "should be able to parse response",
args: fetchOption{
baseOption: baseOption{
repositoryUrl: "https://dev.azure.com/Organisation/Project/_git/Repository",
},
args: fetchOptions{
referenceName: "",
},
repositoryUrl: "https://dev.azure.com/Organisation/Project/_git/Repository"},
want: "27104ad7549d9e66685e115a497533f18024be9c",
wantErr: false,
},
@@ -362,262 +319,3 @@ func Test_azureDownloader_latestCommitID(t *testing.T) {
})
}
}
type testRepoManager struct {
called bool
}
func (t *testRepoManager) download(_ context.Context, _ string, _ cloneOption) error {
t.called = true
return nil
}
func (t *testRepoManager) latestCommitID(_ context.Context, _ fetchOption) (string, error) {
return "", nil
}
func (t *testRepoManager) listRefs(_ context.Context, _ baseOption) ([]string, error) {
return nil, nil
}
func (t *testRepoManager) listFiles(_ context.Context, _ fetchOption) ([]string, error) {
return nil, nil
}
func Test_cloneRepository_azure(t *testing.T) {
tests := []struct {
name string
url string
called bool
}{
{
name: "Azure HTTP URL",
url: "https://Organisation@dev.azure.com/Organisation/Project/_git/Repository",
called: true,
},
{
name: "Azure SSH URL",
url: "git@ssh.dev.azure.com:v3/Organisation/Project/Repository",
called: true,
},
{
name: "Something else",
url: "https://example.com",
called: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
azure := &testRepoManager{}
git := &testRepoManager{}
s := &Service{azure: azure, git: git}
s.cloneRepository("", cloneOption{
fetchOption: fetchOption{
baseOption: baseOption{
repositoryUrl: tt.url,
},
},
depth: 1,
})
// if azure API is called, git isn't and vice versa
assert.Equal(t, tt.called, azure.called)
assert.Equal(t, tt.called, !git.called)
})
}
}
func Test_listRefs_azure(t *testing.T) {
ensureIntegrationTest(t)
client := NewAzureClient()
type expectResult struct {
err error
refsCount int
}
accessToken := getRequiredValue(t, "AZURE_DEVOPS_PAT")
username := getRequiredValue(t, "AZURE_DEVOPS_USERNAME")
tests := []struct {
name string
args baseOption
expect expectResult
}{
{
name: "list refs of a real repository",
args: baseOption{
repositoryUrl: privateAzureRepoURL,
username: username,
password: accessToken,
},
expect: expectResult{
err: nil,
refsCount: 2,
},
},
{
name: "list refs of a real repository with incorrect credential",
args: baseOption{
repositoryUrl: privateAzureRepoURL,
username: "test-username",
password: "test-token",
},
expect: expectResult{
err: ErrAuthenticationFailure,
},
},
{
name: "list refs of a real repository without providing credential",
args: baseOption{
repositoryUrl: privateAzureRepoURL,
username: "",
password: "",
},
expect: expectResult{
err: ErrAuthenticationFailure,
},
},
{
name: "list refs of a fake repository",
args: baseOption{
repositoryUrl: privateAzureRepoURL + "fake",
username: username,
password: accessToken,
},
expect: expectResult{
err: ErrIncorrectRepositoryURL,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
refs, err := client.listRefs(context.TODO(), tt.args)
if tt.expect.err == nil {
assert.NoError(t, err)
if tt.expect.refsCount > 0 {
assert.Greater(t, len(refs), 0)
}
} else {
assert.Error(t, err)
assert.Equal(t, tt.expect.err, err)
}
})
}
}
func Test_listFiles_azure(t *testing.T) {
ensureIntegrationTest(t)
client := NewAzureClient()
type expectResult struct {
shouldFail bool
err error
matchedCount int
}
accessToken := getRequiredValue(t, "AZURE_DEVOPS_PAT")
username := getRequiredValue(t, "AZURE_DEVOPS_USERNAME")
tests := []struct {
name string
args fetchOption
expect expectResult
}{
{
name: "list tree with real repository and head ref but incorrect credential",
args: fetchOption{
baseOption: baseOption{
repositoryUrl: privateAzureRepoURL,
username: "test-username",
password: "test-token",
},
referenceName: "refs/heads/main",
},
expect: expectResult{
shouldFail: true,
err: ErrAuthenticationFailure,
},
},
{
name: "list tree with real repository and head ref but no credential",
args: fetchOption{
baseOption: baseOption{
repositoryUrl: privateAzureRepoURL,
username: "",
password: "",
},
referenceName: "refs/heads/main",
},
expect: expectResult{
shouldFail: true,
err: ErrAuthenticationFailure,
},
},
{
name: "list tree with real repository and head ref",
args: fetchOption{
baseOption: baseOption{
repositoryUrl: privateAzureRepoURL,
username: username,
password: accessToken,
},
referenceName: "refs/heads/main",
},
expect: expectResult{
err: nil,
matchedCount: 19,
},
},
{
name: "list tree with real repository but non-existing ref",
args: fetchOption{
baseOption: baseOption{
repositoryUrl: privateAzureRepoURL,
username: username,
password: accessToken,
},
referenceName: "refs/fake/feature",
},
expect: expectResult{
shouldFail: true,
},
},
{
name: "list tree with fake repository ",
args: fetchOption{
baseOption: baseOption{
repositoryUrl: privateAzureRepoURL + "fake",
username: username,
password: accessToken,
},
referenceName: "refs/fake/feature",
},
expect: expectResult{
shouldFail: true,
err: ErrIncorrectRepositoryURL,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
paths, err := client.listFiles(context.TODO(), tt.args)
if tt.expect.shouldFail {
assert.Error(t, err)
if tt.expect.err != nil {
assert.Equal(t, tt.expect.err, err)
}
} else {
assert.NoError(t, err)
if tt.expect.matchedCount > 0 {
assert.Greater(t, len(paths), 0)
}
}
})
}
}

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