diff --git a/.prettierignore b/.prettierignore index 53c37a166..23a3eacf5 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1 +1,2 @@ -dist \ No newline at end of file +dist +api/datastore/test_data \ No newline at end of file diff --git a/api/api-description.md b/api/api-description.md index 5218e2a65..5ebdeb7ed 100644 --- a/api/api-description.md +++ b/api/api-description.md @@ -53,11 +53,12 @@ To do so, you can use the `/endpoints/{id}/docker` Portainer API environment(end # Private Registry Using private registry, you will need to pass a based64 encoded JSON string ‘{"registryId":\}’ inside the Request Header. The parameter name is "X-Registry-Auth". -\ - The registry ID where the repository was created. +\ - The registry ID where the repository was created. Example: ``` eyJyZWdpc3RyeUlkIjoxfQ== ``` + **NOTE**: You can find more information on how to query the Docker API in the [Docker official documentation](https://docs.docker.com/engine/api/v1.30/) as well as in [this Portainer example](https://documentation.portainer.io/api/api-examples/). diff --git a/api/apikey/service_test.go b/api/apikey/service_test.go index 8bdab171a..3f184efb4 100644 --- a/api/apikey/service_test.go +++ b/api/apikey/service_test.go @@ -20,7 +20,7 @@ func Test_SatisfiesAPIKeyServiceInterface(t *testing.T) { func Test_GenerateApiKey(t *testing.T) { is := assert.New(t) - _, store, teardown := datastore.MustNewTestStore(true) + _, store, teardown := datastore.MustNewTestStore(true, true) defer teardown() service := NewAPIKeyService(store.APIKeyRepository(), store.User()) @@ -74,7 +74,7 @@ func Test_GenerateApiKey(t *testing.T) { func Test_GetAPIKey(t *testing.T) { is := assert.New(t) - _, store, teardown := datastore.MustNewTestStore(true) + _, store, teardown := datastore.MustNewTestStore(true, true) defer teardown() service := NewAPIKeyService(store.APIKeyRepository(), store.User()) @@ -94,7 +94,7 @@ func Test_GetAPIKey(t *testing.T) { func Test_GetAPIKeys(t *testing.T) { is := assert.New(t) - _, store, teardown := datastore.MustNewTestStore(true) + _, store, teardown := datastore.MustNewTestStore(true, true) defer teardown() service := NewAPIKeyService(store.APIKeyRepository(), store.User()) @@ -115,7 +115,7 @@ func Test_GetAPIKeys(t *testing.T) { func Test_GetDigestUserAndKey(t *testing.T) { is := assert.New(t) - _, store, teardown := datastore.MustNewTestStore(true) + _, store, teardown := datastore.MustNewTestStore(true, true) defer teardown() service := NewAPIKeyService(store.APIKeyRepository(), store.User()) @@ -151,7 +151,7 @@ func Test_GetDigestUserAndKey(t *testing.T) { func Test_UpdateAPIKey(t *testing.T) { is := assert.New(t) - _, store, teardown := datastore.MustNewTestStore(true) + _, store, teardown := datastore.MustNewTestStore(true, true) defer teardown() service := NewAPIKeyService(store.APIKeyRepository(), store.User()) @@ -199,7 +199,7 @@ func Test_UpdateAPIKey(t *testing.T) { func Test_DeleteAPIKey(t *testing.T) { is := assert.New(t) - _, store, teardown := datastore.MustNewTestStore(true) + _, store, teardown := datastore.MustNewTestStore(true, true) defer teardown() service := NewAPIKeyService(store.APIKeyRepository(), store.User()) @@ -240,7 +240,7 @@ func Test_DeleteAPIKey(t *testing.T) { func Test_InvalidateUserKeyCache(t *testing.T) { is := assert.New(t) - _, store, teardown := datastore.MustNewTestStore(true) + _, store, teardown := datastore.MustNewTestStore(true, true) defer teardown() service := NewAPIKeyService(store.APIKeyRepository(), store.User()) diff --git a/api/cmd/portainer/main_test.go b/api/cmd/portainer/main_test.go index e80c05ce2..8e736c238 100644 --- a/api/cmd/portainer/main_test.go +++ b/api/cmd/portainer/main_test.go @@ -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(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(true) + _, store, teardown := datastore.MustNewTestStore(true, true) defer teardown() // Enable the test feature diff --git a/api/database/boltdb/db.go b/api/database/boltdb/db.go index 2741492a3..09054b830 100644 --- a/api/database/boltdb/db.go +++ b/api/database/boltdb/db.go @@ -161,7 +161,7 @@ func (connection *DbConnection) ExportRaw(filename string) error { return fmt.Errorf("stat on %s failed: %s", databasePath, err) } - b, err := connection.exportJson(databasePath) + b, err := connection.ExportJson(databasePath, true) if err != nil { return err } diff --git a/api/database/boltdb/export.go b/api/database/boltdb/export.go index 4d48442f8..cd2b7487c 100644 --- a/api/database/boltdb/export.go +++ b/api/database/boltdb/export.go @@ -8,9 +8,30 @@ import ( bolt "go.etcd.io/bbolt" ) +func backupMetadata(connection *bolt.DB) (map[string]interface{}, error) { + buckets := map[string]interface{}{} + + err := connection.View(func(tx *bolt.Tx) error { + err := tx.ForEach(func(name []byte, bucket *bolt.Bucket) error { + bucketName := string(name) + bucket = tx.Bucket([]byte(bucketName)) + seqId := bucket.Sequence() + buckets[bucketName] = int(seqId) + return nil + }) + + return err + }) + + return buckets, err +} + +// 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 // inspired by github.com/konoui/boltdb-exporter (which has no license) // but very much simplified, based on how we use boltdb -func (c *DbConnection) exportJson(databasePath string) ([]byte, error) { +func (c *DbConnection) ExportJson(databasePath string, metadata bool) ([]byte, error) { logrus.WithField("databasePath", databasePath).Infof("exportJson") connection, err := bolt.Open(databasePath, 0600, &bolt.Options{Timeout: 1 * time.Second, ReadOnly: true}) @@ -20,6 +41,13 @@ func (c *DbConnection) exportJson(databasePath string) ([]byte, error) { defer connection.Close() backup := make(map[string]interface{}) + if metadata { + meta, err := backupMetadata(connection) + if err != nil { + logrus.WithError(err).Errorf("Failed exporting metadata: %v", err) + } + backup["__metadata"] = meta + } err = connection.View(func(tx *bolt.Tx) error { err = tx.ForEach(func(name []byte, bucket *bolt.Bucket) error { @@ -45,15 +73,20 @@ func (c *DbConnection) exportJson(databasePath string) ([]byte, error) { } if bucketName == "version" { backup[bucketName] = version + return nil } if len(list) > 0 { if bucketName == "ssl" || bucketName == "settings" || bucketName == "tunnel_server" { - backup[bucketName] = list[0] + backup[bucketName] = nil + if len(list) > 0 { + backup[bucketName] = list[0] + } return nil } backup[bucketName] = list + return nil } return nil diff --git a/api/dataservices/stack/tests/stack_test.go b/api/dataservices/stack/tests/stack_test.go index 0f2e9a5dc..d782c25d6 100644 --- a/api/dataservices/stack/tests/stack_test.go +++ b/api/dataservices/stack/tests/stack_test.go @@ -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(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(true) + _, store, teardown := datastore.MustNewTestStore(true, true) defer teardown() staticStack := portainer.Stack{ID: 1} diff --git a/api/dataservices/team/tests/team_test.go b/api/dataservices/team/tests/team_test.go index 45755ad4a..d8d985119 100644 --- a/api/dataservices/team/tests/team_test.go +++ b/api/dataservices/team/tests/team_test.go @@ -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(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(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(true) + _, store, teardown := datastore.MustNewTestStore(true, true) defer teardown() teamBuilder := teamBuilder{ diff --git a/api/datastore/backup.go b/api/datastore/backup.go index f5f972c63..1ecf8f05e 100644 --- a/api/datastore/backup.go +++ b/api/datastore/backup.go @@ -69,6 +69,11 @@ func getBackupRestoreOptions(backupDir string) *BackupOptions { } } +// Backup current database with default options +func (store *Store) Backup() (string, error) { + return store.backupWithOptions(nil) +} + func (store *Store) setupOptions(options *BackupOptions) *BackupOptions { if options == nil { options = &BackupOptions{} diff --git a/api/datastore/backup_test.go b/api/datastore/backup_test.go index 19ee34951..99acc5c0a 100644 --- a/api/datastore/backup_test.go +++ b/api/datastore/backup_test.go @@ -10,7 +10,7 @@ import ( ) func TestCreateBackupFolders(t *testing.T) { - _, store, teardown := MustNewTestStore(false) + _, 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(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(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(true) + _, store, teardown := MustNewTestStore(true, true) defer teardown() t.Run("successfully removes file if existent", func(t *testing.T) { diff --git a/api/datastore/datastore_test.go b/api/datastore/datastore_test.go index 3a504965c..e83636fe0 100644 --- a/api/datastore/datastore_test.go +++ b/api/datastore/datastore_test.go @@ -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(true) + _, store, teardown := MustNewTestStore(true, true) defer teardown() testCases := map[string]func(t *testing.T){ diff --git a/api/datastore/log/log.go b/api/datastore/log/log.go index 5ae90946a..5deb9a12f 100644 --- a/api/datastore/log/log.go +++ b/api/datastore/log/log.go @@ -28,10 +28,20 @@ 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)) } diff --git a/api/datastore/migrate_data.go b/api/datastore/migrate_data.go index a453d96d5..9d9bd97a1 100644 --- a/api/datastore/migrate_data.go +++ b/api/datastore/migrate_data.go @@ -9,6 +9,7 @@ import ( 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" portainer "github.com/portainer/portainer/api" @@ -24,6 +25,12 @@ func (store *Store) MigrateData() error { return err } + // Backup Database + backupPath, err := store.Backup() + if err != nil { + return werrors.Wrap(err, "while backing up db before migration") + } + migratorParams := &migrator.MigratorParameters{ DatabaseVersion: version, EndpointGroupService: store.EndpointGroupService, @@ -46,7 +53,27 @@ func (store *Store) MigrateData() error { AuthorizationService: authorization.NewService(store), } - return store.connectionMigrateData(migratorParams) + // restore on error + err = store.connectionMigrateData(migratorParams) + if err != nil { + logrus.Errorf("While DB migration %v. Restoring DB", err) + // Restore options + options := BackupOptions{ + BackupPath: backupPath, + } + err := store.restoreWithOptions(&options) + if err != nil { + 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, + ) + } + } + + return err } // FailSafeMigrate backup and restore DB if migration fail diff --git a/api/datastore/migrate_data_test.go b/api/datastore/migrate_data_test.go index b48d46474..d404c0443 100644 --- a/api/datastore/migrate_data_test.go +++ b/api/datastore/migrate_data_test.go @@ -1,13 +1,19 @@ package datastore import ( + "bytes" + "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" ) // testVersion is a helper which tests current store version against wanted version @@ -22,8 +28,32 @@ func testVersion(store *Store, versionWant int, t *testing.T) { } func TestMigrateData(t *testing.T) { + snapshotTests := []struct { + testName string + srcPath string + wantPath string + }{ + { + testName: "migrate version 24 to 35", + srcPath: "test_data/input_24.json", + wantPath: "test_data/output_35.json", + }, + } + for _, test := range snapshotTests { + t.Run(test.testName, func(t *testing.T) { + err := migrateDBTestHelper(t, test.srcPath, test.wantPath) + if err != nil { + t.Errorf( + "Failed migrating mock database %v: %v", + test.srcPath, + err, + ) + } + }) + } + t.Run("MigrateData for New Store & Re-Open Check", func(t *testing.T) { - newStore, store, teardown := MustNewTestStore(false) + newStore, store, teardown := MustNewTestStore(false, true) defer teardown() if !newStore { @@ -50,7 +80,7 @@ func TestMigrateData(t *testing.T) { {version: 21, expectedVersion: portainer.DBVersion}, } for _, tc := range tests { - _, store, teardown := MustNewTestStore(true) + _, store, teardown := MustNewTestStore(true, true) defer teardown() // Setup data @@ -75,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(false) + _, store, teardown := MustNewTestStore(false, true) defer teardown() version := 17 @@ -87,7 +117,7 @@ func TestMigrateData(t *testing.T) { }) t.Run("MigrateData should create backup file upon update", func(t *testing.T) { - _, store, teardown := MustNewTestStore(false) + _, store, teardown := MustNewTestStore(false, true) defer teardown() store.VersionService.StoreDBVersion(0) @@ -101,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(false) + _, store, teardown := MustNewTestStore(false, true) defer teardown() store.VersionService.StoreIsUpdating(true) @@ -116,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(false) + _, store, teardown := MustNewTestStore(false, true) defer teardown() store.MigrateData() @@ -131,7 +161,7 @@ func TestMigrateData(t *testing.T) { } func Test_getBackupRestoreOptions(t *testing.T) { - _, store, teardown := MustNewTestStore(false) + _, store, teardown := MustNewTestStore(false, true) defer teardown() options := getBackupRestoreOptions(store.commonBackupDir()) @@ -150,7 +180,7 @@ func Test_getBackupRestoreOptions(t *testing.T) { func TestRollback(t *testing.T) { t.Run("Rollback should restore upgrade after backup", func(t *testing.T) { version := 21 - _, store, teardown := MustNewTestStore(false) + _, store, teardown := MustNewTestStore(false, true) defer teardown() store.VersionService.StoreDBVersion(version) @@ -185,3 +215,250 @@ func isFileExist(path string) bool { } return len(matches) > 0 } + +// migrateDBTestHelper loads a json representation of a bolt database from srcPath, +// parses it into a database, runs a migration on that database, and then +// compares it with an expected output database. +func migrateDBTestHelper(t *testing.T, srcPath, wantPath string) error { + srcJSON, err := os.ReadFile(srcPath) + if err != nil { + t.Fatalf("failed loading source JSON file %v: %v", srcPath, err) + } + + // Parse source json to db. + _, store, teardown := MustNewTestStore(true, false) + defer teardown() + err = importJSON(t, bytes.NewReader(srcJSON), store) + if err != nil { + return err + } + + // Run the actual migrations on our input database. + err = store.MigrateData() + if err != nil { + return err + } + + // Assert that our database connection is using bolt so we can call + // exportJson rather than ExportRaw. The exportJson function allows us to + // strip out the metadata which we don't want for our tests. + // TODO: update connection interface in CE to allow us to use ExportRaw and pass meta false + err = store.connection.Close() + if err != nil { + t.Fatalf("err closing bolt connection: %v", err) + } + con, ok := store.connection.(*boltdb.DbConnection) + if !ok { + t.Fatalf("backing database is not using boltdb, but the migrations test requires it") + } + + // Convert database back to json. + databasePath := con.GetDatabaseFilePath() + if _, err := os.Stat(databasePath); err != nil { + return fmt.Errorf("stat on %s failed: %s", databasePath, err) + } + + gotJSON, err := con.ExportJson(databasePath, false) + if err != nil { + t.Logf( + "failed re-exporting database %s to JSON: %v", + databasePath, + err, + ) + } + + wantJSON, err := os.ReadFile(wantPath) + if err != nil { + t.Fatalf("failed loading want JSON file %v: %v", wantPath, err) + } + + // Compare the result we got with the one we wanted. + if diff := cmp.Diff(wantJSON, gotJSON); diff != "" { + gotPath := filepath.Join(os.TempDir(), "portainer-migrator-test-fail.json") + os.WriteFile( + gotPath, + gotJSON, + 0600, + ) + t.Errorf( + "migrate data from %s to %s failed\nwrote migrated input to %s\nmismatch (-want +got):\n%s", + srcPath, + wantPath, + gotPath, + diff, + ) + } + return nil +} + +// importJSON reads input JSON and commits it to a portainer datastore.Store. +// Errors are logged with the testing package. +func importJSON(t *testing.T, r io.Reader, store *Store) error { + objects := make(map[string]interface{}) + + // Parse json into map of objects. + d := json.NewDecoder(r) + d.UseNumber() + err := d.Decode(&objects) + if err != nil { + return err + } + + // Get database connection from store. + con := store.connection + + for k, v := range objects { + switch k { + case "version": + versions, ok := v.(map[string]interface{}) + if !ok { + t.Logf("failed casting %s to map[string]interface{}", k) + } + + dbVersion, ok := versions["DB_VERSION"] + if !ok { + t.Logf("failed getting DB_VERSION from %s", k) + } + + numDBVersion, ok := dbVersion.(json.Number) + if !ok { + t.Logf("failed parsing DB_VERSION as json number from %s", k) + } + + intDBVersion, err := numDBVersion.Int64() + if err != nil { + t.Logf("failed casting %v to int: %v", numDBVersion, intDBVersion) + } + + err = con.CreateObjectWithStringId( + k, + []byte("DB_VERSION"), + int(intDBVersion), + ) + if err != nil { + t.Logf("failed writing DB_VERSION in %s: %v", k, err) + } + + instanceID, ok := versions["INSTANCE_ID"] + if !ok { + t.Logf("failed getting INSTANCE_ID from %s", k) + } + + err = con.CreateObjectWithStringId( + k, + []byte("INSTANCE_ID"), + instanceID, + ) + if err != nil { + t.Logf("failed writing INSTANCE_ID in %s: %v", k, err) + } + + case "dockerhub": + obj, ok := v.([]interface{}) + if !ok { + t.Logf("failed to cast %s to []interface{}", k) + } + err := con.CreateObjectWithStringId( + k, + []byte("DOCKERHUB"), + obj[0], + ) + if err != nil { + t.Logf("failed writing DOCKERHUB in %s: %v", k, err) + } + + case "ssl": + obj, ok := v.(map[string]interface{}) + if !ok { + t.Logf("failed to case %s to map[string]interface{}", k) + } + err := con.CreateObjectWithStringId( + k, + []byte("SSL"), + obj, + ) + if err != nil { + t.Logf("failed writing SSL in %s: %v", k, err) + } + + case "settings": + obj, ok := v.(map[string]interface{}) + if !ok { + t.Logf("failed to case %s to map[string]interface{}", k) + } + err := con.CreateObjectWithStringId( + k, + []byte("SETTINGS"), + obj, + ) + if err != nil { + t.Logf("failed writing SETTINGS in %s: %v", k, err) + } + + case "tunnel_server": + obj, ok := v.(map[string]interface{}) + if !ok { + t.Logf("failed to case %s to map[string]interface{}", k) + } + err := con.CreateObjectWithStringId( + k, + []byte("INFO"), + obj, + ) + if err != nil { + t.Logf("failed writing INFO in %s: %v", k, err) + } + case "templates": + continue + + default: + objlist, ok := v.([]interface{}) + if !ok { + t.Logf("failed to cast %s to []interface{}", k) + } + + for _, obj := range objlist { + value, ok := obj.(map[string]interface{}) + if !ok { + t.Logf("failed to cast %v to map[string]interface{}", obj) + } else { + var ok bool + var id interface{} + switch k { + case "endpoint_relations": + // TODO: need to make into an int, then do that weird + // stringification + id, ok = value["EndpointID"] + default: + id, ok = value["Id"] + } + if !ok { + // endpoint_relations: EndpointID + t.Logf("missing Id field: %s", k) + id = "error" + } + n, ok := id.(json.Number) + if !ok { + t.Logf("failed to cast %v to json.Number in %s", id, k) + } else { + key, err := n.Int64() + if err != nil { + t.Logf("failed to cast %v to int in %s", n, k) + } else { + err := con.CreateObjectWithId( + k, + int(key), + value, + ) + if err != nil { + t.Logf("failed writing %v in %s: %v", key, k, err) + } + } + } + } + } + } + } + + return nil +} diff --git a/api/datastore/migrate_dbversion29_test.go b/api/datastore/migrate_dbversion29_test.go index 914bffc74..0937b5e2f 100644 --- a/api/datastore/migrate_dbversion29_test.go +++ b/api/datastore/migrate_dbversion29_test.go @@ -33,7 +33,7 @@ func setup(store *Store) error { } func TestMigrateSettings(t *testing.T) { - _, store, teardown := MustNewTestStore(false) + _, store, teardown := MustNewTestStore(false, true) defer teardown() err := setup(store) diff --git a/api/datastore/migrate_dbversion33_test.go b/api/datastore/migrate_dbversion33_test.go index 9d1c9ce3a..cd78344ab 100644 --- a/api/datastore/migrate_dbversion33_test.go +++ b/api/datastore/migrate_dbversion33_test.go @@ -10,7 +10,7 @@ import ( ) func TestMigrateStackEntryPoint(t *testing.T) { - _, store, teardown := MustNewTestStore(false) + _, store, teardown := MustNewTestStore(false, true) defer teardown() stackService := store.Stack() diff --git a/api/datastore/migrator/migrate_ce.go b/api/datastore/migrator/migrate_ce.go index 3684439c5..1004af53a 100644 --- a/api/datastore/migrator/migrate_ce.go +++ b/api/datastore/migrator/migrate_ce.go @@ -1,16 +1,38 @@ package migrator import ( - "fmt" + "errors" + "reflect" + "runtime" werrors "github.com/pkg/errors" portainer "github.com/portainer/portainer/api" ) +type migration struct { + dbversion int + migrate func() error +} + func migrationError(err error, context string) error { return werrors.Wrap(err, "failed in "+context) } +func newMigration(dbversion int, migrate func() error) migration { + return migration{ + dbversion: dbversion, + migrate: migrate, + } +} + +func dbTooOldError() error { + return errors.New("migrating from less than Portainer 1.21.0 is not supported, please contact Portainer support.") +} + +func GetFunctionName(i interface{}) string { + return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name() +} + // Migrate checks the database version and migrate the existing data to the most recent data model. func (m *Migrator) Migrate() error { // set DB to updating status @@ -19,181 +41,87 @@ func (m *Migrator) Migrate() error { return migrationError(err, "StoreIsUpdating") } - if m.currentDBVersion < 17 { - return migrationError(err, "migrating from less than Portainer 1.21.0 is not supported, please contact Portainer support.") - } + migrations := []migration{ + // Portainer < 1.21.0 + newMigration(17, dbTooOldError), - // Portainer 1.21.0 - if m.currentDBVersion < 18 { - err := m.updateUsersToDBVersion18() - if err != nil { - return migrationError(err, "updateUsersToDBVersion18") - } + // Portainer 1.21.0 + newMigration(18, m.updateUsersToDBVersion18), + newMigration(18, m.updateEndpointsToDBVersion18), + newMigration(18, m.updateEndpointGroupsToDBVersion18), + newMigration(18, m.updateRegistriesToDBVersion18), - err = m.updateEndpointsToDBVersion18() - if err != nil { - return migrationError(err, "updateEndpointsToDBVersion18") - } + // 1.22.0 + newMigration(19, m.updateSettingsToDBVersion19), - err = m.updateEndpointGroupsToDBVersion18() - if err != nil { - return migrationError(err, "updateEndpointGroupsToDBVersion18") - } + // 1.22.1 + newMigration(20, m.updateUsersToDBVersion20), + newMigration(20, m.updateSettingsToDBVersion20), + newMigration(20, m.updateSchedulesToDBVersion20), - err = m.updateRegistriesToDBVersion18() - if err != nil { - return migrationError(err, "updateRegistriesToDBVersion18") - } - } + // Portainer 1.23.0 + // DBVersion 21 is missing as it was shipped as via hotfix 1.22.2 + newMigration(22, m.updateResourceControlsToDBVersion22), + newMigration(22, m.updateUsersAndRolesToDBVersion22), - // Portainer 1.22.0 - if m.currentDBVersion < 19 { - err := m.updateSettingsToDBVersion19() - if err != nil { - return migrationError(err, "updateSettingsToDBVersion19") - } - } + // Portainer 1.24.0 + newMigration(23, m.updateTagsToDBVersion23), + newMigration(23, m.updateEndpointsAndEndpointGroupsToDBVersion23), - // Portainer 1.22.1 - if m.currentDBVersion < 20 { - err := m.updateUsersToDBVersion20() - if err != nil { - return migrationError(err, "updateUsersToDBVersion20") - } + // Portainer 1.24.1 + newMigration(24, m.updateSettingsToDB24), - err = m.updateSettingsToDBVersion20() - if err != nil { - return migrationError(err, "updateSettingsToDBVersion20") - } + // Portainer 2.0.0 + newMigration(25, m.updateSettingsToDB25), + newMigration(25, m.updateStacksToDB24), // yes this looks odd. Don't be tempted to move it - err = m.updateSchedulesToDBVersion20() - if err != nil { - return migrationError(err, "updateSchedulesToDBVersion20") - } - } + // Portainer 2.1.0 + newMigration(26, m.updateEndpointSettingsToDB25), - // Portainer 1.23.0 - // DBVersion 21 is missing as it was shipped as via hotfix 1.22.2 - if m.currentDBVersion < 22 { - err := m.updateResourceControlsToDBVersion22() - if err != nil { - return migrationError(err, "updateResourceControlsToDBVersion22") - } + // Portainer 2.2.0 + newMigration(27, m.updateStackResourceControlToDB27), - err = m.updateUsersAndRolesToDBVersion22() - if err != nil { - return migrationError(err, "updateUsersAndRolesToDBVersion22") - } - } + // Portainer 2.6.0 + newMigration(30, m.migrateDBVersionToDB30), - // Portainer 1.24.0 - if m.currentDBVersion < 23 { - migrateLog.Info("Migrating to DB 23") - err := m.updateTagsToDBVersion23() - if err != nil { - return migrationError(err, "updateTagsToDBVersion23") - } + // Portainer 2.9.0 + newMigration(32, m.migrateDBVersionToDB32), - err = m.updateEndpointsAndEndpointGroupsToDBVersion23() - if err != nil { - return migrationError(err, "updateEndpointsAndEndpointGroupsToDBVersion23") - } - } - - // Portainer 1.24.1 - if m.currentDBVersion < 24 { - migrateLog.Info("Migrating to DB 24") - err := m.updateSettingsToDB24() - if err != nil { - return migrationError(err, "updateSettingsToDB24") - } - } - - // Portainer 2.0.0 - if m.currentDBVersion < 25 { - migrateLog.Info("Migrating to DB 25") - err := m.updateSettingsToDB25() - if err != nil { - return migrationError(err, "updateSettingsToDB25") - } - - err = m.updateStacksToDB24() - if err != nil { - return migrationError(err, "updateStacksToDB24") - } - } - - // Portainer 2.1.0 - if m.currentDBVersion < 26 { - migrateLog.Info("Migrating to DB 26") - err := m.updateEndpointSettingsToDB25() - if err != nil { - return migrationError(err, "updateEndpointSettingsToDB25") - } - } + // Portainer 2.9.1, 2.9.2 + newMigration(33, m.migrateDBVersionToDB33), - // Portainer 2.2.0 - if m.currentDBVersion < 27 { - migrateLog.Info("Migrating to DB 27") - err := m.updateStackResourceControlToDB27() - if err != nil { - return migrationError(err, "updateStackResourceControlToDB27") - } - } + // Portainer 2.10 + newMigration(34, m.migrateDBVersionToDB34), - // Portainer 2.6.0 - if m.currentDBVersion < 30 { - migrateLog.Info("Migrating to DB 30") - err := m.migrateDBVersionToDB30() - if err != nil { - return migrationError(err, "migrateDBVersionToDB30") - } - } + // Portainer 2.9.3 (yep out of order, but 2.10 is EE only) + newMigration(35, m.migrateDBVersionToDB35), - // Portainer 2.9.0 - if m.currentDBVersion < 32 { - err := m.migrateDBVersionToDB32() - if err != nil { - return migrationError(err, "migrateDBVersionToDB32") - } + newMigration(36, m.migrateDBVersionToDB36), } - // Portainer 2.9.1, 2.9.2 - if m.currentDBVersion < 33 { - migrateLog.Info("Migrating to DB 33") - err := m.migrateDBVersionToDB33() - if err != nil { - return migrationError(err, "migrateDBVersionToDB33") - } - } + var lastDbVersion int + for _, migration := range migrations { + if m.currentDBVersion < migration.dbversion { - // Portainer 2.10 - if m.currentDBVersion < 34 { - migrateLog.Info("Migrating to DB 34") - if err := m.migrateDBVersionToDB34(); err != nil { - return migrationError(err, "migrateDBVersionToDB34") - } - } + // Print the next line only when the version changes + if migration.dbversion > lastDbVersion { + migrateLog.Infof("Migrating DB to version %d", migration.dbversion) + } - // Portainer 2.9.3 (yep out of order, but 2.10 is EE only) - if m.currentDBVersion < 35 { - migrateLog.Info("Migrating to DB 35") - if err := m.migrateDBVersionToDB35(); err != nil { - return migrationError(err, "migrateDBVersionToDB35") + err := migration.migrate() + if err != nil { + return migrationError(err, GetFunctionName(migration.migrate)) + } } + lastDbVersion = migration.dbversion } - if m.currentDBVersion < 36 { - migrateLog.Info("Migrating to DB 36") - if err := m.migrateDBVersionToDB36(); err != nil { - return migrationError(err, "migrateDBVersionToDB36") - } - } + migrateLog.Infof("Setting DB version to %d", portainer.DBVersion) err = m.versionService.StoreDBVersion(portainer.DBVersion) if err != nil { return migrationError(err, "StoreDBVersion") } - migrateLog.Info(fmt.Sprintf("Updated DB version to %d", portainer.DBVersion)) + migrateLog.Infof("Updated DB version to %d", portainer.DBVersion) // reset DB updating status return m.versionService.StoreIsUpdating(false) diff --git a/api/datastore/migrator/migrate_dbversion17.go b/api/datastore/migrator/migrate_dbversion17.go index 4e17090a8..ee1b563cf 100644 --- a/api/datastore/migrator/migrate_dbversion17.go +++ b/api/datastore/migrator/migrate_dbversion17.go @@ -5,6 +5,7 @@ import ( ) func (m *Migrator) updateUsersToDBVersion18() error { + migrateLog.Info("- updating users") legacyUsers, err := m.userService.Users() if err != nil { return err @@ -39,6 +40,7 @@ func (m *Migrator) updateUsersToDBVersion18() error { } func (m *Migrator) updateEndpointsToDBVersion18() error { + migrateLog.Info("- updating endpoints") legacyEndpoints, err := m.endpointService.Endpoints() if err != nil { return err @@ -69,6 +71,7 @@ func (m *Migrator) updateEndpointsToDBVersion18() error { } func (m *Migrator) updateEndpointGroupsToDBVersion18() error { + migrateLog.Info("- updating endpoint groups") legacyEndpointGroups, err := m.endpointGroupService.EndpointGroups() if err != nil { return err @@ -99,6 +102,7 @@ func (m *Migrator) updateEndpointGroupsToDBVersion18() error { } func (m *Migrator) updateRegistriesToDBVersion18() error { + migrateLog.Info("- updating registries") legacyRegistries, err := m.registryService.Registries() if err != nil { return err diff --git a/api/datastore/migrator/migrate_dbversion18.go b/api/datastore/migrator/migrate_dbversion18.go index 2de9dc2fd..a3f450289 100644 --- a/api/datastore/migrator/migrate_dbversion18.go +++ b/api/datastore/migrator/migrate_dbversion18.go @@ -3,6 +3,7 @@ package migrator import portainer "github.com/portainer/portainer/api" func (m *Migrator) updateSettingsToDBVersion19() error { + migrateLog.Info("- updating settings") legacySettings, err := m.settingsService.Settings() if err != nil { return err diff --git a/api/datastore/migrator/migrate_dbversion19.go b/api/datastore/migrator/migrate_dbversion19.go index 9f793f41f..9782ab336 100644 --- a/api/datastore/migrator/migrate_dbversion19.go +++ b/api/datastore/migrator/migrate_dbversion19.go @@ -7,6 +7,7 @@ import ( const scheduleScriptExecutionJobType = 1 func (m *Migrator) updateUsersToDBVersion20() error { + migrateLog.Info("- updating user authentication") return m.authorizationService.UpdateUsersAuthorizations() } @@ -22,6 +23,7 @@ func (m *Migrator) updateSettingsToDBVersion20() error { } func (m *Migrator) updateSchedulesToDBVersion20() error { + migrateLog.Info("- updating schedules") legacySchedules, err := m.scheduleService.Schedules() if err != nil { return err diff --git a/api/datastore/migrator/migrate_dbversion20.go b/api/datastore/migrator/migrate_dbversion20.go index 23ddd82ab..54daf6ced 100644 --- a/api/datastore/migrator/migrate_dbversion20.go +++ b/api/datastore/migrator/migrate_dbversion20.go @@ -6,6 +6,7 @@ import ( ) func (m *Migrator) updateResourceControlsToDBVersion22() error { + migrateLog.Info("- updating resource controls") legacyResourceControls, err := m.resourceControlService.ResourceControls() if err != nil { return err @@ -24,6 +25,7 @@ func (m *Migrator) updateResourceControlsToDBVersion22() error { } func (m *Migrator) updateUsersAndRolesToDBVersion22() error { + migrateLog.Info("- updating users and roles") legacyUsers, err := m.userService.Users() if err != nil { return err diff --git a/api/datastore/migrator/migrate_dbversion22.go b/api/datastore/migrator/migrate_dbversion22.go index b5466fe6a..e1ecf7c67 100644 --- a/api/datastore/migrator/migrate_dbversion22.go +++ b/api/datastore/migrator/migrate_dbversion22.go @@ -3,7 +3,7 @@ package migrator import portainer "github.com/portainer/portainer/api" func (m *Migrator) updateTagsToDBVersion23() error { - migrateLog.Info("Updating tags") + migrateLog.Info("- Updating tags") tags, err := m.tagService.Tags() if err != nil { return err @@ -21,7 +21,7 @@ func (m *Migrator) updateTagsToDBVersion23() error { } func (m *Migrator) updateEndpointsAndEndpointGroupsToDBVersion23() error { - migrateLog.Info("Updating endpoints and endpoint groups") + migrateLog.Info("- updating endpoints and endpoint groups") tags, err := m.tagService.Tags() if err != nil { return err diff --git a/api/datastore/migrator/migrate_dbversion23.go b/api/datastore/migrator/migrate_dbversion23.go index 69b2fa552..e66c2ceb6 100644 --- a/api/datastore/migrator/migrate_dbversion23.go +++ b/api/datastore/migrator/migrate_dbversion23.go @@ -3,7 +3,7 @@ package migrator import portainer "github.com/portainer/portainer/api" func (m *Migrator) updateSettingsToDB24() error { - migrateLog.Info("Updating Settings") + migrateLog.Info("- updating Settings") legacySettings, err := m.settingsService.Settings() if err != nil { @@ -18,7 +18,7 @@ func (m *Migrator) updateSettingsToDB24() error { } func (m *Migrator) updateStacksToDB24() error { - migrateLog.Info("Updating stacks") + migrateLog.Info("- updating stacks") stacks, err := m.stackService.Stacks() if err != nil { return err diff --git a/api/datastore/migrator/migrate_dbversion24.go b/api/datastore/migrator/migrate_dbversion24.go index de3915af6..1ee8c2f6c 100644 --- a/api/datastore/migrator/migrate_dbversion24.go +++ b/api/datastore/migrator/migrate_dbversion24.go @@ -5,7 +5,7 @@ import ( ) func (m *Migrator) updateSettingsToDB25() error { - migrateLog.Info("Updating settings") + migrateLog.Info("- updating settings") legacySettings, err := m.settingsService.Settings() if err != nil { diff --git a/api/datastore/migrator/migrate_dbversion25.go b/api/datastore/migrator/migrate_dbversion25.go index d9b39e47c..ba4c35b71 100644 --- a/api/datastore/migrator/migrate_dbversion25.go +++ b/api/datastore/migrator/migrate_dbversion25.go @@ -5,7 +5,7 @@ import ( ) func (m *Migrator) updateEndpointSettingsToDB25() error { - migrateLog.Info("Updating endpoint settings") + migrateLog.Info("- updating endpoint settings") settings, err := m.settingsService.Settings() if err != nil { return err diff --git a/api/datastore/migrator/migrate_dbversion26.go b/api/datastore/migrator/migrate_dbversion26.go index e85a5ec24..50b6c4b0e 100644 --- a/api/datastore/migrator/migrate_dbversion26.go +++ b/api/datastore/migrator/migrate_dbversion26.go @@ -7,7 +7,7 @@ import ( ) func (m *Migrator) updateStackResourceControlToDB27() error { - migrateLog.Info("Updating stack resource controls") + migrateLog.Info("- updating stack resource controls") resourceControls, err := m.resourceControlService.ResourceControls() if err != nil { return err diff --git a/api/datastore/migrator/migrate_dbversion29.go b/api/datastore/migrator/migrate_dbversion29.go index 32194d462..f59120b55 100644 --- a/api/datastore/migrator/migrate_dbversion29.go +++ b/api/datastore/migrator/migrate_dbversion29.go @@ -1,7 +1,7 @@ package migrator func (m *Migrator) migrateDBVersionToDB30() error { - migrateLog.Info("Updating legacy settings") + migrateLog.Info("- updating legacy settings") if err := m.MigrateSettingsToDB30(); err != nil { return err } diff --git a/api/datastore/migrator/migrate_dbversion31.go b/api/datastore/migrator/migrate_dbversion31.go index d84bd8446..7e4249421 100644 --- a/api/datastore/migrator/migrate_dbversion31.go +++ b/api/datastore/migrator/migrate_dbversion31.go @@ -2,38 +2,34 @@ package migrator import ( "fmt" - "github.com/portainer/portainer/api/dataservices/errors" "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 { - migrateLog.Info("Updating registries") err := m.updateRegistriesToDB32() if err != nil { return err } - migrateLog.Info("Updating dockerhub") err = m.updateDockerhubToDB32() if err != nil { return err } - migrateLog.Info("Updating resource controls") if err := m.updateVolumeResourceControlToDB32(); err != nil { return err } - migrateLog.Info("Updating kubeconfig expiry") if err := m.kubeconfigExpiryToDB32(); err != nil { return err } - migrateLog.Info("Setting default helm repository url") if err := m.helmRepositoryURLToDB32(); err != nil { return err } @@ -42,6 +38,7 @@ func (m *Migrator) migrateDBVersionToDB32() error { } func (m *Migrator) updateRegistriesToDB32() error { + migrateLog.Info("- updating registries") registries, err := m.registryService.Registries() if err != nil { return err @@ -84,6 +81,7 @@ func (m *Migrator) updateRegistriesToDB32() error { } func (m *Migrator) updateDockerhubToDB32() error { + migrateLog.Info("- updating dockerhub") dockerhub, err := m.dockerhubService.DockerHub() if err == errors.ErrObjectNotFound { return nil @@ -172,6 +170,7 @@ func (m *Migrator) updateDockerhubToDB32() error { } func (m *Migrator) updateVolumeResourceControlToDB32() error { + migrateLog.Info("- updating resource controls") endpoints, err := m.endpointService.Endpoints() if err != nil { return fmt.Errorf("failed fetching environments: %w", err) @@ -264,6 +263,7 @@ func findResourcesToUpdateForDB32(dockerID string, volumesData map[string]interf } func (m *Migrator) kubeconfigExpiryToDB32() error { + migrateLog.Info("- updating kubeconfig expiry") settings, err := m.settingsService.Settings() if err != nil { return err @@ -273,6 +273,7 @@ func (m *Migrator) kubeconfigExpiryToDB32() error { } func (m *Migrator) helmRepositoryURLToDB32() error { + migrateLog.Info("- setting default helm repository URL") settings, err := m.settingsService.Settings() if err != nil { return err diff --git a/api/datastore/migrator/migrate_dbversion32.go b/api/datastore/migrator/migrate_dbversion32.go index be638f4a9..f41bb7a7b 100644 --- a/api/datastore/migrator/migrate_dbversion32.go +++ b/api/datastore/migrator/migrate_dbversion32.go @@ -1,8 +1,11 @@ package migrator -import portainer "github.com/portainer/portainer/api" +import ( + portainer "github.com/portainer/portainer/api" +) func (m *Migrator) migrateDBVersionToDB33() error { + migrateLog.Info("- updating settings") if err := m.migrateSettingsToDB33(); err != nil { return err } @@ -16,7 +19,7 @@ func (m *Migrator) migrateSettingsToDB33() error { return err } - migrateLog.Info("Setting default kubectl shell image") + migrateLog.Info("- setting default kubectl shell image") settings.KubectlShellImage = portainer.DefaultKubectlShellImage return m.settingsService.UpdateSettings(settings) } diff --git a/api/datastore/migrator/migrate_dbversion33.go b/api/datastore/migrator/migrate_dbversion33.go index 304df6a1b..ed067dc12 100644 --- a/api/datastore/migrator/migrate_dbversion33.go +++ b/api/datastore/migrator/migrate_dbversion33.go @@ -1,9 +1,11 @@ package migrator -import "github.com/portainer/portainer/api/dataservices" +import ( + "github.com/portainer/portainer/api/dataservices" +) func (m *Migrator) migrateDBVersionToDB34() error { - migrateLog.Info("Migrating stacks") + migrateLog.Info("- updating stacks") err := MigrateStackEntryPoint(m.stackService) if err != nil { return err diff --git a/api/datastore/migrator/migrate_dbversion34.go b/api/datastore/migrator/migrate_dbversion34.go index 79b7b53f2..d0ce0d8a6 100644 --- a/api/datastore/migrator/migrate_dbversion34.go +++ b/api/datastore/migrator/migrate_dbversion34.go @@ -3,7 +3,7 @@ package migrator 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. - migrateLog.Info("Updating dockerhub registries") + migrateLog.Info("- updating dockerhub registries") err := m.updateDockerhubToDB32() if err != nil { return err diff --git a/api/datastore/test_data/input_24.json b/api/datastore/test_data/input_24.json new file mode 100644 index 000000000..889f80c83 --- /dev/null +++ b/api/datastore/test_data/input_24.json @@ -0,0 +1,2804 @@ +{ + "dockerhub": [ + { + "Authentication": false, + "Username": "" + } + ], + "endpoint_groups": [ + { + "AuthorizedTeams": null, + "AuthorizedUsers": null, + "Description": "Unassigned endpoints", + "Id": 1, + "Labels": [], + "Name": "Unassigned", + "TagIds": [], + "Tags": null, + "TeamAccessPolicies": {}, + "UserAccessPolicies": {} + } + ], + "endpoint_relations": [ + { + "EdgeStacks": {}, + "EndpointID": 1 + } + ], + "endpoints": [ + { + "AuthorizedTeams": null, + "AuthorizedUsers": null, + "AzureCredentials": { + "ApplicationID": "", + "AuthenticationKey": "", + "TenantID": "" + }, + "EdgeKey": "", + "Extensions": [], + "GroupId": 1, + "Id": 1, + "Name": "local", + "PublicURL": "", + "Snapshots": [ + { + "DockerVersion": "20.10.13", + "HealthyContainerCount": 0, + "ImageCount": 9, + "RunningContainerCount": 5, + "ServiceCount": 0, + "SnapshotRaw": { + "Containers": [ + { + "Command": "/docker-entrypoint.sh nginx -g 'daemon off;'", + "Created": 1648609973, + "HostConfig": { + "NetworkMode": "nginx_default" + }, + "Id": "0eca796ba47ad6e479a09191d90be17b5d151b63227f30ec1974338a55a24f11", + "Image": "nginx:latest", + "ImageID": "sha256:c919045c4c2b0b0007c606e763ed2c830c7b1d038ce878a3c0d6f5b81e6ab80b", + "Labels": { + "com.docker.compose.config-hash": "b6013e48916cd17f37d6675ae35e0cac34cace20", + "com.docker.compose.container-number": "1", + "com.docker.compose.oneoff": "False", + "com.docker.compose.project": "nginx", + "com.docker.compose.service": "redis-master", + "com.docker.compose.version": "1.5.0", + "maintainer": "NGINX Docker Maintainers \u003cdocker-maint@nginx.com\u003e" + }, + "Mounts": [], + "Names": ["/nginx_redis-master_1"], + "NetworkSettings": { + "Networks": { + "nginx_default": { + "Aliases": null, + "DriverOpts": null, + "EndpointID": "f761433ec60e0514f2a4ce9e7e408029af6c363a95e5723e31f82a497597ede4", + "Gateway": "172.20.0.1", + "GlobalIPv6Address": "", + "GlobalIPv6PrefixLen": 0, + "IPAMConfig": {}, + "IPAddress": "172.20.0.2", + "IPPrefixLen": 16, + "IPv6Gateway": "", + "Links": null, + "MacAddress": "02:42:ac:14:00:02", + "NetworkID": "d9576d8c709d65504ca1d0a654cdc7b13ab17ebc6ae051f74e6b124d9d368c9a" + } + } + }, + "Ports": [ + { + "IP": "0.0.0.0", + "PrivatePort": 80, + "PublicPort": 8080, + "Type": "tcp" + }, + { + "IP": "::", + "PrivatePort": 80, + "PublicPort": 8080, + "Type": "tcp" + } + ], + "State": "running", + "Status": "Up 2 minutes" + }, + { + "Command": "apache2-foreground", + "Created": 1648609919, + "HostConfig": { + "NetworkMode": "redis_default" + }, + "Id": "ab541bbf504f9ef6cddfbd0dd06224f36fe7b291a2d9a3bc1ed406140312bf7a", + "Image": "gcr.io/google-samples/gb-frontend:v4", + "ImageID": "sha256:e2b3e8542af735080e6bda06873ce666e2319eea353884a88e45f3c9ef996846", + "Labels": { + "com.docker.compose.config-hash": "7c2882b6ebdb0d582b4a326e9383d0ea5b3cc155", + "com.docker.compose.container-number": "1", + "com.docker.compose.oneoff": "False", + "com.docker.compose.project": "redis", + "com.docker.compose.service": "frontend", + "com.docker.compose.version": "1.5.0", + "kompose.service.type": "LoadBalancer" + }, + "Mounts": [], + "Names": ["/redis_frontend_1"], + "NetworkSettings": { + "Networks": { + "redis_default": { + "Aliases": null, + "DriverOpts": null, + "EndpointID": "f9c6fb004c9546d999230b1a3b64f0e680eef8a74d494f7e0455ba64a190b322", + "Gateway": "172.19.0.1", + "GlobalIPv6Address": "", + "GlobalIPv6PrefixLen": 0, + "IPAMConfig": {}, + "IPAddress": "172.19.0.2", + "IPPrefixLen": 16, + "IPv6Gateway": "", + "Links": null, + "MacAddress": "02:42:ac:13:00:02", + "NetworkID": "9fa60f4b6a71b29a95127e99ae0ba09f616386a58ae790f0e8f975e8029f791d" + } + } + }, + "Ports": [ + { + "IP": "0.0.0.0", + "PrivatePort": 80, + "PublicPort": 80, + "Type": "tcp" + }, + { + "IP": "::", + "PrivatePort": 80, + "PublicPort": 80, + "Type": "tcp" + } + ], + "State": "running", + "Status": "Up 3 minutes" + }, + { + "Command": "redis-server /etc/redis/redis.conf", + "Created": 1648609919, + "HostConfig": { + "NetworkMode": "redis_default" + }, + "Id": "621d31f8aa50ad8ee182f3bc581fe19fa67c4f3728b412cfe3d20aa0d6deb2ef", + "Image": "k8s.gcr.io/redis:e2e", + "ImageID": "sha256:e5e67996c442f903cda78dd983ea6e94bb4e542950fd2eba666b44cbd303df42", + "Labels": { + "com.docker.compose.config-hash": "86f8fef221e155eb14f94d707313986c865e8ac5", + "com.docker.compose.container-number": "1", + "com.docker.compose.oneoff": "False", + "com.docker.compose.project": "redis", + "com.docker.compose.service": "redis-master", + "com.docker.compose.version": "1.5.0" + }, + "Mounts": [ + { + "Destination": "/data", + "Driver": "local", + "Mode": "", + "Name": "a3fedc4b90b70e9f28456b4f88f8a2ebd90f76cf8a8a5e4fb5dcbd0b90ff0153", + "Propagation": "", + "RW": true, + "Source": "", + "Type": "volume" + } + ], + "Names": ["/redis_redis-master_1"], + "NetworkSettings": { + "Networks": { + "redis_default": { + "Aliases": null, + "DriverOpts": null, + "EndpointID": "7efb43c9f1b67e518e53b2dbcee2cfbbab07dcfa4788bad8c9fde8334cb6a213", + "Gateway": "172.19.0.1", + "GlobalIPv6Address": "", + "GlobalIPv6PrefixLen": 0, + "IPAMConfig": {}, + "IPAddress": "172.19.0.3", + "IPPrefixLen": 16, + "IPv6Gateway": "", + "Links": null, + "MacAddress": "02:42:ac:13:00:03", + "NetworkID": "9fa60f4b6a71b29a95127e99ae0ba09f616386a58ae790f0e8f975e8029f791d" + } + } + }, + "Ports": [ + { + "IP": "0.0.0.0", + "PrivatePort": 6379, + "PublicPort": 49154, + "Type": "tcp" + }, + { + "IP": "::", + "PrivatePort": 6379, + "PublicPort": 49154, + "Type": "tcp" + } + ], + "State": "running", + "Status": "Up 3 minutes" + }, + { + "Command": "/entrypoint.sh /bin/sh -c /run.sh", + "Created": 1648609919, + "HostConfig": { + "NetworkMode": "redis_default" + }, + "Id": "536f5789838e57d3e24658cf5a45d7957861542ac4d8dab688d7a64d2be274f1", + "Image": "gcr.io/google_samples/gb-redisslave:v1", + "ImageID": "sha256:5f026ddffa27f011242781f7f2498538334e173869e7fe757008881fb48180b6", + "Labels": { + "com.docker.compose.config-hash": "757a0da54b072e33a11a367408e83d930ad1f30f", + "com.docker.compose.container-number": "1", + "com.docker.compose.oneoff": "False", + "com.docker.compose.project": "redis", + "com.docker.compose.service": "redis-slave", + "com.docker.compose.version": "1.5.0" + }, + "Mounts": [ + { + "Destination": "/data", + "Driver": "local", + "Mode": "", + "Name": "5f93240d96e42d0b3728435cbfb43b6fcb3b01446d5c7d4be1cba8f9336c0a58", + "Propagation": "", + "RW": true, + "Source": "", + "Type": "volume" + } + ], + "Names": ["/redis_redis-slave_1"], + "NetworkSettings": { + "Networks": { + "redis_default": { + "Aliases": null, + "DriverOpts": null, + "EndpointID": "78b76382fcb909030a6e96caf5ce579d852bb565d82d47293302900eb81042ee", + "Gateway": "172.19.0.1", + "GlobalIPv6Address": "", + "GlobalIPv6PrefixLen": 0, + "IPAMConfig": {}, + "IPAddress": "172.19.0.4", + "IPPrefixLen": 16, + "IPv6Gateway": "", + "Links": null, + "MacAddress": "02:42:ac:13:00:04", + "NetworkID": "9fa60f4b6a71b29a95127e99ae0ba09f616386a58ae790f0e8f975e8029f791d" + } + } + }, + "Ports": [ + { + "IP": "0.0.0.0", + "PrivatePort": 6379, + "PublicPort": 49155, + "Type": "tcp" + }, + { + "IP": "::", + "PrivatePort": 6379, + "PublicPort": 49155, + "Type": "tcp" + } + ], + "State": "running", + "Status": "Up 3 minutes" + }, + { + "Command": "httpd-foreground", + "Created": 1647381304, + "HostConfig": { + "NetworkMode": "bridge" + }, + "Id": "74034d9d05b07b2592fbe4ec878486eac5c743e960f75816884750e0aa40939b", + "Image": "httpd:latest", + "ImageID": "sha256:6b8e87fff1072470bbfc957a735e7e46007177864a7f61bd9e0f5872d3d7b4a5", + "Labels": {}, + "Mounts": [ + { + "Destination": "/usr/local/apache2/htdocs", + "Driver": "local", + "Mode": "z", + "Name": "f1d6fe0188cc05f24309a86b58cf8130f89fa21aaa73f37789ad36fd188b38e2", + "Propagation": "", + "RW": true, + "Source": "/var/lib/docker/volumes/f1d6fe0188cc05f24309a86b58cf8130f89fa21aaa73f37789ad36fd188b38e2/_data", + "Type": "volume" + } + ], + "Names": ["/httpd"], + "NetworkSettings": { + "Networks": { + "bridge": { + "Aliases": null, + "DriverOpts": null, + "EndpointID": "2914aa7399460c2e7d48e9c7ae17b29a099bbda9e4fd514ac4cb660c30edf677", + "Gateway": "172.17.0.1", + "GlobalIPv6Address": "", + "GlobalIPv6PrefixLen": 0, + "IPAMConfig": null, + "IPAddress": "172.17.0.2", + "IPPrefixLen": 16, + "IPv6Gateway": "", + "Links": null, + "MacAddress": "02:42:ac:11:00:02", + "NetworkID": "ed5a12608291f91d5c344f685f97665e3b7252d32c71ec15b10c55c3d8eb8235" + } + } + }, + "Ports": [ + { + "IP": "0.0.0.0", + "PrivatePort": 80, + "PublicPort": 49153, + "Type": "tcp" + }, + { + "IP": "::", + "PrivatePort": 80, + "PublicPort": 49153, + "Type": "tcp" + } + ], + "State": "running", + "Status": "Up 2 days" + } + ], + "Images": [ + { + "Containers": -1, + "Created": 1648010665, + "Id": "sha256:86a511f3915ac296298506d2caf827e9597483bf84115bbe4e1707829de8534a", + "Labels": null, + "ParentId": "sha256:d03d4e751a04005e130d20ca4f22b3074b1ffbfdcf6b59a5d85727e267d31d98", + "RepoDigests": ["prabhat/ubuntu@sha256:bfe1371854e7e0e28517a041bfda08ecb3b345738c62092bfdf04f0513c27219"], + "RepoTags": ["ubuntu-prabhat:latest", "prabhat/ubuntu:latest"], + "SharedSize": -1, + "Size": 753642080, + "VirtualSize": 753642080 + }, + { + "Containers": -1, + "Created": 1647581440, + "Id": "sha256:ff0fea8310f3957d9b1e6ba494f3e4b63cb348c76160c6c15578e65995ffaa87", + "Labels": null, + "ParentId": "", + "RepoDigests": ["ubuntu@sha256:bea6d19168bbfd6af8d77c2cc3c572114eb5d113e6f422573c93cb605a0e2ffb"], + "RepoTags": ["ubuntu:latest"], + "SharedSize": -1, + "Size": 72759731, + "VirtualSize": 72759731 + }, + { + "Containers": -1, + "Created": 1647285410, + "Id": "sha256:6b8e87fff1072470bbfc957a735e7e46007177864a7f61bd9e0f5872d3d7b4a5", + "Labels": null, + "ParentId": "", + "RepoDigests": ["httpd@sha256:73496cbfc473872dd185154a3b96faa4407d773e893c6a7b9d8f977c331bc45d"], + "RepoTags": ["httpd:latest"], + "SharedSize": -1, + "Size": 143974476, + "VirtualSize": 143974476 + }, + { + "Containers": -1, + "Created": 1646799692, + "Id": "sha256:a4ca82e34b45e34c0a8bffe4e974983a528e8beca6030c1f9d17ac7f96c7847f", + "Labels": null, + "ParentId": "", + "RepoDigests": ["portainer/agent@sha256:ca1a51a745f2490cf5345883dd8c5f6a953a15251ab95af246ca9e2fb3436dde"], + "RepoTags": null, + "SharedSize": -1, + "Size": 154347153, + "VirtualSize": 154347153 + }, + { + "Containers": -1, + "Created": 1646143205, + "Id": "sha256:c919045c4c2b0b0007c606e763ed2c830c7b1d038ce878a3c0d6f5b81e6ab80b", + "Labels": { + "maintainer": "NGINX Docker Maintainers \u003cdocker-maint@nginx.com\u003e" + }, + "ParentId": "", + "RepoDigests": [ + "nginx@sha256:1c13bc6de5dfca749c377974146ac05256791ca2fe1979fc8e8278bf0121d285", + "prabhat/nginx@sha256:2468d48e476b6a079eb646e87620f96ce1818ac0c5b3a8450532cea64b3421f4" + ], + "RepoTags": ["nginx:latest", "prabhat/nginx:latest"], + "SharedSize": -1, + "Size": 141505630, + "VirtualSize": 141505630 + }, + { + "Containers": -1, + "Created": 1551997193, + "Id": "sha256:6d1ef012b5674ad8a127ecfa9b5e6f5178d171b90ee462846974177fd9bdd39f", + "Labels": null, + "ParentId": "", + "RepoDigests": ["alpine@sha256:8421d9a84432575381bfabd248f1eb56f3aa21d9d7cd2511583c68c9b7511d10"], + "RepoTags": null, + "SharedSize": -1, + "Size": 4206494, + "VirtualSize": 4206494 + }, + { + "Containers": -1, + "Created": 1460421063, + "Id": "sha256:e2b3e8542af735080e6bda06873ce666e2319eea353884a88e45f3c9ef996846", + "Labels": {}, + "ParentId": "", + "RepoDigests": ["gcr.io/google-samples/gb-frontend@sha256:d44e7d7491a537f822e7fe8615437e4a8a08f3a7a1d7d4cb9066b92f7556ba6d"], + "RepoTags": ["gcr.io/google-samples/gb-frontend:v4"], + "SharedSize": -1, + "Size": 512161546, + "VirtualSize": 512161546 + }, + { + "Containers": -1, + "Created": 1439232099, + "Id": "sha256:5f026ddffa27f011242781f7f2498538334e173869e7fe757008881fb48180b6", + "Labels": null, + "ParentId": "", + "RepoDigests": ["gcr.io/google_samples/gb-redisslave@sha256:90f62695e641e1a27d1a5e0bbb8b622205a48e18311b51b0da419ffad24b9016"], + "RepoTags": ["gcr.io/google_samples/gb-redisslave:v1"], + "SharedSize": -1, + "Size": 109508753, + "VirtualSize": 109508753 + }, + { + "Containers": -1, + "Created": 1426838165, + "Id": "sha256:e5e67996c442f903cda78dd983ea6e94bb4e542950fd2eba666b44cbd303df42", + "Labels": null, + "ParentId": "", + "RepoDigests": ["k8s.gcr.io/redis@sha256:f066bcf26497fbc55b9bf0769cb13a35c0afa2aa42e737cc46b7fb04b23a2f25"], + "RepoTags": ["k8s.gcr.io/redis:e2e"], + "SharedSize": -1, + "Size": 419003740, + "VirtualSize": 419003740 + } + ], + "Info": { + "Architecture": "x86_64", + "BridgeNfIp6tables": true, + "BridgeNfIptables": true, + "CPUSet": true, + "CPUShares": true, + "CgroupDriver": "cgroupfs", + "ClusterAdvertise": "", + "ClusterStore": "", + "ContainerdCommit": { + "Expected": "2a1d4dbdb2a1030dc5b01e96fb110a9d9f150ecc", + "ID": "2a1d4dbdb2a1030dc5b01e96fb110a9d9f150ecc" + }, + "Containers": 5, + "ContainersPaused": 0, + "ContainersRunning": 5, + "ContainersStopped": 0, + "CpuCfsPeriod": true, + "CpuCfsQuota": true, + "Debug": false, + "DefaultRuntime": "runc", + "DockerRootDir": "/var/lib/docker", + "Driver": "overlay2", + "DriverStatus": [ + ["Backing Filesystem", "extfs"], + ["Supports d_type", "true"], + ["Native Overlay Diff", "true"], + ["userxattr", "false"] + ], + "ExperimentalBuild": false, + "GenericResources": null, + "HttpProxy": "", + "HttpsProxy": "", + "ID": "UQ2Y:ZHNN:XIZL:66ZK:NJCU:EO2L:BH35:SXHA:6TLU:AA25:PCAE:UQVE", + "IPv4Forwarding": true, + "Images": 13, + "IndexServerAddress": "https://index.docker.io/v1/", + "InitBinary": "docker-init", + "InitCommit": { + "Expected": "de40ad0", + "ID": "de40ad0" + }, + "Isolation": "", + "KernelMemory": true, + "KernelMemoryTCP": true, + "KernelVersion": "5.13.0-35-generic", + "Labels": [], + "LiveRestoreEnabled": false, + "LoggingDriver": "json-file", + "MemTotal": 25098706944, + "MemoryLimit": true, + "NCPU": 8, + "NEventsListener": 0, + "NFd": 813, + "NGoroutines": 68, + "Name": "prabhat-linux", + "NoProxy": "", + "OSType": "linux", + "OomKillDisable": true, + "OperatingSystem": "Zorin OS 16.1", + "PidsLimit": true, + "Plugins": { + "Authorization": null, + "Log": ["awslogs", "fluentd", "gcplogs", "gelf", "journald", "json-file", "local", "logentries", "splunk", "syslog"], + "Network": ["bridge", "host", "ipvlan", "macvlan", "null", "overlay"], + "Volume": ["local"] + }, + "RegistryConfig": { + "AllowNondistributableArtifactsCIDRs": [], + "AllowNondistributableArtifactsHostnames": [], + "IndexConfigs": { + "docker.io": { + "Mirrors": [], + "Name": "docker.io", + "Official": true, + "Secure": true + } + }, + "InsecureRegistryCIDRs": ["127.0.0.0/8"], + "Mirrors": [] + }, + "RuncCommit": { + "Expected": "v1.0.3-0-gf46b6ba", + "ID": "v1.0.3-0-gf46b6ba" + }, + "Runtimes": { + "io.containerd.runc.v2": { + "path": "runc" + }, + "io.containerd.runtime.v1.linux": { + "path": "runc" + }, + "runc": { + "path": "runc" + } + }, + "SecurityOptions": ["name=apparmor", "name=seccomp,profile=default"], + "ServerVersion": "20.10.13", + "SwapLimit": true, + "Swarm": { + "ControlAvailable": false, + "Error": "", + "LocalNodeState": "inactive", + "NodeAddr": "", + "NodeID": "", + "RemoteManagers": null + }, + "SystemStatus": null, + "SystemTime": "2022-03-30T16:15:12.017274117+13:00", + "Warnings": null + }, + "Networks": [ + { + "Attachable": false, + "ConfigFrom": { + "Network": "" + }, + "ConfigOnly": false, + "Containers": {}, + "Created": "2022-03-28T08:57:24.093279463+13:00", + "Driver": "bridge", + "EnableIPv6": false, + "IPAM": { + "Config": [ + { + "Gateway": "172.17.0.1", + "Subnet": "172.17.0.0/16" + } + ], + "Driver": "default", + "Options": null + }, + "Id": "ed5a12608291f91d5c344f685f97665e3b7252d32c71ec15b10c55c3d8eb8235", + "Ingress": false, + "Internal": false, + "Labels": {}, + "Name": "bridge", + "Options": { + "com.docker.network.bridge.default_bridge": "true", + "com.docker.network.bridge.enable_icc": "true", + "com.docker.network.bridge.enable_ip_masquerade": "true", + "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0", + "com.docker.network.bridge.name": "docker0", + "com.docker.network.driver.mtu": "1500" + }, + "Scope": "local" + }, + { + "Attachable": false, + "ConfigFrom": { + "Network": "" + }, + "ConfigOnly": false, + "Containers": {}, + "Created": "2022-03-03T23:31:59.297633861+13:00", + "Driver": "null", + "EnableIPv6": false, + "IPAM": { + "Config": [], + "Driver": "default", + "Options": null + }, + "Id": "33c89bff6ff59323ac1c9b10dc7c2920b994b03eca696cfdbabce802f236f6e2", + "Ingress": false, + "Internal": false, + "Labels": {}, + "Name": "none", + "Options": {}, + "Scope": "local" + }, + { + "Attachable": false, + "ConfigFrom": { + "Network": "" + }, + "ConfigOnly": false, + "Containers": {}, + "Created": "2022-03-03T23:31:59.309101595+13:00", + "Driver": "host", + "EnableIPv6": false, + "IPAM": { + "Config": [], + "Driver": "default", + "Options": null + }, + "Id": "968304627a446dbe28a9985cbc2b7b8a0414cbb400cfc6533d26dafc715ff175", + "Ingress": false, + "Internal": false, + "Labels": {}, + "Name": "host", + "Options": {}, + "Scope": "local" + }, + { + "Attachable": false, + "ConfigFrom": { + "Network": "" + }, + "ConfigOnly": false, + "Containers": {}, + "Created": "2022-03-28T10:01:18.600583916+13:00", + "Driver": "bridge", + "EnableIPv6": false, + "IPAM": { + "Config": [ + { + "Gateway": "172.18.0.1", + "Subnet": "172.18.0.0/16" + } + ], + "Driver": "default", + "Options": null + }, + "Id": "817c5491280dd743a47eb32119fe15eb0fc5bff118ae485dcd0d95673fdd55fd", + "Ingress": false, + "Internal": false, + "Labels": {}, + "Name": "docker_gwbridge", + "Options": { + "com.docker.network.bridge.enable_icc": "false", + "com.docker.network.bridge.enable_ip_masquerade": "true", + "com.docker.network.bridge.name": "docker_gwbridge" + }, + "Scope": "local" + }, + { + "Attachable": false, + "ConfigFrom": { + "Network": "" + }, + "ConfigOnly": false, + "Containers": {}, + "Created": "2022-03-30T16:11:59.214342406+13:00", + "Driver": "bridge", + "EnableIPv6": false, + "IPAM": { + "Config": [ + { + "Gateway": "172.19.0.1", + "Subnet": "172.19.0.0/16" + } + ], + "Driver": "default", + "Options": null + }, + "Id": "9fa60f4b6a71b29a95127e99ae0ba09f616386a58ae790f0e8f975e8029f791d", + "Ingress": false, + "Internal": false, + "Labels": {}, + "Name": "redis_default", + "Options": {}, + "Scope": "local" + }, + { + "Attachable": false, + "ConfigFrom": { + "Network": "" + }, + "ConfigOnly": false, + "Containers": {}, + "Created": "2022-03-30T16:12:52.93768265+13:00", + "Driver": "bridge", + "EnableIPv6": false, + "IPAM": { + "Config": [ + { + "Gateway": "172.20.0.1", + "Subnet": "172.20.0.0/16" + } + ], + "Driver": "default", + "Options": null + }, + "Id": "d9576d8c709d65504ca1d0a654cdc7b13ab17ebc6ae051f74e6b124d9d368c9a", + "Ingress": false, + "Internal": false, + "Labels": {}, + "Name": "nginx_default", + "Options": {}, + "Scope": "local" + } + ], + "Version": { + "ApiVersion": "1.41", + "Arch": "amd64", + "BuildTime": "2022-03-10T14:05:44.000000000+00:00", + "Components": [ + { + "Details": { + "ApiVersion": "1.41", + "Arch": "amd64", + "BuildTime": "2022-03-10T14:05:44.000000000+00:00", + "Experimental": "false", + "GitCommit": "906f57f", + "GoVersion": "go1.16.15", + "KernelVersion": "5.13.0-35-generic", + "MinAPIVersion": "1.12", + "Os": "linux" + }, + "Name": "Engine", + "Version": "20.10.13" + }, + { + "Details": { + "GitCommit": "2a1d4dbdb2a1030dc5b01e96fb110a9d9f150ecc" + }, + "Name": "containerd", + "Version": "1.5.10" + }, + { + "Details": { + "GitCommit": "v1.0.3-0-gf46b6ba" + }, + "Name": "runc", + "Version": "1.0.3" + }, + { + "Details": { + "GitCommit": "de40ad0" + }, + "Name": "docker-init", + "Version": "0.19.0" + } + ], + "GitCommit": "906f57f", + "GoVersion": "go1.16.15", + "KernelVersion": "5.13.0-35-generic", + "MinAPIVersion": "1.12", + "Os": "linux", + "Platform": { + "Name": "Docker Engine - Community" + }, + "Version": "20.10.13" + }, + "Volumes": { + "Volumes": [ + { + "CreatedAt": "2022-03-22T19:52:28+13:00", + "Driver": "local", + "Labels": null, + "Mountpoint": "/var/lib/docker/volumes/prabhat/_data", + "Name": "prabhat", + "Options": { + "device": "/home/prabhat/portainer/mounted", + "type": "tmpfs" + }, + "Scope": "local" + }, + { + "CreatedAt": "2022-03-18T13:51:39+13:00", + "Driver": "local", + "Labels": null, + "Mountpoint": "/var/lib/docker/volumes/bc9d2f97a920b6be6869dc69738b1e682234d3b530539986eb5343bcf5572983/_data", + "Name": "bc9d2f97a920b6be6869dc69738b1e682234d3b530539986eb5343bcf5572983", + "Options": null, + "Scope": "local" + }, + { + "CreatedAt": "2022-03-30T16:12:01+13:00", + "Driver": "local", + "Labels": null, + "Mountpoint": "/var/lib/docker/volumes/a3fedc4b90b70e9f28456b4f88f8a2ebd90f76cf8a8a5e4fb5dcbd0b90ff0153/_data", + "Name": "a3fedc4b90b70e9f28456b4f88f8a2ebd90f76cf8a8a5e4fb5dcbd0b90ff0153", + "Options": null, + "Scope": "local" + }, + { + "CreatedAt": "2022-03-30T16:12:01+13:00", + "Driver": "local", + "Labels": null, + "Mountpoint": "/var/lib/docker/volumes/5f93240d96e42d0b3728435cbfb43b6fcb3b01446d5c7d4be1cba8f9336c0a58/_data", + "Name": "5f93240d96e42d0b3728435cbfb43b6fcb3b01446d5c7d4be1cba8f9336c0a58", + "Options": null, + "Scope": "local" + }, + { + "CreatedAt": "2022-03-16T10:55:05+13:00", + "Driver": "local", + "Labels": null, + "Mountpoint": "/var/lib/docker/volumes/f1d6fe0188cc05f24309a86b58cf8130f89fa21aaa73f37789ad36fd188b38e2/_data", + "Name": "f1d6fe0188cc05f24309a86b58cf8130f89fa21aaa73f37789ad36fd188b38e2", + "Options": null, + "Scope": "local" + }, + { + "CreatedAt": "2022-03-09T12:05:37+13:00", + "Driver": "local", + "Labels": null, + "Mountpoint": "/var/lib/docker/volumes/83aea89faf9721ec0065ea37b32608a504088ac86a882069776be3899a0034a5/_data", + "Name": "83aea89faf9721ec0065ea37b32608a504088ac86a882069776be3899a0034a5", + "Options": null, + "Scope": "local" + }, + { + "CreatedAt": "2022-03-11T08:56:01+13:00", + "Driver": "local", + "Labels": null, + "Mountpoint": "/var/lib/docker/volumes/d38856ca6c6879a8c667c300aa6e4c35c2ba93d1a1b06ba78813141fd27b0697/_data", + "Name": "d38856ca6c6879a8c667c300aa6e4c35c2ba93d1a1b06ba78813141fd27b0697", + "Options": null, + "Scope": "local" + }, + { + "CreatedAt": "2022-03-22T22:06:49+13:00", + "Driver": "local", + "Labels": null, + "Mountpoint": "/var/lib/docker/volumes/pk/_data", + "Name": "pk", + "Options": {}, + "Scope": "local" + }, + { + "CreatedAt": "2022-03-22T22:02:46+13:00", + "Driver": "local", + "Labels": {}, + "Mountpoint": "/var/lib/docker/volumes/test-vol/_data", + "Name": "test-vol", + "Options": {}, + "Scope": "local" + }, + { + "CreatedAt": "2022-03-16T10:49:47+13:00", + "Driver": "local", + "Labels": null, + "Mountpoint": "/var/lib/docker/volumes/326d246e9371e4b426b018a7093a522f699a79bf6dda185314ef6683747a5836/_data", + "Name": "326d246e9371e4b426b018a7093a522f699a79bf6dda185314ef6683747a5836", + "Options": null, + "Scope": "local" + } + ], + "Warnings": null + } + }, + "StackCount": 2, + "StoppedContainerCount": 0, + "Swarm": false, + "Time": 1648610112, + "TotalCPU": 8, + "TotalMemory": 25098706944, + "UnhealthyContainerCount": 0, + "VolumeCount": 10 + } + ], + "Status": 1, + "TLSConfig": { + "TLS": false, + "TLSSkipVerify": false + }, + "TagIds": [], + "Tags": null, + "TeamAccessPolicies": {}, + "Type": 1, + "URL": "unix:///var/run/docker.sock", + "UserAccessPolicies": {} + } + ], + "registries": [ + { + "Authentication": true, + "AuthorizedTeams": null, + "AuthorizedUsers": null, + "Gitlab": { + "InstanceURL": "", + "ProjectId": 0, + "ProjectPath": "" + }, + "Id": 1, + "ManagementConfiguration": null, + "Name": "canister.io", + "Password": "MjWbx8A6YK7cw7", + "TeamAccessPolicies": {}, + "Type": 3, + "URL": "cloud.canister.io:5000", + "UserAccessPolicies": {}, + "Username": "prabhatkhera" + } + ], + "resource_control": [ + { + "AdministratorsOnly": false, + "Id": 2, + "Public": true, + "ResourceId": "762gbwaj8r4gcsdy8ld1u4why", + "SubResourceIds": [], + "System": false, + "TeamAccesses": [], + "Type": 5, + "UserAccesses": [] + }, + { + "AdministratorsOnly": false, + "Id": 3, + "Public": true, + "ResourceId": "alpine", + "SubResourceIds": [], + "System": false, + "TeamAccesses": [], + "Type": 6, + "UserAccesses": [] + }, + { + "AdministratorsOnly": false, + "Id": 4, + "Public": true, + "ResourceId": "redis", + "SubResourceIds": [], + "System": false, + "TeamAccesses": [], + "Type": 6, + "UserAccesses": [] + }, + { + "AdministratorsOnly": false, + "Id": 5, + "Public": false, + "ResourceId": "nginx", + "SubResourceIds": [], + "System": false, + "TeamAccesses": [ + { + "AccessLevel": 1, + "TeamId": 1 + } + ], + "Type": 6, + "UserAccesses": [] + } + ], + "roles": [ + { + "Authorizations": { + "DockerAgentBrowseDelete": true, + "DockerAgentBrowseGet": true, + "DockerAgentBrowseList": true, + "DockerAgentBrowsePut": true, + "DockerAgentBrowseRename": true, + "DockerAgentHostInfo": true, + "DockerAgentList": true, + "DockerAgentPing": true, + "DockerAgentUndefined": true, + "DockerBuildCancel": true, + "DockerBuildPrune": true, + "DockerConfigCreate": true, + "DockerConfigDelete": true, + "DockerConfigInspect": true, + "DockerConfigList": true, + "DockerConfigUpdate": true, + "DockerContainerArchive": true, + "DockerContainerArchiveInfo": true, + "DockerContainerAttach": true, + "DockerContainerAttachWebsocket": true, + "DockerContainerChanges": true, + "DockerContainerCreate": true, + "DockerContainerDelete": true, + "DockerContainerExec": true, + "DockerContainerExport": true, + "DockerContainerInspect": true, + "DockerContainerKill": true, + "DockerContainerList": true, + "DockerContainerLogs": true, + "DockerContainerPause": true, + "DockerContainerPrune": true, + "DockerContainerPutContainerArchive": true, + "DockerContainerRename": true, + "DockerContainerResize": true, + "DockerContainerRestart": true, + "DockerContainerStart": true, + "DockerContainerStats": true, + "DockerContainerStop": true, + "DockerContainerTop": true, + "DockerContainerUnpause": true, + "DockerContainerUpdate": true, + "DockerContainerWait": true, + "DockerDistributionInspect": true, + "DockerEvents": true, + "DockerExecInspect": true, + "DockerExecResize": true, + "DockerExecStart": true, + "DockerImageBuild": true, + "DockerImageCommit": true, + "DockerImageCreate": true, + "DockerImageDelete": true, + "DockerImageGet": true, + "DockerImageGetAll": true, + "DockerImageHistory": true, + "DockerImageInspect": true, + "DockerImageList": true, + "DockerImageLoad": true, + "DockerImagePrune": true, + "DockerImagePush": true, + "DockerImageSearch": true, + "DockerImageTag": true, + "DockerInfo": true, + "DockerNetworkConnect": true, + "DockerNetworkCreate": true, + "DockerNetworkDelete": true, + "DockerNetworkDisconnect": true, + "DockerNetworkInspect": true, + "DockerNetworkList": true, + "DockerNetworkPrune": true, + "DockerNodeDelete": true, + "DockerNodeInspect": true, + "DockerNodeList": true, + "DockerNodeUpdate": true, + "DockerPing": true, + "DockerPluginCreate": true, + "DockerPluginDelete": true, + "DockerPluginDisable": true, + "DockerPluginEnable": true, + "DockerPluginInspect": true, + "DockerPluginList": true, + "DockerPluginPrivileges": true, + "DockerPluginPull": true, + "DockerPluginPush": true, + "DockerPluginSet": true, + "DockerPluginUpgrade": true, + "DockerSecretCreate": true, + "DockerSecretDelete": true, + "DockerSecretInspect": true, + "DockerSecretList": true, + "DockerSecretUpdate": true, + "DockerServiceCreate": true, + "DockerServiceDelete": true, + "DockerServiceInspect": true, + "DockerServiceList": true, + "DockerServiceLogs": true, + "DockerServiceUpdate": true, + "DockerSessionStart": true, + "DockerSwarmInit": true, + "DockerSwarmInspect": true, + "DockerSwarmJoin": true, + "DockerSwarmLeave": true, + "DockerSwarmUnlock": true, + "DockerSwarmUnlockKey": true, + "DockerSwarmUpdate": true, + "DockerSystem": true, + "DockerTaskInspect": true, + "DockerTaskList": true, + "DockerTaskLogs": true, + "DockerUndefined": true, + "DockerVersion": true, + "DockerVolumeCreate": true, + "DockerVolumeDelete": true, + "DockerVolumeInspect": true, + "DockerVolumeList": true, + "DockerVolumePrune": true, + "EndpointResourcesAccess": true, + "IntegrationStoridgeAdmin": true, + "PortainerResourceControlCreate": true, + "PortainerResourceControlUpdate": true, + "PortainerStackCreate": true, + "PortainerStackDelete": true, + "PortainerStackFile": true, + "PortainerStackInspect": true, + "PortainerStackList": true, + "PortainerStackMigrate": true, + "PortainerStackUpdate": true, + "PortainerWebhookCreate": true, + "PortainerWebhookDelete": true, + "PortainerWebhookList": true, + "PortainerWebsocketExec": true + }, + "Description": "Full control of all resources in an endpoint", + "Id": 1, + "Name": "Endpoint administrator", + "Priority": 1 + }, + { + "Authorizations": { + "DockerAgentHostInfo": true, + "DockerAgentList": true, + "DockerAgentPing": true, + "DockerConfigInspect": true, + "DockerConfigList": true, + "DockerContainerArchiveInfo": true, + "DockerContainerChanges": true, + "DockerContainerInspect": true, + "DockerContainerList": true, + "DockerContainerLogs": true, + "DockerContainerStats": true, + "DockerContainerTop": true, + "DockerDistributionInspect": true, + "DockerEvents": true, + "DockerImageGet": true, + "DockerImageGetAll": true, + "DockerImageHistory": true, + "DockerImageInspect": true, + "DockerImageList": true, + "DockerImageSearch": true, + "DockerInfo": true, + "DockerNetworkInspect": true, + "DockerNetworkList": true, + "DockerNodeInspect": true, + "DockerNodeList": true, + "DockerPing": true, + "DockerPluginList": true, + "DockerSecretInspect": true, + "DockerSecretList": true, + "DockerServiceInspect": true, + "DockerServiceList": true, + "DockerServiceLogs": true, + "DockerSwarmInspect": true, + "DockerSystem": true, + "DockerTaskInspect": true, + "DockerTaskList": true, + "DockerTaskLogs": true, + "DockerVersion": true, + "DockerVolumeInspect": true, + "DockerVolumeList": true, + "EndpointResourcesAccess": true, + "PortainerStackFile": true, + "PortainerStackInspect": true, + "PortainerStackList": true, + "PortainerWebhookList": true + }, + "Description": "Read-only access of all resources in an endpoint", + "Id": 2, + "Name": "Helpdesk", + "Priority": 2 + }, + { + "Authorizations": { + "DockerAgentHostInfo": true, + "DockerAgentList": true, + "DockerAgentPing": true, + "DockerAgentUndefined": true, + "DockerBuildCancel": true, + "DockerBuildPrune": true, + "DockerConfigCreate": true, + "DockerConfigDelete": true, + "DockerConfigInspect": true, + "DockerConfigList": true, + "DockerConfigUpdate": true, + "DockerContainerArchive": true, + "DockerContainerArchiveInfo": true, + "DockerContainerAttach": true, + "DockerContainerAttachWebsocket": true, + "DockerContainerChanges": true, + "DockerContainerCreate": true, + "DockerContainerDelete": true, + "DockerContainerExec": true, + "DockerContainerExport": true, + "DockerContainerInspect": true, + "DockerContainerKill": true, + "DockerContainerList": true, + "DockerContainerLogs": true, + "DockerContainerPause": true, + "DockerContainerPutContainerArchive": true, + "DockerContainerRename": true, + "DockerContainerResize": true, + "DockerContainerRestart": true, + "DockerContainerStart": true, + "DockerContainerStats": true, + "DockerContainerStop": true, + "DockerContainerTop": true, + "DockerContainerUnpause": true, + "DockerContainerUpdate": true, + "DockerContainerWait": true, + "DockerDistributionInspect": true, + "DockerEvents": true, + "DockerExecInspect": true, + "DockerExecResize": true, + "DockerExecStart": true, + "DockerImageBuild": true, + "DockerImageCommit": true, + "DockerImageCreate": true, + "DockerImageDelete": true, + "DockerImageGet": true, + "DockerImageGetAll": true, + "DockerImageHistory": true, + "DockerImageInspect": true, + "DockerImageList": true, + "DockerImageLoad": true, + "DockerImagePush": true, + "DockerImageSearch": true, + "DockerImageTag": true, + "DockerInfo": true, + "DockerNetworkConnect": true, + "DockerNetworkCreate": true, + "DockerNetworkDelete": true, + "DockerNetworkDisconnect": true, + "DockerNetworkInspect": true, + "DockerNetworkList": true, + "DockerNodeDelete": true, + "DockerNodeInspect": true, + "DockerNodeList": true, + "DockerNodeUpdate": true, + "DockerPing": true, + "DockerPluginCreate": true, + "DockerPluginDelete": true, + "DockerPluginDisable": true, + "DockerPluginEnable": true, + "DockerPluginInspect": true, + "DockerPluginList": true, + "DockerPluginPrivileges": true, + "DockerPluginPull": true, + "DockerPluginPush": true, + "DockerPluginSet": true, + "DockerPluginUpgrade": true, + "DockerSecretCreate": true, + "DockerSecretDelete": true, + "DockerSecretInspect": true, + "DockerSecretList": true, + "DockerSecretUpdate": true, + "DockerServiceCreate": true, + "DockerServiceDelete": true, + "DockerServiceInspect": true, + "DockerServiceList": true, + "DockerServiceLogs": true, + "DockerServiceUpdate": true, + "DockerSessionStart": true, + "DockerSwarmInit": true, + "DockerSwarmInspect": true, + "DockerSwarmJoin": true, + "DockerSwarmLeave": true, + "DockerSwarmUnlock": true, + "DockerSwarmUnlockKey": true, + "DockerSwarmUpdate": true, + "DockerSystem": true, + "DockerTaskInspect": true, + "DockerTaskList": true, + "DockerTaskLogs": true, + "DockerUndefined": true, + "DockerVersion": true, + "DockerVolumeCreate": true, + "DockerVolumeDelete": true, + "DockerVolumeInspect": true, + "DockerVolumeList": true, + "PortainerResourceControlUpdate": true, + "PortainerStackCreate": true, + "PortainerStackDelete": true, + "PortainerStackFile": true, + "PortainerStackInspect": true, + "PortainerStackList": true, + "PortainerStackMigrate": true, + "PortainerStackUpdate": true, + "PortainerWebhookCreate": true, + "PortainerWebhookList": true, + "PortainerWebsocketExec": true + }, + "Description": "Full control of assigned resources in an endpoint", + "Id": 3, + "Name": "Standard user", + "Priority": 3 + }, + { + "Authorizations": { + "DockerAgentHostInfo": true, + "DockerAgentList": true, + "DockerAgentPing": true, + "DockerConfigInspect": true, + "DockerConfigList": true, + "DockerContainerArchiveInfo": true, + "DockerContainerChanges": true, + "DockerContainerInspect": true, + "DockerContainerList": true, + "DockerContainerLogs": true, + "DockerContainerStats": true, + "DockerContainerTop": true, + "DockerDistributionInspect": true, + "DockerEvents": true, + "DockerImageGet": true, + "DockerImageGetAll": true, + "DockerImageHistory": true, + "DockerImageInspect": true, + "DockerImageList": true, + "DockerImageSearch": true, + "DockerInfo": true, + "DockerNetworkInspect": true, + "DockerNetworkList": true, + "DockerNodeInspect": true, + "DockerNodeList": true, + "DockerPing": true, + "DockerPluginList": true, + "DockerSecretInspect": true, + "DockerSecretList": true, + "DockerServiceInspect": true, + "DockerServiceList": true, + "DockerServiceLogs": true, + "DockerSwarmInspect": true, + "DockerSystem": true, + "DockerTaskInspect": true, + "DockerTaskList": true, + "DockerTaskLogs": true, + "DockerVersion": true, + "DockerVolumeInspect": true, + "DockerVolumeList": true, + "PortainerStackFile": true, + "PortainerStackInspect": true, + "PortainerStackList": true, + "PortainerWebhookList": true + }, + "Description": "Read-only access of assigned resources in an endpoint", + "Id": 4, + "Name": "Read-only user", + "Priority": 4 + } + ], + "schedules": [ + { + "Created": 1648608136, + "CronExpression": "@every 5m", + "EdgeSchedule": null, + "EndpointSyncJob": null, + "Id": 1, + "JobType": 2, + "Name": "system_snapshot", + "Recurring": true, + "ScriptExecutionJob": null, + "SnapshotJob": {} + } + ], + "settings": { + "AllowBindMountsForRegularUsers": true, + "AllowContainerCapabilitiesForRegularUsers": true, + "AllowDeviceMappingForRegularUsers": true, + "AllowHostNamespaceForRegularUsers": true, + "AllowPrivilegedModeForRegularUsers": true, + "AllowStackManagementForRegularUsers": true, + "AllowVolumeBrowserForRegularUsers": false, + "AuthenticationMethod": 1, + "BlackListedLabels": [], + "DisplayDonationHeader": false, + "DisplayExternalContributors": false, + "EdgeAgentCheckinInterval": 5, + "EnableEdgeComputeFeatures": false, + "EnableHostManagementFeatures": false, + "LDAPSettings": { + "AnonymousMode": true, + "AutoCreateUsers": true, + "GroupSearchSettings": [ + { + "GroupAttribute": "", + "GroupBaseDN": "", + "GroupFilter": "" + } + ], + "ReaderDN": "", + "SearchSettings": [ + { + "BaseDN": "", + "Filter": "", + "UserNameAttribute": "" + } + ], + "StartTLS": false, + "TLSConfig": { + "TLS": false, + "TLSSkipVerify": false + }, + "URL": "" + }, + "LogoURL": "", + "OAuthSettings": { + "AccessTokenURI": "", + "AuthorizationURI": "", + "ClientID": "", + "DefaultTeamID": 0, + "OAuthAutoCreateUsers": false, + "RedirectURI": "", + "ResourceURI": "", + "Scopes": "", + "UserIdentifier": "" + }, + "SnapshotInterval": "5m", + "TemplatesURL": "" + }, + "stacks": [ + { + "EndpointId": 1, + "EntryPoint": "docker/alpine37-compose.yml", + "Env": [], + "Id": 2, + "Name": "alpine", + "ProjectPath": "/home/prabhat/portainer/data/ce1.25/compose/2", + "ResourceControl": null, + "SwarmId": "s3fd604zdba7z13tbq2x6lyue", + "Type": 1 + }, + { + "EndpointId": 1, + "EntryPoint": "docker-compose.yml", + "Env": [], + "Id": 5, + "Name": "redis", + "ProjectPath": "/home/prabhat/portainer/data/ce1.25/compose/5", + "ResourceControl": null, + "SwarmId": "", + "Type": 2 + }, + { + "EndpointId": 1, + "EntryPoint": "docker-compose.yml", + "Env": [], + "Id": 6, + "Name": "nginx", + "ProjectPath": "/home/prabhat/portainer/data/ce1.25/compose/6", + "ResourceControl": null, + "SwarmId": "", + "Type": 2 + } + ], + "teams": [ + { + "Id": 1, + "Name": "hello" + } + ], + "templates": [ + { + "Id": 1, + "administrator_only": false, + "categories": ["docker"], + "description": "Docker image registry", + "image": "registry:latest", + "logo": "https://portainer-io-assets.sfo2.digitaloceanspaces.com/logos/registry.png", + "platform": "linux", + "ports": ["5000/tcp"], + "repository": { + "stackfile": "", + "url": "" + }, + "stackFile": "", + "title": "Registry", + "type": 1, + "volumes": [ + { + "container": "/var/lib/registry" + } + ] + }, + { + "Id": 2, + "administrator_only": false, + "categories": ["webserver"], + "description": "High performance web server", + "image": "nginx:latest", + "logo": "https://portainer-io-assets.sfo2.digitaloceanspaces.com/logos/nginx.png", + "platform": "linux", + "ports": ["80/tcp", "443/tcp"], + "repository": { + "stackfile": "", + "url": "" + }, + "stackFile": "", + "title": "Nginx", + "type": 1, + "volumes": [ + { + "container": "/etc/nginx" + }, + { + "container": "/usr/share/nginx/html" + } + ] + }, + { + "Id": 3, + "administrator_only": false, + "categories": ["webserver"], + "description": "Open-source HTTP server", + "image": "httpd:latest", + "logo": "https://portainer-io-assets.sfo2.digitaloceanspaces.com/logos/httpd.png", + "platform": "linux", + "ports": ["80/tcp"], + "repository": { + "stackfile": "", + "url": "" + }, + "stackFile": "", + "title": "Httpd", + "type": 1, + "volumes": [ + { + "container": "/usr/local/apache2/htdocs/" + } + ] + }, + { + "Id": 4, + "administrator_only": false, + "categories": ["webserver"], + "description": "HTTP/2 web server with automatic HTTPS", + "image": "abiosoft/caddy:latest", + "logo": "https://portainer-io-assets.sfo2.digitaloceanspaces.com/logos/caddy.png", + "platform": "linux", + "ports": ["80/tcp", "443/tcp", "2015/tcp"], + "repository": { + "stackfile": "", + "url": "" + }, + "stackFile": "", + "title": "Caddy", + "type": 1, + "volumes": [ + { + "container": "/root/.caddy" + } + ] + }, + { + "Id": 5, + "administrator_only": false, + "categories": ["database"], + "description": "The most popular open-source database", + "env": [ + { + "label": "Root password", + "name": "MYSQL_ROOT_PASSWORD" + } + ], + "image": "mysql:latest", + "logo": "https://portainer-io-assets.sfo2.digitaloceanspaces.com/logos/mysql.png", + "platform": "linux", + "ports": ["3306/tcp"], + "repository": { + "stackfile": "", + "url": "" + }, + "stackFile": "", + "title": "MySQL", + "type": 1, + "volumes": [ + { + "container": "/var/lib/mysql" + } + ] + }, + { + "Id": 6, + "administrator_only": false, + "categories": ["database"], + "description": "Performance beyond MySQL", + "env": [ + { + "label": "Root password", + "name": "MYSQL_ROOT_PASSWORD" + } + ], + "image": "mariadb:latest", + "logo": "https://portainer-io-assets.sfo2.digitaloceanspaces.com/logos/mariadb.png", + "platform": "linux", + "ports": ["3306/tcp"], + "repository": { + "stackfile": "", + "url": "" + }, + "stackFile": "", + "title": "MariaDB", + "type": 1, + "volumes": [ + { + "container": "/var/lib/mysql" + } + ] + }, + { + "Id": 7, + "administrator_only": false, + "categories": ["database"], + "description": "The most advanced open-source database", + "env": [ + { + "label": "Superuser", + "name": "POSTGRES_USER" + }, + { + "label": "Superuser password", + "name": "POSTGRES_PASSWORD" + } + ], + "image": "postgres:latest", + "logo": "https://portainer-io-assets.sfo2.digitaloceanspaces.com/logos/postgres.png", + "platform": "linux", + "ports": ["5432/tcp"], + "repository": { + "stackfile": "", + "url": "" + }, + "stackFile": "", + "title": "PostgreSQL", + "type": 1, + "volumes": [ + { + "container": "/var/lib/postgresql/data" + } + ] + }, + { + "Id": 8, + "administrator_only": false, + "categories": ["database"], + "description": "Open-source document-oriented database", + "image": "mongo:latest", + "logo": "https://portainer-io-assets.sfo2.digitaloceanspaces.com/logos/mongo.png", + "platform": "linux", + "ports": ["27017/tcp"], + "repository": { + "stackfile": "", + "url": "" + }, + "stackFile": "", + "title": "Mongo", + "type": 1, + "volumes": [ + { + "container": "/data/db" + } + ] + }, + { + "Id": 9, + "administrator_only": false, + "categories": ["database"], + "command": "start --insecure", + "description": "An open-source, survivable, strongly consistent, scale-out SQL database", + "image": "cockroachdb/cockroach:latest", + "logo": "https://portainer-io-assets.sfo2.digitaloceanspaces.com/logos/cockroachdb.png", + "platform": "linux", + "ports": ["26257/tcp", "8080/tcp"], + "repository": { + "stackfile": "", + "url": "" + }, + "stackFile": "", + "title": "CockroachDB", + "type": 1, + "volumes": [ + { + "container": "/cockroach/cockroach-data" + } + ] + }, + { + "Id": 10, + "administrator_only": false, + "categories": ["database"], + "description": "An open-source distributed SQL database", + "image": "crate:latest", + "logo": "https://portainer-io-assets.sfo2.digitaloceanspaces.com/logos/cratedb.png", + "platform": "linux", + "ports": ["4200/tcp", "4300/tcp"], + "repository": { + "stackfile": "", + "url": "" + }, + "stackFile": "", + "title": "CrateDB", + "type": 1, + "volumes": [ + { + "container": "/data" + } + ] + }, + { + "Id": 11, + "administrator_only": false, + "categories": ["database"], + "description": "Open-source search and analytics engine", + "image": "elasticsearch:latest", + "logo": "https://portainer-io-assets.sfo2.digitaloceanspaces.com/logos/elasticsearch.png", + "platform": "linux", + "ports": ["9200/tcp", "9300/tcp"], + "repository": { + "stackfile": "", + "url": "" + }, + "stackFile": "", + "title": "Elasticsearch", + "type": 1, + "volumes": [ + { + "container": "/usr/share/elasticsearch/data" + } + ] + }, + { + "Id": 12, + "administrator_only": false, + "categories": ["development", "project-management"], + "description": "Open-source end-to-end software development platform", + "image": "gitlab/gitlab-ce:latest", + "logo": "https://portainer-io-assets.sfo2.digitaloceanspaces.com/logos/gitlab_ce.png", + "note": "Default username is \u003cb\u003eroot\u003c/b\u003e. Check the \u003ca href=\"https://docs.gitlab.com/omnibus/docker/README.html#after-starting-a-container\" target=\"_blank\"\u003eGitlab documentation\u003c/a\u003e to get started.", + "platform": "linux", + "ports": ["80/tcp", "443/tcp", "22/tcp"], + "repository": { + "stackfile": "", + "url": "" + }, + "stackFile": "", + "title": "Gitlab CE", + "type": 1, + "volumes": [ + { + "container": "/etc/gitlab" + }, + { + "container": "/var/log/gitlab" + }, + { + "container": "/var/opt/gitlab" + } + ] + }, + { + "Id": 13, + "administrator_only": false, + "categories": ["storage"], + "command": "server /data", + "description": "A distributed object storage server built for cloud applications and devops", + "env": [ + { + "label": "Minio access key", + "name": "MINIO_ACCESS_KEY" + }, + { + "label": "Minio secret key", + "name": "MINIO_SECRET_KEY" + } + ], + "image": "minio/minio:latest", + "logo": "https://portainer-io-assets.sfo2.digitaloceanspaces.com/logos/minio.png", + "platform": "linux", + "ports": ["9000/tcp"], + "repository": { + "stackfile": "", + "url": "" + }, + "stackFile": "", + "title": "Minio", + "type": 1, + "volumes": [ + { + "container": "/data" + }, + { + "container": "/root/.minio" + } + ] + }, + { + "Id": 14, + "administrator_only": false, + "categories": ["storage"], + "description": "Standalone AWS S3 protocol server", + "env": [ + { + "label": "Scality S3 access key", + "name": "SCALITY_ACCESS_KEY" + }, + { + "label": "Scality S3 secret key", + "name": "SCALITY_SECRET_KEY" + } + ], + "image": "scality/s3server", + "logo": "https://portainer-io-assets.sfo2.digitaloceanspaces.com/logos/scality-s3.png", + "platform": "linux", + "ports": ["8000/tcp"], + "repository": { + "stackfile": "", + "url": "" + }, + "stackFile": "", + "title": "Scality S3", + "type": 1, + "volumes": [ + { + "container": "/usr/src/app/localData" + }, + { + "container": "/usr/src/app/localMetadata" + } + ] + }, + { + "Id": 15, + "administrator_only": false, + "categories": ["database"], + "description": "Microsoft SQL Server on Linux", + "env": [ + { + "name": "ACCEPT_EULA" + }, + { + "label": "SA password", + "name": "SA_PASSWORD" + } + ], + "image": "microsoft/mssql-server-linux:2017-GA", + "logo": "https://portainer-io-assets.sfo2.digitaloceanspaces.com/logos/microsoft.png", + "note": "Password needs to include at least 8 characters including uppercase, lowercase letters, base-10 digits and/or non-alphanumeric symbols.", + "platform": "linux", + "ports": ["1433/tcp"], + "repository": { + "stackfile": "", + "url": "" + }, + "stackFile": "", + "title": "SQL Server", + "type": 1 + }, + { + "Id": 16, + "administrator_only": false, + "categories": ["database"], + "description": "Microsoft SQL Server Developer for Windows containers", + "env": [ + { + "name": "ACCEPT_EULA" + }, + { + "label": "SA password", + "name": "sa_password" + } + ], + "image": "microsoft/mssql-server-windows-developer:latest", + "logo": "https://portainer-io-assets.sfo2.digitaloceanspaces.com/logos/microsoft.png", + "note": "Password needs to include at least 8 characters including uppercase, lowercase letters, base-10 digits and/or non-alphanumeric symbols.", + "platform": "windows", + "ports": ["1433/tcp"], + "repository": { + "stackfile": "", + "url": "" + }, + "stackFile": "", + "title": "SQL Server", + "type": 1, + "volumes": [ + { + "container": "C:/temp/" + } + ] + }, + { + "Id": 17, + "administrator_only": false, + "categories": ["database"], + "description": "Microsoft SQL Server Express for Windows containers", + "env": [ + { + "name": "ACCEPT_EULA" + }, + { + "label": "SA password", + "name": "sa_password" + } + ], + "image": "microsoft/mssql-server-windows-express:latest", + "logo": "https://portainer-io-assets.sfo2.digitaloceanspaces.com/logos/microsoft.png", + "note": "Password needs to include at least 8 characters including uppercase, lowercase letters, base-10 digits and/or non-alphanumeric symbols.", + "platform": "windows", + "ports": ["1433/tcp"], + "repository": { + "stackfile": "", + "url": "" + }, + "stackFile": "", + "title": "SQL Server Express", + "type": 1, + "volumes": [ + { + "container": "C:/temp/" + } + ] + }, + { + "Id": 18, + "administrator_only": false, + "categories": ["serverless"], + "description": "Open-source serverless computing platform", + "image": "iron/functions:latest", + "logo": "https://portainer-io-assets.sfo2.digitaloceanspaces.com/logos/ironfunctions.png", + "platform": "linux", + "ports": ["8080/tcp"], + "privileged": true, + "repository": { + "stackfile": "", + "url": "" + }, + "stackFile": "", + "title": "IronFunctions API", + "type": 1, + "volumes": [ + { + "container": "/app/data" + } + ] + }, + { + "Id": 19, + "administrator_only": false, + "categories": ["serverless"], + "description": "Open-source user interface for IronFunctions", + "env": [ + { + "label": "API URL", + "name": "API_URL" + } + ], + "image": "iron/functions-ui:latest", + "logo": "https://portainer-io-assets.sfo2.digitaloceanspaces.com/logos/ironfunctions.png", + "platform": "linux", + "ports": ["4000/tcp"], + "privileged": true, + "repository": { + "stackfile": "", + "url": "" + }, + "stackFile": "", + "title": "IronFunctions UI", + "type": 1, + "volumes": [ + { + "container": "/app/data" + } + ] + }, + { + "Id": 20, + "administrator_only": false, + "categories": ["search-engine"], + "description": "Open-source enterprise search platform", + "image": "solr:latest", + "logo": "https://portainer-io-assets.sfo2.digitaloceanspaces.com/logos/solr.png", + "platform": "linux", + "ports": ["8983/tcp"], + "repository": { + "stackfile": "", + "url": "" + }, + "stackFile": "", + "title": "Solr", + "type": 1, + "volumes": [ + { + "container": "/opt/solr/mydata" + } + ] + }, + { + "Id": 21, + "administrator_only": false, + "categories": ["database"], + "description": "Open-source in-memory data structure store", + "image": "redis:latest", + "logo": "https://portainer-io-assets.sfo2.digitaloceanspaces.com/logos/redis.png", + "platform": "linux", + "ports": ["6379/tcp"], + "repository": { + "stackfile": "", + "url": "" + }, + "stackFile": "", + "title": "Redis", + "type": 1, + "volumes": [ + { + "container": "/data" + } + ] + }, + { + "Id": 22, + "administrator_only": false, + "categories": ["messaging"], + "description": "Highly reliable enterprise messaging system", + "image": "rabbitmq:latest", + "logo": "https://portainer-io-assets.sfo2.digitaloceanspaces.com/logos/rabbitmq.png", + "platform": "linux", + "ports": ["5671/tcp", "5672/tcp"], + "repository": { + "stackfile": "", + "url": "" + }, + "stackFile": "", + "title": "RabbitMQ", + "type": 1, + "volumes": [ + { + "container": "/var/lib/rabbitmq" + } + ] + }, + { + "Id": 23, + "administrator_only": false, + "categories": ["blog"], + "description": "Free and open-source blogging platform", + "image": "ghost:latest", + "logo": "https://portainer-io-assets.sfo2.digitaloceanspaces.com/logos/ghost.png", + "note": "Access the blog management interface under \u003ccode\u003e/ghost/\u003c/code\u003e.", + "platform": "linux", + "ports": ["2368/tcp"], + "repository": { + "stackfile": "", + "url": "" + }, + "stackFile": "", + "title": "Ghost", + "type": 1, + "volumes": [ + { + "container": "/var/lib/ghost/content" + } + ] + }, + { + "Id": 24, + "administrator_only": false, + "categories": ["CMS"], + "description": "WebOps platform and hosting control panel", + "image": "plesk/plesk:preview", + "logo": "https://portainer-io-assets.sfo2.digitaloceanspaces.com/logos/plesk.png", + "note": "Default credentials: admin / changeme", + "platform": "linux", + "ports": ["21/tcp", "80/tcp", "443/tcp", "8880/tcp", "8443/tcp", "8447/tcp"], + "repository": { + "stackfile": "", + "url": "" + }, + "stackFile": "", + "title": "Plesk", + "type": 1 + }, + { + "Id": 25, + "administrator_only": false, + "categories": ["CMS"], + "description": "Another free and open-source CMS", + "env": [ + { + "label": "MySQL database host", + "name": "JOOMLA_DB_HOST" + }, + { + "label": "Database password", + "name": "JOOMLA_DB_PASSWORD" + } + ], + "image": "joomla:latest", + "logo": "https://portainer-io-assets.sfo2.digitaloceanspaces.com/logos/joomla.png", + "platform": "linux", + "ports": ["80/tcp"], + "repository": { + "stackfile": "", + "url": "" + }, + "stackFile": "", + "title": "Joomla", + "type": 1, + "volumes": [ + { + "container": "/var/www/html" + } + ] + }, + { + "Id": 26, + "administrator_only": false, + "categories": ["CMS"], + "description": "Open-source content management framework", + "image": "drupal:latest", + "logo": "https://portainer-io-assets.sfo2.digitaloceanspaces.com/logos/drupal.png", + "platform": "linux", + "ports": ["80/tcp"], + "repository": { + "stackfile": "", + "url": "" + }, + "stackFile": "", + "title": "Drupal", + "type": 1, + "volumes": [ + { + "container": "/var/www/html" + } + ] + }, + { + "Id": 27, + "administrator_only": false, + "categories": ["CMS"], + "description": "A free and open-source CMS built on top of Zope", + "image": "plone:latest", + "logo": "https://portainer-io-assets.sfo2.digitaloceanspaces.com/logos/plone.png", + "platform": "linux", + "ports": ["8080/tcp"], + "repository": { + "stackfile": "", + "url": "" + }, + "stackFile": "", + "title": "Plone", + "type": 1, + "volumes": [ + { + "container": "/data" + } + ] + }, + { + "Id": 28, + "administrator_only": false, + "categories": ["CMS"], + "description": "Open-source e-commerce platform", + "image": "alankent/gsd:latest", + "logo": "https://portainer-io-assets.sfo2.digitaloceanspaces.com/logos/magento.png", + "platform": "linux", + "ports": ["80/tcp", "3000/tcp", "3001/tcp"], + "repository": { + "stackfile": "", + "url": "" + }, + "stackFile": "", + "title": "Magento 2", + "type": 1, + "volumes": [ + { + "container": "/var/www/html/" + } + ] + }, + { + "Id": 29, + "administrator_only": false, + "categories": ["Log Management", "Monitoring"], + "description": "Collect logs, metrics and docker events", + "env": [ + { + "label": "Logs token", + "name": "LOGSENE_TOKEN" + }, + { + "label": "SPM monitoring token", + "name": "SPM_TOKEN" + } + ], + "image": "sematext/sematext-agent-docker:latest", + "logo": "https://portainer-io-assets.sfo2.digitaloceanspaces.com/logos/sematext_agent.png", + "name": "sematext-agent", + "platform": "linux", + "privileged": true, + "repository": { + "stackfile": "", + "url": "" + }, + "stackFile": "", + "title": "Sematext Docker Agent", + "type": 1, + "volumes": [ + { + "bind": "/var/run/docker.sock", + "container": "/var/run/docker.sock" + } + ] + }, + { + "Id": 30, + "administrator_only": false, + "categories": ["Monitoring"], + "description": "Collect events and metrics", + "env": [ + { + "label": "Datadog API key", + "name": "DD_API_KEY" + } + ], + "image": "datadog/agent:latest", + "logo": "https://portainer-io-assets.sfo2.digitaloceanspaces.com/logos/datadog_agent.png", + "platform": "linux", + "repository": { + "stackfile": "", + "url": "" + }, + "stackFile": "", + "title": "Datadog agent", + "type": 1, + "volumes": [ + { + "bind": "/var/run/docker.sock", + "container": "/var/run/docker.sock", + "readonly": true + }, + { + "bind": "/sys/fs/cgroup", + "container": "/host/sys/fs/cgroup", + "readonly": true + }, + { + "bind": "/proc", + "container": "/host/proc", + "readonly": true + } + ] + }, + { + "Id": 31, + "administrator_only": false, + "categories": ["marketing"], + "description": "Open-source marketing automation platform", + "env": [ + { + "label": "MySQL database host", + "name": "MAUTIC_DB_HOST" + }, + { + "label": "Database password", + "name": "MAUTIC_DB_PASSWORD" + } + ], + "image": "mautic/mautic:latest", + "logo": "https://portainer-io-assets.sfo2.digitaloceanspaces.com/logos/mautic.png", + "platform": "linux", + "ports": ["80/tcp"], + "repository": { + "stackfile": "", + "url": "" + }, + "stackFile": "", + "title": "Mautic", + "type": 1, + "volumes": [ + { + "container": "/var/www/html" + } + ] + }, + { + "Id": 32, + "administrator_only": false, + "categories": ["streaming"], + "description": "Streaming media server", + "env": [ + { + "label": "Agree to Wowza EULA", + "name": "WOWZA_ACCEPT_LICENSE" + }, + { + "label": "License key", + "name": "WOWZA_KEY" + } + ], + "image": "sameersbn/wowza:4.1.2-8", + "logo": "https://portainer-io-assets.sfo2.digitaloceanspaces.com/logos/wowza.png", + "platform": "linux", + "ports": ["1935/tcp", "8086/tcp", "8087/tcp", "8088/tcp"], + "repository": { + "stackfile": "", + "url": "" + }, + "stackFile": "", + "title": "Wowza", + "type": 1, + "volumes": [ + { + "container": "/var/lib/wowza" + } + ] + }, + { + "Id": 33, + "administrator_only": false, + "categories": ["continuous-integration"], + "description": "Open-source continuous integration tool", + "env": [ + { + "label": "Jenkins options", + "name": "JENKINS_OPTS" + } + ], + "image": "jenkins/jenkins:lts", + "logo": "https://portainer-io-assets.sfo2.digitaloceanspaces.com/logos/jenkins.png", + "platform": "linux", + "ports": ["8080/tcp", "50000/tcp"], + "repository": { + "stackfile": "", + "url": "" + }, + "stackFile": "", + "title": "Jenkins", + "type": 1, + "volumes": [ + { + "container": "/var/jenkins_home" + } + ] + }, + { + "Id": 34, + "administrator_only": false, + "categories": ["project-management"], + "description": "Open-source project management tool", + "image": "redmine:latest", + "logo": "https://portainer-io-assets.sfo2.digitaloceanspaces.com/logos/redmine.png", + "platform": "linux", + "ports": ["3000/tcp"], + "repository": { + "stackfile": "", + "url": "" + }, + "stackFile": "", + "title": "Redmine", + "type": 1, + "volumes": [ + { + "container": "/usr/src/redmine/files" + } + ] + }, + { + "Id": 35, + "administrator_only": false, + "categories": ["project-management"], + "description": "Open-source business apps", + "env": [ + { + "label": "PostgreSQL database host", + "name": "HOST" + }, + { + "label": "Database user", + "name": "USER" + }, + { + "label": "Database password", + "name": "PASSWORD" + } + ], + "image": "odoo:latest", + "logo": "https://portainer-io-assets.sfo2.digitaloceanspaces.com/logos/odoo.png", + "platform": "linux", + "ports": ["8069/tcp"], + "repository": { + "stackfile": "", + "url": "" + }, + "stackFile": "", + "title": "Odoo", + "type": 1, + "volumes": [ + { + "container": "/var/lib/odoo" + }, + { + "container": "/mnt/extra-addons" + } + ] + }, + { + "Id": 36, + "administrator_only": false, + "categories": ["backup"], + "description": "Open-source network backup", + "image": "cfstras/urbackup", + "logo": "https://portainer-io-assets.sfo2.digitaloceanspaces.com/logos/urbackup.png", + "note": "This application web interface is exposed on the port 55414 inside the container.", + "platform": "linux", + "ports": ["55413/tcp", "55414/tcp", "55415/tcp", "35622/tcp"], + "repository": { + "stackfile": "", + "url": "" + }, + "stackFile": "", + "title": "Urbackup", + "type": 1, + "volumes": [ + { + "container": "/var/urbackup" + } + ] + }, + { + "Id": 37, + "administrator_only": false, + "categories": ["filesystem", "storage"], + "command": "--port 80 --database /data/database.db --scope /srv", + "description": "A web file manager", + "image": "filebrowser/filebrowser:latest", + "logo": "https://portainer-io-assets.sfo2.digitaloceanspaces.com/logos/filebrowser.png", + "note": "Default credentials: admin/admin", + "platform": "linux", + "ports": ["80/tcp"], + "repository": { + "stackfile": "", + "url": "" + }, + "stackFile": "", + "title": "File browser", + "type": 1, + "volumes": [ + { + "container": "/data" + }, + { + "container": "/srv" + } + ] + }, + { + "Id": 38, + "administrator_only": false, + "categories": ["development"], + "description": "ColdFusion (CFML) CLI", + "env": [ + { + "name": "CFENGINE" + } + ], + "image": "ortussolutions/commandbox:latest", + "logo": "https://portainer-io-assets.sfo2.digitaloceanspaces.com/logos/ortussolutions-commandbox.png", + "platform": "linux", + "ports": ["8080/tcp", "8443/tcp"], + "repository": { + "stackfile": "", + "url": "" + }, + "stackFile": "", + "title": "CommandBox", + "type": 1 + }, + { + "Id": 39, + "administrator_only": false, + "categories": ["CMS"], + "description": "Open-source modular CMS", + "env": [ + { + "name": "express" + }, + { + "name": "install" + }, + { + "name": "CFENGINE" + } + ], + "image": "ortussolutions/contentbox:latest", + "logo": "https://portainer-io-assets.sfo2.digitaloceanspaces.com/logos/ortussolutions-contentbox.png", + "platform": "linux", + "ports": ["8080/tcp", "8443/tcp"], + "repository": { + "stackfile": "", + "url": "" + }, + "stackFile": "", + "title": "ContentBox", + "type": 1, + "volumes": [ + { + "container": "/data/contentbox/db" + }, + { + "container": "/app/includes/shared/media" + } + ] + }, + { + "Id": 40, + "administrator_only": false, + "categories": ["portainer"], + "description": "Manage all the resources in your Swarm cluster", + "image": "", + "logo": "https://portainer-io-assets.sfo2.digitaloceanspaces.com/logos/portainer.png", + "note": "The agent will be deployed globally inside your cluster and available on port 9001.", + "platform": "linux", + "repository": { + "stackfile": "stacks/portainer-agent/docker-stack.yml", + "url": "https://github.com/portainer/templates" + }, + "stackFile": "", + "title": "Portainer Agent", + "type": 2 + }, + { + "Id": 41, + "administrator_only": false, + "categories": ["serverless"], + "description": "Serverless functions made simple", + "image": "", + "logo": "https://portainer-io-assets.sfo2.digitaloceanspaces.com/logos/openfaas.png", + "name": "func", + "note": "Deploys the API gateway and sample functions. You can access the UI on port 8080. \u003cb\u003eWarning\u003c/b\u003e: the name of the stack must be 'func'.", + "platform": "linux", + "repository": { + "stackfile": "docker-compose.yml", + "url": "https://github.com/openfaas/faas" + }, + "stackFile": "", + "title": "OpenFaaS", + "type": 2 + }, + { + "Id": 42, + "administrator_only": false, + "categories": ["serverless"], + "description": "Open-source serverless computing platform", + "image": "", + "logo": "https://portainer-io-assets.sfo2.digitaloceanspaces.com/logos/ironfunctions.png", + "note": "Deploys the IronFunctions API and UI.", + "platform": "linux", + "repository": { + "stackfile": "stacks/ironfunctions/docker-stack.yml", + "url": "https://github.com/portainer/templates" + }, + "stackFile": "", + "title": "IronFunctions", + "type": 2 + }, + { + "Id": 43, + "administrator_only": false, + "categories": ["database"], + "description": "CockroachDB cluster", + "image": "", + "logo": "https://portainer-io-assets.sfo2.digitaloceanspaces.com/logos/cockroachdb.png", + "note": "Deploys an insecure CockroachDB cluster, please refer to \u003ca href=\"https://www.cockroachlabs.com/docs/stable/orchestrate-cockroachdb-with-docker-swarm.html\" target=\"_blank\"\u003eCockroachDB documentation\u003c/a\u003e for production deployments.", + "platform": "linux", + "repository": { + "stackfile": "stacks/cockroachdb/docker-stack.yml", + "url": "https://github.com/portainer/templates" + }, + "stackFile": "", + "title": "CockroachDB", + "type": 2 + }, + { + "Id": 44, + "administrator_only": false, + "categories": ["CMS"], + "description": "Wordpress setup with a MySQL database", + "env": [ + { + "description": "Password used by the MySQL root user.", + "label": "Database root password", + "name": "MYSQL_DATABASE_PASSWORD" + } + ], + "image": "", + "logo": "https://portainer-io-assets.sfo2.digitaloceanspaces.com/logos/wordpress.png", + "note": "Deploys a Wordpress instance connected to a MySQL database.", + "platform": "linux", + "repository": { + "stackfile": "stacks/wordpress/docker-stack.yml", + "url": "https://github.com/portainer/templates" + }, + "stackFile": "", + "title": "Wordpress", + "type": 2 + }, + { + "Id": 45, + "administrator_only": false, + "categories": ["CMS"], + "description": "Wordpress setup with a MySQL database", + "env": [ + { + "description": "Password used by the MySQL root user.", + "label": "Database root password", + "name": "MYSQL_DATABASE_PASSWORD" + } + ], + "image": "", + "logo": "https://portainer-io-assets.sfo2.digitaloceanspaces.com/logos/wordpress.png", + "note": "Deploys a Wordpress instance connected to a MySQL database.", + "platform": "linux", + "repository": { + "stackfile": "stacks/wordpress/docker-compose.yml", + "url": "https://github.com/portainer/templates" + }, + "stackFile": "", + "title": "Wordpress", + "type": 3 + }, + { + "Id": 46, + "administrator_only": false, + "categories": ["OPS"], + "description": "Microsoft Operations Management Suite Linux agent.", + "env": [ + { + "description": "Azure Workspace ID", + "label": "Workspace ID", + "name": "AZURE_WORKSPACE_ID" + }, + { + "description": "Azure primary key", + "label": "Primary key", + "name": "AZURE_PRIMARY_KEY" + } + ], + "image": "", + "logo": "https://portainer-io-assets.sfo2.digitaloceanspaces.com/logos/microsoft.png", + "platform": "linux", + "repository": { + "stackfile": "stacks/microsoft-oms/docker-stack.yml", + "url": "https://github.com/portainer/templates" + }, + "stackFile": "", + "title": "Microsoft OMS Agent", + "type": 2 + }, + { + "Id": 47, + "administrator_only": false, + "categories": ["Log Management", "Monitoring"], + "description": "Collect logs, metrics and docker events", + "env": [ + { + "label": "Logs token", + "name": "LOGSENE_TOKEN" + }, + { + "label": "SPM monitoring token", + "name": "SPM_TOKEN" + } + ], + "image": "", + "logo": "https://portainer-io-assets.sfo2.digitaloceanspaces.com/logos/sematext_agent.png", + "platform": "linux", + "repository": { + "stackfile": "stacks/sematext-agent-docker/docker-stack.yml", + "url": "https://github.com/portainer/templates" + }, + "stackFile": "", + "title": "Sematext Docker Agent", + "type": 2 + }, + { + "Id": 48, + "administrator_only": false, + "categories": ["Monitoring"], + "description": "Collect events and metrics", + "env": [ + { + "label": "Datadog API key", + "name": "API_KEY" + } + ], + "image": "", + "logo": "https://portainer-io-assets.sfo2.digitaloceanspaces.com/logos/datadog_agent.png", + "platform": "linux", + "repository": { + "stackfile": "stacks/datadog-agent/docker-stack.yml", + "url": "https://github.com/portainer/templates" + }, + "stackFile": "", + "title": "Datadog agent", + "type": 2 + }, + { + "Id": 49, + "administrator_only": false, + "categories": ["docker"], + "description": "Sonatype Nexus3 registry manager", + "image": "sonatype/nexus3:latest", + "logo": "https://portainer-io-assets.sfo2.digitaloceanspaces.com/logos/sonatype.png", + "platform": "linux", + "ports": ["8081/tcp"], + "repository": { + "stackfile": "", + "url": "" + }, + "stackFile": "", + "title": "Sonatype Nexus3", + "type": 1, + "volumes": [ + { + "container": "/nexus-data" + } + ] + } + ], + "tunnel_server": { + "PrivateKeySeed": "IvX6ZPRuWtLS5zyg" + }, + "users": [ + { + "EndpointAuthorizations": null, + "Id": 1, + "Password": "$2a$10$siRDprr/5uUFAU8iom3Sr./WXQkN2dhSNjAC471pkJaALkghS762a", + "PortainerAuthorizations": { + "PortainerDockerHubInspect": true, + "PortainerEndpointExtensionAdd": true, + "PortainerEndpointExtensionRemove": true, + "PortainerEndpointGroupList": true, + "PortainerEndpointInspect": true, + "PortainerEndpointList": true, + "PortainerExtensionList": true, + "PortainerMOTD": true, + "PortainerRegistryInspect": true, + "PortainerRegistryList": true, + "PortainerTeamList": true, + "PortainerTemplateInspect": true, + "PortainerTemplateList": true, + "PortainerUserInspect": true, + "PortainerUserList": true, + "PortainerUserMemberships": true + }, + "Role": 1, + "Username": "admin" + }, + { + "EndpointAuthorizations": null, + "Id": 2, + "Password": "$2a$10$WpCAW8mSt6FRRp1GkynbFOGSZnHR6E5j9cETZ8HiMlw06hVlDW/Li", + "PortainerAuthorizations": { + "PortainerDockerHubInspect": true, + "PortainerEndpointExtensionAdd": true, + "PortainerEndpointExtensionRemove": true, + "PortainerEndpointGroupList": true, + "PortainerEndpointInspect": true, + "PortainerEndpointList": true, + "PortainerExtensionList": true, + "PortainerMOTD": true, + "PortainerRegistryInspect": true, + "PortainerRegistryList": true, + "PortainerTeamList": true, + "PortainerTemplateInspect": true, + "PortainerTemplateList": true, + "PortainerUserInspect": true, + "PortainerUserList": true, + "PortainerUserMemberships": true + }, + "Role": 1, + "Username": "prabhat" + } + ], + "version": { + "DB_VERSION": 24 + } +} diff --git a/api/datastore/test_data/output_35.json b/api/datastore/test_data/output_35.json new file mode 100644 index 000000000..023cdd31c --- /dev/null +++ b/api/datastore/test_data/output_35.json @@ -0,0 +1,802 @@ +{ + "dockerhub": [ + { + "Authentication": false, + "Username": "" + } + ], + "endpoint_groups": [ + { + "AuthorizedTeams": null, + "AuthorizedUsers": null, + "Description": "Unassigned endpoints", + "Id": 1, + "Labels": [], + "Name": "Unassigned", + "TagIds": [], + "Tags": null, + "TeamAccessPolicies": {}, + "UserAccessPolicies": {} + } + ], + "endpoint_relations": [ + { + "EdgeStacks": {}, + "EndpointID": 1 + } + ], + "endpoints": [ + { + "AuthorizedTeams": null, + "AuthorizedUsers": null, + "AzureCredentials": { + "ApplicationID": "", + "AuthenticationKey": "", + "TenantID": "" + }, + "ComposeSyntaxMaxVersion": "", + "EdgeCheckinInterval": 0, + "EdgeKey": "", + "GroupId": 1, + "Id": 1, + "IsEdgeDevice": false, + "Kubernetes": { + "Configuration": { + "IngressClasses": null, + "RestrictDefaultNamespace": false, + "StorageClasses": null, + "UseLoadBalancer": false, + "UseServerMetrics": false + }, + "Snapshots": null + }, + "LastCheckInDate": 0, + "Name": "local", + "PublicURL": "", + "QueryDate": 0, + "SecuritySettings": { + "allowBindMountsForRegularUsers": true, + "allowContainerCapabilitiesForRegularUsers": true, + "allowDeviceMappingForRegularUsers": true, + "allowHostNamespaceForRegularUsers": true, + "allowPrivilegedModeForRegularUsers": true, + "allowStackManagementForRegularUsers": true, + "allowSysctlSettingForRegularUsers": false, + "allowVolumeBrowserForRegularUsers": false, + "enableHostManagementFeatures": false + }, + "Snapshots": [ + { + "DockerSnapshotRaw": { + "Containers": null, + "Images": null, + "Info": null, + "Networks": null, + "Version": null, + "Volumes": 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, + "TLSSkipVerify": false + }, + "TagIds": [], + "Tags": null, + "TeamAccessPolicies": {}, + "Type": 1, + "URL": "unix:///var/run/docker.sock", + "UserAccessPolicies": {}, + "UserTrusted": false + } + ], + "registries": [ + { + "Authentication": true, + "AuthorizedTeams": null, + "AuthorizedUsers": null, + "BaseURL": "", + "Ecr": { + "Region": "" + }, + "Gitlab": { + "InstanceURL": "", + "ProjectId": 0, + "ProjectPath": "" + }, + "Id": 1, + "ManagementConfiguration": null, + "Name": "canister.io", + "Password": "MjWbx8A6YK7cw7", + "Quay": { + "OrganisationName": "", + "UseOrganisation": false + }, + "RegistryAccesses": { + "1": { + "Namespaces": [], + "TeamAccessPolicies": {}, + "UserAccessPolicies": {} + } + }, + "TeamAccessPolicies": {}, + "Type": 3, + "URL": "cloud.canister.io:5000", + "UserAccessPolicies": {}, + "Username": "prabhatkhera" + } + ], + "resource_control": [ + { + "AdministratorsOnly": false, + "Id": 2, + "Public": true, + "ResourceId": "762gbwaj8r4gcsdy8ld1u4why", + "SubResourceIds": [], + "System": false, + "TeamAccesses": [], + "Type": 5, + "UserAccesses": [] + }, + { + "AdministratorsOnly": false, + "Id": 3, + "Public": true, + "ResourceId": "1_alpine", + "SubResourceIds": [], + "System": false, + "TeamAccesses": [], + "Type": 6, + "UserAccesses": [] + }, + { + "AdministratorsOnly": false, + "Id": 4, + "Public": true, + "ResourceId": "1_redis", + "SubResourceIds": [], + "System": false, + "TeamAccesses": [], + "Type": 6, + "UserAccesses": [] + }, + { + "AdministratorsOnly": false, + "Id": 5, + "Public": false, + "ResourceId": "1_nginx", + "SubResourceIds": [], + "System": false, + "TeamAccesses": [ + { + "AccessLevel": 1, + "TeamId": 1 + } + ], + "Type": 6, + "UserAccesses": [] + } + ], + "roles": [ + { + "Authorizations": { + "DockerAgentBrowseDelete": true, + "DockerAgentBrowseGet": true, + "DockerAgentBrowseList": true, + "DockerAgentBrowsePut": true, + "DockerAgentBrowseRename": true, + "DockerAgentHostInfo": true, + "DockerAgentList": true, + "DockerAgentPing": true, + "DockerAgentUndefined": true, + "DockerBuildCancel": true, + "DockerBuildPrune": true, + "DockerConfigCreate": true, + "DockerConfigDelete": true, + "DockerConfigInspect": true, + "DockerConfigList": true, + "DockerConfigUpdate": true, + "DockerContainerArchive": true, + "DockerContainerArchiveInfo": true, + "DockerContainerAttach": true, + "DockerContainerAttachWebsocket": true, + "DockerContainerChanges": true, + "DockerContainerCreate": true, + "DockerContainerDelete": true, + "DockerContainerExec": true, + "DockerContainerExport": true, + "DockerContainerInspect": true, + "DockerContainerKill": true, + "DockerContainerList": true, + "DockerContainerLogs": true, + "DockerContainerPause": true, + "DockerContainerPrune": true, + "DockerContainerPutContainerArchive": true, + "DockerContainerRename": true, + "DockerContainerResize": true, + "DockerContainerRestart": true, + "DockerContainerStart": true, + "DockerContainerStats": true, + "DockerContainerStop": true, + "DockerContainerTop": true, + "DockerContainerUnpause": true, + "DockerContainerUpdate": true, + "DockerContainerWait": true, + "DockerDistributionInspect": true, + "DockerEvents": true, + "DockerExecInspect": true, + "DockerExecResize": true, + "DockerExecStart": true, + "DockerImageBuild": true, + "DockerImageCommit": true, + "DockerImageCreate": true, + "DockerImageDelete": true, + "DockerImageGet": true, + "DockerImageGetAll": true, + "DockerImageHistory": true, + "DockerImageInspect": true, + "DockerImageList": true, + "DockerImageLoad": true, + "DockerImagePrune": true, + "DockerImagePush": true, + "DockerImageSearch": true, + "DockerImageTag": true, + "DockerInfo": true, + "DockerNetworkConnect": true, + "DockerNetworkCreate": true, + "DockerNetworkDelete": true, + "DockerNetworkDisconnect": true, + "DockerNetworkInspect": true, + "DockerNetworkList": true, + "DockerNetworkPrune": true, + "DockerNodeDelete": true, + "DockerNodeInspect": true, + "DockerNodeList": true, + "DockerNodeUpdate": true, + "DockerPing": true, + "DockerPluginCreate": true, + "DockerPluginDelete": true, + "DockerPluginDisable": true, + "DockerPluginEnable": true, + "DockerPluginInspect": true, + "DockerPluginList": true, + "DockerPluginPrivileges": true, + "DockerPluginPull": true, + "DockerPluginPush": true, + "DockerPluginSet": true, + "DockerPluginUpgrade": true, + "DockerSecretCreate": true, + "DockerSecretDelete": true, + "DockerSecretInspect": true, + "DockerSecretList": true, + "DockerSecretUpdate": true, + "DockerServiceCreate": true, + "DockerServiceDelete": true, + "DockerServiceInspect": true, + "DockerServiceList": true, + "DockerServiceLogs": true, + "DockerServiceUpdate": true, + "DockerSessionStart": true, + "DockerSwarmInit": true, + "DockerSwarmInspect": true, + "DockerSwarmJoin": true, + "DockerSwarmLeave": true, + "DockerSwarmUnlock": true, + "DockerSwarmUnlockKey": true, + "DockerSwarmUpdate": true, + "DockerSystem": true, + "DockerTaskInspect": true, + "DockerTaskList": true, + "DockerTaskLogs": true, + "DockerUndefined": true, + "DockerVersion": true, + "DockerVolumeCreate": true, + "DockerVolumeDelete": true, + "DockerVolumeInspect": true, + "DockerVolumeList": true, + "DockerVolumePrune": true, + "EndpointResourcesAccess": true, + "IntegrationStoridgeAdmin": true, + "PortainerResourceControlCreate": true, + "PortainerResourceControlUpdate": true, + "PortainerStackCreate": true, + "PortainerStackDelete": true, + "PortainerStackFile": true, + "PortainerStackInspect": true, + "PortainerStackList": true, + "PortainerStackMigrate": true, + "PortainerStackUpdate": true, + "PortainerWebhookCreate": true, + "PortainerWebhookDelete": true, + "PortainerWebhookList": true, + "PortainerWebsocketExec": true + }, + "Description": "Full control of all resources in an endpoint", + "Id": 1, + "Name": "Endpoint administrator", + "Priority": 1 + }, + { + "Authorizations": { + "DockerAgentHostInfo": true, + "DockerAgentList": true, + "DockerAgentPing": true, + "DockerConfigInspect": true, + "DockerConfigList": true, + "DockerContainerArchiveInfo": true, + "DockerContainerChanges": true, + "DockerContainerInspect": true, + "DockerContainerList": true, + "DockerContainerLogs": true, + "DockerContainerStats": true, + "DockerContainerTop": true, + "DockerDistributionInspect": true, + "DockerEvents": true, + "DockerImageGet": true, + "DockerImageGetAll": true, + "DockerImageHistory": true, + "DockerImageInspect": true, + "DockerImageList": true, + "DockerImageSearch": true, + "DockerInfo": true, + "DockerNetworkInspect": true, + "DockerNetworkList": true, + "DockerNodeInspect": true, + "DockerNodeList": true, + "DockerPing": true, + "DockerPluginList": true, + "DockerSecretInspect": true, + "DockerSecretList": true, + "DockerServiceInspect": true, + "DockerServiceList": true, + "DockerServiceLogs": true, + "DockerSwarmInspect": true, + "DockerSystem": true, + "DockerTaskInspect": true, + "DockerTaskList": true, + "DockerTaskLogs": true, + "DockerVersion": true, + "DockerVolumeInspect": true, + "DockerVolumeList": true, + "EndpointResourcesAccess": true, + "PortainerStackFile": true, + "PortainerStackInspect": true, + "PortainerStackList": true, + "PortainerWebhookList": true + }, + "Description": "Read-only access of all resources in an endpoint", + "Id": 2, + "Name": "Helpdesk", + "Priority": 2 + }, + { + "Authorizations": { + "DockerAgentHostInfo": true, + "DockerAgentList": true, + "DockerAgentPing": true, + "DockerAgentUndefined": true, + "DockerBuildCancel": true, + "DockerBuildPrune": true, + "DockerConfigCreate": true, + "DockerConfigDelete": true, + "DockerConfigInspect": true, + "DockerConfigList": true, + "DockerConfigUpdate": true, + "DockerContainerArchive": true, + "DockerContainerArchiveInfo": true, + "DockerContainerAttach": true, + "DockerContainerAttachWebsocket": true, + "DockerContainerChanges": true, + "DockerContainerCreate": true, + "DockerContainerDelete": true, + "DockerContainerExec": true, + "DockerContainerExport": true, + "DockerContainerInspect": true, + "DockerContainerKill": true, + "DockerContainerList": true, + "DockerContainerLogs": true, + "DockerContainerPause": true, + "DockerContainerPutContainerArchive": true, + "DockerContainerRename": true, + "DockerContainerResize": true, + "DockerContainerRestart": true, + "DockerContainerStart": true, + "DockerContainerStats": true, + "DockerContainerStop": true, + "DockerContainerTop": true, + "DockerContainerUnpause": true, + "DockerContainerUpdate": true, + "DockerContainerWait": true, + "DockerDistributionInspect": true, + "DockerEvents": true, + "DockerExecInspect": true, + "DockerExecResize": true, + "DockerExecStart": true, + "DockerImageBuild": true, + "DockerImageCommit": true, + "DockerImageCreate": true, + "DockerImageDelete": true, + "DockerImageGet": true, + "DockerImageGetAll": true, + "DockerImageHistory": true, + "DockerImageInspect": true, + "DockerImageList": true, + "DockerImageLoad": true, + "DockerImagePush": true, + "DockerImageSearch": true, + "DockerImageTag": true, + "DockerInfo": true, + "DockerNetworkConnect": true, + "DockerNetworkCreate": true, + "DockerNetworkDelete": true, + "DockerNetworkDisconnect": true, + "DockerNetworkInspect": true, + "DockerNetworkList": true, + "DockerNodeDelete": true, + "DockerNodeInspect": true, + "DockerNodeList": true, + "DockerNodeUpdate": true, + "DockerPing": true, + "DockerPluginCreate": true, + "DockerPluginDelete": true, + "DockerPluginDisable": true, + "DockerPluginEnable": true, + "DockerPluginInspect": true, + "DockerPluginList": true, + "DockerPluginPrivileges": true, + "DockerPluginPull": true, + "DockerPluginPush": true, + "DockerPluginSet": true, + "DockerPluginUpgrade": true, + "DockerSecretCreate": true, + "DockerSecretDelete": true, + "DockerSecretInspect": true, + "DockerSecretList": true, + "DockerSecretUpdate": true, + "DockerServiceCreate": true, + "DockerServiceDelete": true, + "DockerServiceInspect": true, + "DockerServiceList": true, + "DockerServiceLogs": true, + "DockerServiceUpdate": true, + "DockerSessionStart": true, + "DockerSwarmInit": true, + "DockerSwarmInspect": true, + "DockerSwarmJoin": true, + "DockerSwarmLeave": true, + "DockerSwarmUnlock": true, + "DockerSwarmUnlockKey": true, + "DockerSwarmUpdate": true, + "DockerSystem": true, + "DockerTaskInspect": true, + "DockerTaskList": true, + "DockerTaskLogs": true, + "DockerUndefined": true, + "DockerVersion": true, + "DockerVolumeCreate": true, + "DockerVolumeDelete": true, + "DockerVolumeInspect": true, + "DockerVolumeList": true, + "PortainerResourceControlUpdate": true, + "PortainerStackCreate": true, + "PortainerStackDelete": true, + "PortainerStackFile": true, + "PortainerStackInspect": true, + "PortainerStackList": true, + "PortainerStackMigrate": true, + "PortainerStackUpdate": true, + "PortainerWebhookCreate": true, + "PortainerWebhookList": true, + "PortainerWebsocketExec": true + }, + "Description": "Full control of assigned resources in an endpoint", + "Id": 3, + "Name": "Standard user", + "Priority": 3 + }, + { + "Authorizations": { + "DockerAgentHostInfo": true, + "DockerAgentList": true, + "DockerAgentPing": true, + "DockerConfigInspect": true, + "DockerConfigList": true, + "DockerContainerArchiveInfo": true, + "DockerContainerChanges": true, + "DockerContainerInspect": true, + "DockerContainerList": true, + "DockerContainerLogs": true, + "DockerContainerStats": true, + "DockerContainerTop": true, + "DockerDistributionInspect": true, + "DockerEvents": true, + "DockerImageGet": true, + "DockerImageGetAll": true, + "DockerImageHistory": true, + "DockerImageInspect": true, + "DockerImageList": true, + "DockerImageSearch": true, + "DockerInfo": true, + "DockerNetworkInspect": true, + "DockerNetworkList": true, + "DockerNodeInspect": true, + "DockerNodeList": true, + "DockerPing": true, + "DockerPluginList": true, + "DockerSecretInspect": true, + "DockerSecretList": true, + "DockerServiceInspect": true, + "DockerServiceList": true, + "DockerServiceLogs": true, + "DockerSwarmInspect": true, + "DockerSystem": true, + "DockerTaskInspect": true, + "DockerTaskList": true, + "DockerTaskLogs": true, + "DockerVersion": true, + "DockerVolumeInspect": true, + "DockerVolumeList": true, + "PortainerStackFile": true, + "PortainerStackInspect": true, + "PortainerStackList": true, + "PortainerWebhookList": true + }, + "Description": "Read-only access of assigned resources in an endpoint", + "Id": 4, + "Name": "Read-only user", + "Priority": 4 + } + ], + "schedules": [ + { + "Created": 1648608136, + "CronExpression": "@every 5m", + "EdgeSchedule": null, + "EndpointSyncJob": null, + "Id": 1, + "JobType": 2, + "Name": "system_snapshot", + "Recurring": true, + "ScriptExecutionJob": null, + "SnapshotJob": {} + } + ], + "settings": { + "AllowBindMountsForRegularUsers": true, + "AllowContainerCapabilitiesForRegularUsers": true, + "AllowDeviceMappingForRegularUsers": true, + "AllowHostNamespaceForRegularUsers": true, + "AllowPrivilegedModeForRegularUsers": true, + "AllowStackManagementForRegularUsers": true, + "AllowVolumeBrowserForRegularUsers": false, + "AuthenticationMethod": 1, + "BlackListedLabels": [], + "DisableTrustOnFirstConnect": false, + "DisplayDonationHeader": false, + "DisplayExternalContributors": false, + "EdgeAgentCheckinInterval": 5, + "EnableEdgeComputeFeatures": false, + "EnableHostManagementFeatures": false, + "EnableTelemetry": true, + "EnforceEdgeID": false, + "FeatureFlagSettings": null, + "HelmRepositoryURL": "https://charts.bitnami.com/bitnami", + "KubeconfigExpiry": "0", + "KubectlShellImage": "portainer/kubectl-shell", + "LDAPSettings": { + "AnonymousMode": true, + "AutoCreateUsers": true, + "GroupSearchSettings": [ + { + "GroupAttribute": "", + "GroupBaseDN": "", + "GroupFilter": "" + } + ], + "ReaderDN": "", + "SearchSettings": [ + { + "BaseDN": "", + "Filter": "", + "UserNameAttribute": "" + } + ], + "StartTLS": false, + "TLSConfig": { + "TLS": false, + "TLSSkipVerify": false + }, + "URL": "" + }, + "LogoURL": "", + "OAuthSettings": { + "AccessTokenURI": "", + "AuthorizationURI": "", + "ClientID": "", + "DefaultTeamID": 0, + "KubeSecretKey": null, + "LogoutURI": "", + "OAuthAutoCreateUsers": false, + "RedirectURI": "", + "ResourceURI": "", + "SSO": false, + "Scopes": "", + "UserIdentifier": "" + }, + "SnapshotInterval": "5m", + "TemplatesURL": "https://raw.githubusercontent.com/portainer/templates/master/templates-2.0.json", + "UserSessionTimeout": "8h", + "fdoConfiguration": { + "enabled": false, + "ownerPassword": "", + "ownerURL": "", + "ownerUsername": "" + }, + "openAMTConfiguration": { + "certFileContent": "", + "certFileName": "", + "certFilePassword": "", + "domainName": "", + "enabled": false, + "mpsPassword": "", + "mpsServer": "", + "mpsToken": "", + "mpsUser": "" + } + }, + "ssl": { + "certPath": "", + "httpEnabled": true, + "keyPath": "", + "selfSigned": false + }, + "stacks": [ + { + "AdditionalFiles": null, + "AutoUpdate": null, + "CreatedBy": "", + "CreationDate": 0, + "EndpointId": 1, + "EntryPoint": "docker/alpine37-compose.yml", + "Env": [], + "FromAppTemplate": false, + "GitConfig": null, + "Id": 2, + "IsComposeFormat": false, + "Name": "alpine", + "Namespace": "", + "ProjectPath": "/home/prabhat/portainer/data/ce1.25/compose/2", + "ResourceControl": null, + "Status": 1, + "SwarmId": "s3fd604zdba7z13tbq2x6lyue", + "Type": 1, + "UpdateDate": 0, + "UpdatedBy": "" + }, + { + "AdditionalFiles": null, + "AutoUpdate": null, + "CreatedBy": "", + "CreationDate": 0, + "EndpointId": 1, + "EntryPoint": "docker-compose.yml", + "Env": [], + "FromAppTemplate": false, + "GitConfig": null, + "Id": 5, + "IsComposeFormat": false, + "Name": "redis", + "Namespace": "", + "ProjectPath": "/home/prabhat/portainer/data/ce1.25/compose/5", + "ResourceControl": null, + "Status": 1, + "SwarmId": "", + "Type": 2, + "UpdateDate": 0, + "UpdatedBy": "" + }, + { + "AdditionalFiles": null, + "AutoUpdate": null, + "CreatedBy": "", + "CreationDate": 0, + "EndpointId": 1, + "EntryPoint": "docker-compose.yml", + "Env": [], + "FromAppTemplate": false, + "GitConfig": null, + "Id": 6, + "IsComposeFormat": false, + "Name": "nginx", + "Namespace": "", + "ProjectPath": "/home/prabhat/portainer/data/ce1.25/compose/6", + "ResourceControl": null, + "Status": 1, + "SwarmId": "", + "Type": 2, + "UpdateDate": 0, + "UpdatedBy": "" + } + ], + "teams": [ + { + "Id": 1, + "Name": "hello" + } + ], + "tunnel_server": { + "PrivateKeySeed": "IvX6ZPRuWtLS5zyg" + }, + "users": [ + { + "EndpointAuthorizations": null, + "Id": 1, + "Password": "$2a$10$siRDprr/5uUFAU8iom3Sr./WXQkN2dhSNjAC471pkJaALkghS762a", + "PortainerAuthorizations": { + "PortainerDockerHubInspect": true, + "PortainerEndpointExtensionAdd": true, + "PortainerEndpointExtensionRemove": true, + "PortainerEndpointGroupList": true, + "PortainerEndpointInspect": true, + "PortainerEndpointList": true, + "PortainerExtensionList": true, + "PortainerMOTD": true, + "PortainerRegistryInspect": true, + "PortainerRegistryList": true, + "PortainerTeamList": true, + "PortainerTemplateInspect": true, + "PortainerTemplateList": true, + "PortainerUserInspect": true, + "PortainerUserList": true, + "PortainerUserMemberships": true + }, + "Role": 1, + "Username": "admin" + }, + { + "EndpointAuthorizations": null, + "Id": 2, + "Password": "$2a$10$WpCAW8mSt6FRRp1GkynbFOGSZnHR6E5j9cETZ8HiMlw06hVlDW/Li", + "PortainerAuthorizations": { + "PortainerDockerHubInspect": true, + "PortainerEndpointExtensionAdd": true, + "PortainerEndpointExtensionRemove": true, + "PortainerEndpointGroupList": true, + "PortainerEndpointInspect": true, + "PortainerEndpointList": true, + "PortainerExtensionList": true, + "PortainerMOTD": true, + "PortainerRegistryInspect": true, + "PortainerRegistryList": true, + "PortainerTeamList": true, + "PortainerTemplateInspect": true, + "PortainerTemplateList": true, + "PortainerUserInspect": true, + "PortainerUserList": true, + "PortainerUserMemberships": true + }, + "Role": 1, + "Username": "prabhat" + } + ], + "version": { + "DB_UPDATING": "false", + "DB_VERSION": "35", + "INSTANCE_ID": "null" + } +} \ No newline at end of file diff --git a/api/datastore/teststore.go b/api/datastore/teststore.go index 7402365d4..bd3b961d7 100644 --- a/api/datastore/teststore.go +++ b/api/datastore/teststore.go @@ -18,8 +18,8 @@ func (store *Store) GetConnection() portainer.Connection { return store.connection } -func MustNewTestStore(init bool) (bool, *Store, func()) { - newStore, store, teardown, err := NewTestStore(init) +func MustNewTestStore(init, secure bool) (bool, *Store, func()) { + newStore, store, teardown, err := NewTestStore(init, secure) if err != nil { if !errors.Is(err, errTempDir) { teardown() @@ -30,7 +30,7 @@ func MustNewTestStore(init bool) (bool, *Store, func()) { return newStore, store, teardown } -func NewTestStore(init bool) (bool, *Store, func(), error) { +func NewTestStore(init, secure bool) (bool, *Store, func(), error) { // Creates unique temp directory in a concurrency friendly manner. storePath, err := ioutil.TempDir("", "test-store") if err != nil { @@ -42,7 +42,12 @@ func NewTestStore(init bool) (bool, *Store, func(), error) { return false, nil, nil, err } - connection, err := database.NewDatabase("boltdb", storePath, []byte("apassphrasewhichneedstobe32bytes")) + secretKey := []byte("apassphrasewhichneedstobe32bytes") + if !secure { + secretKey = nil + } + + connection, err := database.NewDatabase("boltdb", storePath, secretKey) if err != nil { panic(err) } diff --git a/api/datastore/validate/validate.go b/api/datastore/validate/validate.go new file mode 100644 index 000000000..2b37311fe --- /dev/null +++ b/api/datastore/validate/validate.go @@ -0,0 +1,15 @@ +package validate + +import ( + "github.com/go-playground/validator/v10" + portainer "github.com/portainer/portainer/api" +) + +var validate *validator.Validate + +func ValidateLDAPSettings(ldp *portainer.LDAPSettings) error { + validate = validator.New() + registerValidationMethods(validate) + + return validate.Struct(ldp) +} diff --git a/api/datastore/validate/validate_test.go b/api/datastore/validate/validate_test.go new file mode 100644 index 000000000..3fa7bd425 --- /dev/null +++ b/api/datastore/validate/validate_test.go @@ -0,0 +1,61 @@ +package validate + +import ( + "testing" + + portainer "github.com/portainer/portainer/api" +) + +func TestValidateLDAPSettings(t *testing.T) { + + tests := []struct { + name string + ldap portainer.LDAPSettings + wantErr bool + }{ + { + name: "Empty LDAP Settings", + ldap: portainer.LDAPSettings{}, + wantErr: true, + }, + { + name: "With URL", + ldap: portainer.LDAPSettings{ + AnonymousMode: true, + URL: "192.168.0.1:323", + }, + wantErr: false, + }, + { + name: "Validate URL and URLs", + ldap: portainer.LDAPSettings{ + AnonymousMode: true, + URL: "192.168.0.1:323", + }, + wantErr: false, + }, + { + name: "validate client ldap", + ldap: portainer.LDAPSettings{ + AnonymousMode: false, + ReaderDN: "CN=LDAP API Service Account", + Password: "Qu**dfUUU**", + URL: "aukdc15.pgc.co:389", + TLSConfig: portainer.TLSConfiguration{ + TLS: false, + TLSSkipVerify: false, + }, + }, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := ValidateLDAPSettings(&tt.ldap) + if (err == nil) == tt.wantErr { + t.Errorf("No error expected but got %s", err) + } + }) + } +} diff --git a/api/datastore/validate/validationMethods.go b/api/datastore/validate/validationMethods.go new file mode 100644 index 000000000..36abe3a54 --- /dev/null +++ b/api/datastore/validate/validationMethods.go @@ -0,0 +1,17 @@ +package validate + +import ( + "github.com/go-playground/validator/v10" +) + +func registerValidationMethods(v *validator.Validate) { + v.RegisterValidation("validate_bool", ValidateBool) +} + +/** + * Validation methods below are being used for custom validation + */ +func ValidateBool(fl validator.FieldLevel) bool { + _, ok := fl.Field().Interface().(bool) + return ok +} diff --git a/api/go.mod b/api/go.mod index a192ae095..69e0a97d3 100644 --- a/api/go.mod +++ b/api/go.mod @@ -16,8 +16,10 @@ require ( github.com/g07cha/defender v0.0.0-20180505193036-5665c627c814 github.com/go-git/go-git/v5 v5.3.0 github.com/go-ldap/ldap/v3 v3.1.8 + github.com/go-playground/validator/v10 v10.10.1 github.com/gofrs/uuid v4.0.0+incompatible github.com/golang-jwt/jwt v3.2.2+incompatible + github.com/google/go-cmp v0.5.6 github.com/gorilla/handlers v1.5.1 github.com/gorilla/mux v1.7.3 github.com/gorilla/securecookie v1.1.1 @@ -39,7 +41,7 @@ require ( github.com/stretchr/testify v1.7.0 github.com/viney-shih/go-lock v1.1.1 go.etcd.io/bbolt v1.3.6 - golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70 + golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f golang.org/x/sync v0.0.0-20210220032951-036812b2e83c gopkg.in/alecthomas/kingpin.v2 v2.2.6 @@ -71,9 +73,10 @@ require ( github.com/go-git/gcfg v1.5.0 // indirect github.com/go-git/go-billy/v5 v5.1.0 // indirect github.com/go-logr/logr v1.2.2 // indirect + github.com/go-playground/locales v0.14.0 // indirect + github.com/go-playground/universal-translator v0.18.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.2 // indirect - github.com/google/go-cmp v0.5.6 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/googleapis/gnostic v0.5.5 // indirect @@ -84,6 +87,7 @@ require ( github.com/jpillora/requestlog v1.0.0 // indirect github.com/jpillora/sizestr v1.0.0 // indirect github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // indirect + github.com/leodido/go-urn v1.2.1 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.1.2 // indirect github.com/moby/spdystream v0.2.0 // indirect @@ -101,9 +105,9 @@ require ( github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect - golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect - golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 // indirect - golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect + golang.org/x/net v0.0.0-20211216030914-fe4d6282115f // indirect + golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect + golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b // indirect golang.org/x/text v0.3.7 // indirect golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect google.golang.org/appengine v1.6.7 // indirect diff --git a/api/go.sum b/api/go.sum index d9b32f5ff..86bfc17b2 100644 --- a/api/go.sum +++ b/api/go.sum @@ -432,6 +432,14 @@ github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dp github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= +github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= +github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= +github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= +github.com/go-playground/validator/v10 v10.10.1 h1:uA0+amWMiglNZKZ9FJRKUAe9U3RX91eVn1JYXMWt7ig= +github.com/go-playground/validator/v10 v10.10.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= @@ -651,13 +659,16 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxv github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= +github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= github.com/linuxkit/virtsock v0.0.0-20201010232012-f8cee7dfc7a3/go.mod h1:3r6x7q95whyfWQpmGZTu3gk3v2YkMi05HEzl7Tf7YEo= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= @@ -786,6 +797,7 @@ github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/9 github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -843,6 +855,9 @@ github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzG github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= +github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= @@ -1019,8 +1034,8 @@ golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70 h1:syTAU9FwmvzEoIYMqcPHOcVm4H3U5u90WsvuYgwpETU= -golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M= +golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1114,9 +1129,8 @@ golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211216030914-fe4d6282115f h1:hEYJvxw1lSnWIl8X9ofsYMklzaDs90JI2az5YMd4fPM= golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc= -golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1234,21 +1248,20 @@ golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210903071746-97244b99971b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 h1:y/woIyUBFbpQGKS0u1aHF/40WUDnek3fPOyD08H5Vng= -golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b h1:9zKuko04nR4gjZ4+DNjHqRlAJqbJETHwiNKDqTfOjfE= golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/api/http/handler/helm/helm_delete_test.go b/api/http/handler/helm/helm_delete_test.go index a1f141159..b8a3e6ac3 100644 --- a/api/http/handler/helm/helm_delete_test.go +++ b/api/http/handler/helm/helm_delete_test.go @@ -22,7 +22,7 @@ import ( func Test_helmDelete(t *testing.T) { is := assert.New(t) - _, store, teardown := datastore.MustNewTestStore(true) + _, store, teardown := datastore.MustNewTestStore(true, true) defer teardown() err := store.Endpoint().Create(&portainer.Endpoint{ID: 1}) diff --git a/api/http/handler/helm/helm_install_test.go b/api/http/handler/helm/helm_install_test.go index cb011fc70..dce113aab 100644 --- a/api/http/handler/helm/helm_install_test.go +++ b/api/http/handler/helm/helm_install_test.go @@ -25,7 +25,7 @@ import ( func Test_helmInstall(t *testing.T) { is := assert.New(t) - _, store, teardown := datastore.MustNewTestStore(true) + _, store, teardown := datastore.MustNewTestStore(true, true) defer teardown() err := store.Endpoint().Create(&portainer.Endpoint{ID: 1}) diff --git a/api/http/handler/helm/helm_list_test.go b/api/http/handler/helm/helm_list_test.go index 710771b4e..35ea0c1fa 100644 --- a/api/http/handler/helm/helm_list_test.go +++ b/api/http/handler/helm/helm_list_test.go @@ -24,7 +24,7 @@ import ( func Test_helmList(t *testing.T) { is := assert.New(t) - _, store, teardown := datastore.MustNewTestStore(true) + _, store, teardown := datastore.MustNewTestStore(true, true) defer teardown() err := store.Endpoint().Create(&portainer.Endpoint{ID: 1}) diff --git a/api/http/handler/stacks/webhook_invoke_test.go b/api/http/handler/stacks/webhook_invoke_test.go index ccb4161e7..802019ba2 100644 --- a/api/http/handler/stacks/webhook_invoke_test.go +++ b/api/http/handler/stacks/webhook_invoke_test.go @@ -13,7 +13,7 @@ import ( ) func TestHandler_webhookInvoke(t *testing.T) { - _, store, teardown := datastore.MustNewTestStore(true) + _, store, teardown := datastore.MustNewTestStore(true, true) defer teardown() webhookID := newGuidString(t) diff --git a/api/http/handler/users/user_create_access_token_test.go b/api/http/handler/users/user_create_access_token_test.go index 56e2e074c..283ab2bdd 100644 --- a/api/http/handler/users/user_create_access_token_test.go +++ b/api/http/handler/users/user_create_access_token_test.go @@ -21,7 +21,7 @@ import ( func Test_userCreateAccessToken(t *testing.T) { is := assert.New(t) - _, store, teardown := datastore.MustNewTestStore(true) + _, store, teardown := datastore.MustNewTestStore(true, true) defer teardown() // create admin and standard user(s) diff --git a/api/http/handler/users/user_delete_test.go b/api/http/handler/users/user_delete_test.go index 2dc2aced9..6ed52a28d 100644 --- a/api/http/handler/users/user_delete_test.go +++ b/api/http/handler/users/user_delete_test.go @@ -17,7 +17,7 @@ import ( func Test_deleteUserRemovesAccessTokens(t *testing.T) { is := assert.New(t) - _, store, teardown := datastore.MustNewTestStore(true) + _, store, teardown := datastore.MustNewTestStore(true, true) defer teardown() // create standard user diff --git a/api/http/handler/users/user_get_access_tokens_test.go b/api/http/handler/users/user_get_access_tokens_test.go index 3ea2013d3..5a258eab0 100644 --- a/api/http/handler/users/user_get_access_tokens_test.go +++ b/api/http/handler/users/user_get_access_tokens_test.go @@ -20,7 +20,7 @@ import ( func Test_userGetAccessTokens(t *testing.T) { is := assert.New(t) - _, store, teardown := datastore.MustNewTestStore(true) + _, store, teardown := datastore.MustNewTestStore(true, true) defer teardown() // create admin and standard user(s) diff --git a/api/http/handler/users/user_remove_access_token_test.go b/api/http/handler/users/user_remove_access_token_test.go index f6ee4c101..2b3edbdad 100644 --- a/api/http/handler/users/user_remove_access_token_test.go +++ b/api/http/handler/users/user_remove_access_token_test.go @@ -18,7 +18,7 @@ import ( func Test_userRemoveAccessToken(t *testing.T) { is := assert.New(t) - _, store, teardown := datastore.MustNewTestStore(true) + _, store, teardown := datastore.MustNewTestStore(true, true) defer teardown() // create admin and standard user(s) diff --git a/api/http/handler/users/user_update_test.go b/api/http/handler/users/user_update_test.go index 3588f9ad3..dd6f31596 100644 --- a/api/http/handler/users/user_update_test.go +++ b/api/http/handler/users/user_update_test.go @@ -17,7 +17,7 @@ import ( func Test_updateUserRemovesAccessTokens(t *testing.T) { is := assert.New(t) - _, store, teardown := datastore.MustNewTestStore(true) + _, store, teardown := datastore.MustNewTestStore(true, true) defer teardown() // create standard user diff --git a/api/http/security/bouncer_test.go b/api/http/security/bouncer_test.go index d749522b7..f73512fdd 100644 --- a/api/http/security/bouncer_test.go +++ b/api/http/security/bouncer_test.go @@ -36,7 +36,7 @@ func tokenLookupFail(r *http.Request) *portainer.TokenData { func Test_mwAuthenticateFirst(t *testing.T) { is := assert.New(t) - _, store, teardown := datastore.MustNewTestStore(true) + _, store, teardown := datastore.MustNewTestStore(true, true) defer teardown() jwtService, err := jwt.NewService("1h", store) @@ -259,7 +259,7 @@ func Test_extractAPIKeyQueryParam(t *testing.T) { func Test_apiKeyLookup(t *testing.T) { is := assert.New(t) - _, store, teardown := datastore.MustNewTestStore(true) + _, store, teardown := datastore.MustNewTestStore(true, true) defer teardown() // create standard user diff --git a/api/portainer.go b/api/portainer.go index aed012f26..ff7f88981 100644 --- a/api/portainer.go +++ b/api/portainer.go @@ -559,13 +559,13 @@ type ( // LDAPSettings represents the settings used to connect to a LDAP server LDAPSettings struct { // Enable this option if the server is configured for Anonymous access. When enabled, ReaderDN and Password will not be used - AnonymousMode bool `json:"AnonymousMode" example:"true"` + AnonymousMode bool `json:"AnonymousMode" example:"true" validate:"validate_bool"` // Account that will be used to search for users - ReaderDN string `json:"ReaderDN" example:"cn=readonly-account,dc=ldap,dc=domain,dc=tld"` + ReaderDN string `json:"ReaderDN" example:"cn=readonly-account,dc=ldap,dc=domain,dc=tld" validate:"required_if=AnonymousMode false"` // Password of the account that will be used to search users - Password string `json:"Password,omitempty" example:"readonly-password"` + Password string `json:"Password,omitempty" example:"readonly-password" validate:"required_if=AnonymousMode false"` // URL or IP address of the LDAP server - URL string `json:"URL" example:"myldap.domain.tld:389"` + URL string `json:"URL" example:"myldap.domain.tld:389" validate:"hostname_port"` TLSConfig TLSConfiguration `json:"TLSConfig"` // Whether LDAP connection should use StartTLS StartTLS bool `json:"StartTLS" example:"true"` diff --git a/api/stacks/deploy_test.go b/api/stacks/deploy_test.go index 3620e53c4..3402c3b7a 100644 --- a/api/stacks/deploy_test.go +++ b/api/stacks/deploy_test.go @@ -41,7 +41,7 @@ func (s *noopDeployer) DeployKubernetesStack(stack *portainer.Stack, endpoint *p } func Test_redeployWhenChanged_FailsWhenCannotFindStack(t *testing.T) { - _, store, teardown := datastore.MustNewTestStore(true) + _, store, teardown := datastore.MustNewTestStore(true, true) defer teardown() err := RedeployWhenChanged(1, nil, store, nil) @@ -50,7 +50,7 @@ func Test_redeployWhenChanged_FailsWhenCannotFindStack(t *testing.T) { } func Test_redeployWhenChanged_DoesNothingWhenNotAGitBasedStack(t *testing.T) { - _, store, teardown := datastore.MustNewTestStore(true) + _, store, teardown := datastore.MustNewTestStore(true, true) defer teardown() admin := &portainer.User{ID: 1, Username: "admin"} @@ -65,7 +65,7 @@ func Test_redeployWhenChanged_DoesNothingWhenNotAGitBasedStack(t *testing.T) { } func Test_redeployWhenChanged_DoesNothingWhenNoGitChanges(t *testing.T) { - _, store, teardown := datastore.MustNewTestStore(true) + _, store, teardown := datastore.MustNewTestStore(true, true) defer teardown() tmpDir, _ := ioutil.TempDir("", "stack") @@ -91,7 +91,7 @@ func Test_redeployWhenChanged_DoesNothingWhenNoGitChanges(t *testing.T) { func Test_redeployWhenChanged_FailsWhenCannotClone(t *testing.T) { cloneErr := errors.New("failed to clone") - _, store, teardown := datastore.MustNewTestStore(true) + _, store, teardown := datastore.MustNewTestStore(true, true) defer teardown() admin := &portainer.User{ID: 1, Username: "admin"} @@ -114,7 +114,7 @@ func Test_redeployWhenChanged_FailsWhenCannotClone(t *testing.T) { } func Test_redeployWhenChanged(t *testing.T) { - _, store, teardown := datastore.MustNewTestStore(true) + _, store, teardown := datastore.MustNewTestStore(true, true) defer teardown() tmpDir, _ := ioutil.TempDir("", "stack") @@ -165,7 +165,7 @@ func Test_redeployWhenChanged(t *testing.T) { } func Test_getUserRegistries(t *testing.T) { - _, store, teardown := datastore.MustNewTestStore(true) + _, store, teardown := datastore.MustNewTestStore(true, true) defer teardown() endpointID := 123