Compare commits
11 Commits
refactor/E
...
feat/suppo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b5133e3aa6 | ||
|
|
4f0e09fad8 | ||
|
|
428d2a03bf | ||
|
|
82a13560b3 | ||
|
|
6f354833b7 | ||
|
|
ad5ece52b1 | ||
|
|
7ddd02bf91 | ||
|
|
71c8932f76 | ||
|
|
eab2ae6230 | ||
|
|
133bd15dad | ||
|
|
b93a11b4e5 |
@@ -1,5 +1,4 @@
|
||||
# prettier
|
||||
cf5056d9c03b62d91a25c3b9127caac838695f98
|
||||
|
||||
# prettier v2
|
||||
42e7db0ae7897d3cb72b0ea1ecf57ee2dd694169
|
||||
# prettier v2 (put here after fix/EE-2344/fix-eslint-issues is merged)
|
||||
2
.github/ISSUE_TEMPLATE.md
vendored
2
.github/ISSUE_TEMPLATE.md
vendored
@@ -2,7 +2,7 @@
|
||||
|
||||
Thanks for opening an issue on Portainer !
|
||||
|
||||
Do you need help or have a question? Come chat with us on Slack https://portainer.io/slack/ or gitter https://gitter.im/portainer/Lobby.
|
||||
Do you need help or have a question? Come chat with us on Slack http://portainer.io/slack/ or gitter https://gitter.im/portainer/Lobby.
|
||||
|
||||
If you are reporting a new issue, make sure that we do not have any duplicates
|
||||
already open. You can ensure this by searching the issue list for this
|
||||
|
||||
4
.github/ISSUE_TEMPLATE/Bug_report.md
vendored
4
.github/ISSUE_TEMPLATE/Bug_report.md
vendored
@@ -12,7 +12,7 @@ Thanks for reporting a bug for Portainer !
|
||||
|
||||
You can find more information about Portainer support framework policy here: https://www.portainer.io/2019/04/portainer-support-policy/
|
||||
|
||||
Do you need help or have a question? Come chat with us on Slack https://portainer.io/slack/
|
||||
Do you need help or have a question? Come chat with us on Slack http://portainer.slack.com/
|
||||
|
||||
Before opening a new issue, make sure that we do not have any duplicates
|
||||
already open. You can ensure this by searching the issue list for this
|
||||
@@ -47,7 +47,7 @@ You can see how [here](https://documentation.portainer.io/r/portainer-logs)
|
||||
- Platform (windows/linux):
|
||||
- Command used to start Portainer (`docker run -p 9443:9443 portainer/portainer`):
|
||||
- Browser:
|
||||
- Use Case (delete as appropriate): Using Portainer at Home, Using Portainer in a Commercial setup.
|
||||
- Use Case (delete as appropriate): Using Portainer at Home, Using Portainer in a Commerical setup.
|
||||
- Have you reviewed our technical documentation and knowledge base? Yes/No
|
||||
|
||||
**Additional context**
|
||||
|
||||
6
.github/ISSUE_TEMPLATE/Custom.md
vendored
6
.github/ISSUE_TEMPLATE/Custom.md
vendored
@@ -4,11 +4,11 @@ about: Ask us a question about Portainer usage or deployment
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
---
|
||||
Before you start, we need a little bit more information from you:
|
||||
|
||||
Use Case (delete as appropriate): Using Portainer at Home, Using Portainer in a Commercial setup.
|
||||
Use Case (delete as appropriate): Using Portainer at Home, Using Portainer in a Commerical setup.
|
||||
|
||||
Have you reviewed our technical documentation and knowledge base? Yes/No
|
||||
|
||||
@@ -16,7 +16,7 @@ Have you reviewed our technical documentation and knowledge base? Yes/No
|
||||
|
||||
You can find more information about Portainer support framework policy here: https://old.portainer.io/2019/04/portainer-support-policy/
|
||||
|
||||
Do you need help or have a question? Come chat with us on Slack https://portainer.io/slack/
|
||||
Do you need help or have a question? Come chat with us on Slack http://portainer.slack.com/
|
||||
|
||||
Also, be sure to check our FAQ and documentation first: https://documentation.portainer.io/
|
||||
-->
|
||||
|
||||
3
.github/ISSUE_TEMPLATE/Feature_request.md
vendored
3
.github/ISSUE_TEMPLATE/Feature_request.md
vendored
@@ -4,13 +4,14 @@ about: Suggest a feature/enhancement that should be added in Portainer
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
|
||||
Thanks for opening a feature request for Portainer !
|
||||
|
||||
Do you need help or have a question? Come chat with us on Slack https://portainer.io/slack/
|
||||
Do you need help or have a question? Come chat with us on Slack http://portainer.slack.com/
|
||||
|
||||
Before opening a new issue, make sure that we do not have any duplicates
|
||||
already open. You can ensure this by searching the issue list for this
|
||||
|
||||
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
@@ -28,7 +28,7 @@ jobs:
|
||||
|
||||
# ESLint and Prettier must be in `package.json`
|
||||
- name: Install Node.js dependencies
|
||||
run: yarn --frozen-lockfile
|
||||
run: yarn
|
||||
|
||||
- name: Run linters
|
||||
uses: wearerequired/lint-action@v1
|
||||
|
||||
2
.github/workflows/test-client.yaml
vendored
2
.github/workflows/test-client.yaml
vendored
@@ -6,6 +6,6 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install modules
|
||||
run: yarn --frozen-lockfile
|
||||
run: yarn
|
||||
- name: Run tests
|
||||
run: yarn test:client
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "debug",
|
||||
"program": "${workspaceRoot}/api/cmd/portainer",
|
||||
"program": "${workspaceRoot}/api/cmd/portainer/main.go",
|
||||
"cwd": "${workspaceRoot}",
|
||||
"env": {},
|
||||
"showLog": true,
|
||||
|
||||
@@ -4,5 +4,5 @@
|
||||
"gopls": {
|
||||
"build.expandWorkspaceToModule": false
|
||||
},
|
||||
"gitlens.advanced.blame.customArguments": ["--ignore-revs-file", ".git-blame-ignore-revs"]
|
||||
"gitlens.advanced.blame.customArguments": ["–ignore-revs-file", ".git-blame-ignore-revs"]
|
||||
}
|
||||
|
||||
@@ -42,10 +42,10 @@ View [this](https://www.portainer.io/products) table to see all of the Portainer
|
||||
|
||||
Portainer CE is an open source project and is supported by the community. You can buy a supported version of Portainer at portainer.io
|
||||
|
||||
Learn more about Portainer's community support channels [here.](https://www.portainer.io/community_help)
|
||||
Learn more about Portainers community support channels [here.](https://www.portainer.io/community_help)
|
||||
|
||||
- Issues: https://github.com/portainer/portainer/issues
|
||||
- Slack (chat): [https://portainer.io/slack](https://portainer.io/slack)
|
||||
- Slack (chat): [https://portainer.slack.com/](https://join.slack.com/t/portainer/shared_invite/zt-txh3ljab-52QHTyjCqbe5RibC2lcjKA)
|
||||
|
||||
You can join the Portainer Community by visiting community.portainer.io. This will give you advance notice of events, content and other related Portainer content.
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@ func (*Service) ParseFlags(version string) (*portainer.CLIFlags, error) {
|
||||
HTTPDisabled: kingpin.Flag("http-disabled", "Serve portainer only on https").Default(defaultHTTPDisabled).Bool(),
|
||||
HTTPEnabled: kingpin.Flag("http-enabled", "Serve portainer on http").Default(defaultHTTPEnabled).Bool(),
|
||||
SSL: kingpin.Flag("ssl", "Secure Portainer instance using SSL (deprecated)").Default(defaultSSL).Bool(),
|
||||
SSLCacert: kingpin.Flag("sslcacert", "Path to the SSL CA certificate used to validate the edge agent cert").String(),
|
||||
SSLCert: kingpin.Flag("sslcert", "Path to the SSL certificate used to secure the Portainer instance").String(),
|
||||
SSLKey: kingpin.Flag("sslkey", "Path to the SSL key used to secure the Portainer instance").String(),
|
||||
Rollback: kingpin.Flag("rollback", "Rollback the database store to the previous version").Bool(),
|
||||
|
||||
@@ -18,6 +18,7 @@ const (
|
||||
defaultHTTPDisabled = "false"
|
||||
defaultHTTPEnabled = "false"
|
||||
defaultSSL = "false"
|
||||
defaultSSLCacertPath = "/certs/portainer-ca.crt"
|
||||
defaultSSLCertPath = "/certs/portainer.crt"
|
||||
defaultSSLKeyPath = "/certs/portainer.key"
|
||||
defaultBaseURL = "/"
|
||||
|
||||
@@ -15,6 +15,7 @@ const (
|
||||
defaultHTTPDisabled = "false"
|
||||
defaultHTTPEnabled = "false"
|
||||
defaultSSL = "false"
|
||||
defaultSSLCacertPath = "C:\\certs\\portainer-ca.crt"
|
||||
defaultSSLCertPath = "C:\\certs\\portainer.crt"
|
||||
defaultSSLKeyPath = "C:\\certs\\portainer.key"
|
||||
defaultSnapshotInterval = "5m"
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
@@ -12,30 +10,14 @@ type portainerFormatter struct {
|
||||
logrus.TextFormatter
|
||||
}
|
||||
|
||||
func (f *portainerFormatter) Format(entry *logrus.Entry) ([]byte, error) {
|
||||
var levelColor int
|
||||
switch entry.Level {
|
||||
case logrus.DebugLevel, logrus.TraceLevel:
|
||||
levelColor = 31 // gray
|
||||
case logrus.WarnLevel:
|
||||
levelColor = 33 // yellow
|
||||
case logrus.ErrorLevel, logrus.FatalLevel, logrus.PanicLevel:
|
||||
levelColor = 31 // red
|
||||
default:
|
||||
levelColor = 36 // blue
|
||||
}
|
||||
return []byte(fmt.Sprintf("\x1b[%dm%s\x1b[0m %s %s\n", levelColor, strings.ToUpper(entry.Level.String()), entry.Time.Format(f.TimestampFormat), entry.Message)), nil
|
||||
}
|
||||
|
||||
func configureLogger() {
|
||||
logger := logrus.New() // logger is to implicitly substitute stdlib's log
|
||||
log.SetOutput(logger.Writer())
|
||||
|
||||
formatter := &logrus.TextFormatter{DisableTimestamp: true, DisableLevelTruncation: true}
|
||||
formatterLogrus := &portainerFormatter{logrus.TextFormatter{DisableTimestamp: false, DisableLevelTruncation: true, TimestampFormat: "2006/01/02 15:04:05", FullTimestamp: true}}
|
||||
formatter := &logrus.TextFormatter{DisableTimestamp: false, DisableLevelTruncation: true}
|
||||
|
||||
logger.SetFormatter(formatter)
|
||||
logrus.SetFormatter(formatterLogrus)
|
||||
logrus.SetFormatter(formatter)
|
||||
|
||||
logger.SetLevel(logrus.DebugLevel)
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
|
||||
@@ -23,7 +23,6 @@ import (
|
||||
"github.com/portainer/portainer/api/database/boltdb"
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
"github.com/portainer/portainer/api/datastore"
|
||||
"github.com/portainer/portainer/api/datastore/migrations"
|
||||
"github.com/portainer/portainer/api/docker"
|
||||
"github.com/portainer/portainer/api/exec"
|
||||
"github.com/portainer/portainer/api/filesystem"
|
||||
@@ -119,20 +118,13 @@ func initDataStore(flags *portainer.CLIFlags, secretKey []byte, fileService port
|
||||
logrus.Fatalf("Something Failed during creation of new database: %v", err)
|
||||
}
|
||||
if storedVersion != portainer.DBVersion {
|
||||
m := migrations.NewMigrator(*store)
|
||||
err = m.Migrate(storedVersion)
|
||||
err = store.MigrateData()
|
||||
if err != nil {
|
||||
logrus.Fatalf("Failed migration: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m := migrations.NewMigrator(*store)
|
||||
|
||||
m.Migrate(18)
|
||||
|
||||
logrus.Fatal("Dieing.....")
|
||||
|
||||
err = updateSettingsFromFlags(store, flags)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed updating settings from flags: %v", err)
|
||||
@@ -216,7 +208,7 @@ func initGitService() portainer.GitService {
|
||||
return git.NewService()
|
||||
}
|
||||
|
||||
func initSSLService(addr, dataPath, certPath, keyPath string, fileService portainer.FileService, dataStore dataservices.DataStore, shutdownTrigger context.CancelFunc) (*ssl.Service, error) {
|
||||
func initSSLService(addr, dataPath, certPath, keyPath, cacertPath string, fileService portainer.FileService, dataStore dataservices.DataStore, shutdownTrigger context.CancelFunc) (*ssl.Service, error) {
|
||||
slices := strings.Split(addr, ":")
|
||||
host := slices[0]
|
||||
if host == "" {
|
||||
@@ -225,7 +217,7 @@ func initSSLService(addr, dataPath, certPath, keyPath string, fileService portai
|
||||
|
||||
sslService := ssl.NewService(fileService, dataStore, shutdownTrigger)
|
||||
|
||||
err := sslService.Init(host, certPath, keyPath)
|
||||
err := sslService.Init(host, certPath, keyPath, cacertPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -576,7 +568,7 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
|
||||
cryptoService := initCryptoService()
|
||||
digitalSignatureService := initDigitalSignatureService()
|
||||
|
||||
sslService, err := initSSLService(*flags.AddrHTTPS, *flags.Data, *flags.SSLCert, *flags.SSLKey, fileService, dataStore, shutdownTrigger)
|
||||
sslService, err := initSSLService(*flags.AddrHTTPS, *flags.Data, *flags.SSLCert, *flags.SSLKey, *flags.SSLCacert, fileService, dataStore, shutdownTrigger)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -33,7 +33,4 @@ type Connection interface {
|
||||
GetAll(bucketName string, obj interface{}, append func(o interface{}) (interface{}, error)) error
|
||||
GetAllWithJsoniter(bucketName string, obj interface{}, append func(o interface{}) (interface{}, error)) error
|
||||
ConvertToKey(v int) []byte
|
||||
|
||||
BackupMetadata() (map[string]interface{}, error)
|
||||
RestoreMetadata(s map[string]interface{}) error
|
||||
}
|
||||
|
||||
@@ -376,43 +376,3 @@ func (connection *DbConnection) GetAllWithJsoniter(bucketName string, obj interf
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (connection *DbConnection) BackupMetadata() (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
|
||||
}
|
||||
|
||||
func (connection *DbConnection) RestoreMetadata(s map[string]interface{}) error {
|
||||
var err error
|
||||
|
||||
for bucketName, v := range s {
|
||||
id, ok := v.(float64) // JSON ints are unmarshalled to interface as float64. See: https://pkg.go.dev/encoding/json#Decoder.Decode
|
||||
if !ok {
|
||||
logrus.Errorf("Failed to restore metadata to bucket %s, skipped", bucketName)
|
||||
continue
|
||||
}
|
||||
|
||||
err = connection.Batch(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(bucketName))
|
||||
if bucket == nil {
|
||||
return nil
|
||||
}
|
||||
return bucket.SetSequence(uint64(id))
|
||||
})
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package fdoprofile
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/datastore/migrations/types"
|
||||
)
|
||||
|
||||
func init() {
|
||||
migrator.AddMigration(types.Migration{
|
||||
Version: 17,
|
||||
Timestamp: 1645580390,
|
||||
Up: v17_up_users_to_18,
|
||||
Down: v17_down_users_from_18,
|
||||
Name: "Users to 18",
|
||||
})
|
||||
}
|
||||
|
||||
func v17_up_users_to_18() error {
|
||||
legacyUsers, err := migrator.store.UserService.Users()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, user := range legacyUsers {
|
||||
user.PortainerAuthorizations = map[portainer.Authorization]bool{
|
||||
portainer.OperationPortainerDockerHubInspect: true,
|
||||
portainer.OperationPortainerEndpointGroupList: true,
|
||||
portainer.OperationPortainerEndpointList: true,
|
||||
portainer.OperationPortainerEndpointInspect: true,
|
||||
portainer.OperationPortainerEndpointExtensionAdd: true,
|
||||
portainer.OperationPortainerEndpointExtensionRemove: true,
|
||||
portainer.OperationPortainerExtensionList: true,
|
||||
portainer.OperationPortainerMOTD: true,
|
||||
portainer.OperationPortainerRegistryList: true,
|
||||
portainer.OperationPortainerRegistryInspect: true,
|
||||
portainer.OperationPortainerTeamList: true,
|
||||
portainer.OperationPortainerTemplateList: true,
|
||||
portainer.OperationPortainerTemplateInspect: true,
|
||||
portainer.OperationPortainerUserList: true,
|
||||
portainer.OperationPortainerUserMemberships: true,
|
||||
}
|
||||
|
||||
err = migrator.store.UserService.UpdateUser(user.ID, &user)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func v17_down_users_from_18() error {
|
||||
return nil
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/datastore/migrations/types"
|
||||
)
|
||||
|
||||
func init() {
|
||||
migrator.AddMigration(types.Migration{
|
||||
Version: 17,
|
||||
Timestamp: 1645580396,
|
||||
Up: v17_up_endpoints_to_18,
|
||||
Down: v17_down_endpoints_from_18,
|
||||
Name: "Endpoints to 18",
|
||||
})
|
||||
}
|
||||
|
||||
func v17_up_endpoints_to_18() error {
|
||||
legacyEndpoints, err := migrator.store.EndpointService.Endpoints()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, endpoint := range legacyEndpoints {
|
||||
endpoint.UserAccessPolicies = make(portainer.UserAccessPolicies)
|
||||
for _, userID := range endpoint.AuthorizedUsers {
|
||||
endpoint.UserAccessPolicies[userID] = portainer.AccessPolicy{
|
||||
RoleID: 4,
|
||||
}
|
||||
}
|
||||
|
||||
endpoint.TeamAccessPolicies = make(portainer.TeamAccessPolicies)
|
||||
for _, teamID := range endpoint.AuthorizedTeams {
|
||||
endpoint.TeamAccessPolicies[teamID] = portainer.AccessPolicy{
|
||||
RoleID: 4,
|
||||
}
|
||||
}
|
||||
|
||||
err = migrator.store.EndpointService.UpdateEndpoint(endpoint.ID, &endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func v17_down_endpoints_from_18() error {
|
||||
return nil
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/datastore/migrations/types"
|
||||
)
|
||||
|
||||
func init() {
|
||||
migrator.AddMigration(types.Migration{
|
||||
Version: 17,
|
||||
Timestamp: 1645580400,
|
||||
Up: v17_up_endpoints_groups_to_18,
|
||||
Down: v17_down_endpoints_groups_from_18,
|
||||
Name: "Endpoints groups to 18",
|
||||
})
|
||||
}
|
||||
|
||||
func v17_up_endpoints_groups_to_18() error {
|
||||
legacyEndpointGroups, err := migrator.store.EndpointGroupService.EndpointGroups()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, endpointGroup := range legacyEndpointGroups {
|
||||
endpointGroup.UserAccessPolicies = make(portainer.UserAccessPolicies)
|
||||
for _, userID := range endpointGroup.AuthorizedUsers {
|
||||
endpointGroup.UserAccessPolicies[userID] = portainer.AccessPolicy{
|
||||
RoleID: 4,
|
||||
}
|
||||
}
|
||||
|
||||
endpointGroup.TeamAccessPolicies = make(portainer.TeamAccessPolicies)
|
||||
for _, teamID := range endpointGroup.AuthorizedTeams {
|
||||
endpointGroup.TeamAccessPolicies[teamID] = portainer.AccessPolicy{
|
||||
RoleID: 4,
|
||||
}
|
||||
}
|
||||
|
||||
err = migrator.store.EndpointGroupService.UpdateEndpointGroup(endpointGroup.ID, &endpointGroup)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func v17_down_endpoints_groups_from_18() error {
|
||||
return nil
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/datastore/migrations/types"
|
||||
)
|
||||
|
||||
func init() {
|
||||
migrator.AddMigration(types.Migration{
|
||||
Version: 17,
|
||||
Timestamp: 1645580420,
|
||||
Up: v17_up_registries_to_18,
|
||||
Down: v17_down_registries_to_18,
|
||||
Name: "Registries to 18",
|
||||
})
|
||||
}
|
||||
|
||||
func v17_up_registries_to_18() error {
|
||||
legacyRegistries, err := migrator.store.RegistryService.Registries()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, registry := range legacyRegistries {
|
||||
registry.UserAccessPolicies = make(portainer.UserAccessPolicies)
|
||||
for _, userID := range registry.AuthorizedUsers {
|
||||
registry.UserAccessPolicies[userID] = portainer.AccessPolicy{}
|
||||
}
|
||||
|
||||
registry.TeamAccessPolicies = make(portainer.TeamAccessPolicies)
|
||||
for _, teamID := range registry.AuthorizedTeams {
|
||||
registry.TeamAccessPolicies[teamID] = portainer.AccessPolicy{}
|
||||
}
|
||||
|
||||
err = migrator.store.RegistryService.UpdateRegistry(registry.ID, ®istry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func v17_down_registries_to_18() error {
|
||||
return nil
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/datastore/migrations/types"
|
||||
)
|
||||
|
||||
func init() {
|
||||
migrator.AddMigration(types.Migration{
|
||||
Version: 18,
|
||||
Timestamp: 1645677508,
|
||||
Up: v18_up_settings_to_db_19,
|
||||
Down: v18_down_settings_to_db_19,
|
||||
Name: "settings to db 19",
|
||||
})
|
||||
}
|
||||
|
||||
func v18_up_settings_to_db_19() error {
|
||||
legacySettings, err := migrator.store.SettingsService.Settings()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if legacySettings.EdgeAgentCheckinInterval == 0 {
|
||||
legacySettings.EdgeAgentCheckinInterval = portainer.DefaultEdgeAgentCheckinIntervalInSeconds
|
||||
}
|
||||
|
||||
return migrator.store.SettingsService.UpdateSettings(legacySettings)
|
||||
}
|
||||
|
||||
func v18_down_settings_to_db_19() error {
|
||||
return nil
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"github.com/portainer/portainer/api/datastore/migrations/types"
|
||||
)
|
||||
|
||||
func init() {
|
||||
migrator.AddMigration(types.Migration{
|
||||
Version: 19,
|
||||
Timestamp: 1645736810,
|
||||
Up: v19_up_users_to_db_20,
|
||||
Down: v19_down_users_to_db_20,
|
||||
Name: "users to db 20",
|
||||
})
|
||||
}
|
||||
|
||||
func v19_up_users_to_db_20() error {
|
||||
return migrator.store.AuthorizationService.UpdateUsersAuthorizations()
|
||||
}
|
||||
|
||||
func v19_down_users_to_db_20() error {
|
||||
return nil
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"github.com/portainer/portainer/api/datastore/migrations/types"
|
||||
)
|
||||
|
||||
func init() {
|
||||
migrator.AddMigration(types.Migration{
|
||||
Version: 19,
|
||||
Timestamp: 1645737700,
|
||||
Up: v19_up_settings_to_db_20,
|
||||
Down: v19_down_settings_to_db_20,
|
||||
Name: "settings to db 20",
|
||||
})
|
||||
}
|
||||
|
||||
func v19_up_settings_to_db_20() error {
|
||||
legacySettings, err := migrator.store.SettingsService.Settings()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
legacySettings.AllowVolumeBrowserForRegularUsers = false
|
||||
|
||||
return migrator.store.SettingsService.UpdateSettings(legacySettings)
|
||||
}
|
||||
|
||||
func v19_down_settings_to_db_20() error {
|
||||
return nil
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/portainer/portainer/api/datastore/migrations/types"
|
||||
)
|
||||
|
||||
const scheduleScriptExecutionJobType = 1
|
||||
|
||||
func init() {
|
||||
migrator.AddMigration(types.Migration{
|
||||
Version: 19,
|
||||
Timestamp: 1645737802,
|
||||
Up: v19_up_schedules_to_db_20,
|
||||
Down: v19_down_schedules_to_db_20,
|
||||
Name: "schedules to db 20",
|
||||
})
|
||||
}
|
||||
|
||||
func v19_up_schedules_to_db_20() error {
|
||||
legacySchedules, err := migrator.store.ScheduleService.Schedules()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, schedule := range legacySchedules {
|
||||
if schedule.JobType == scheduleScriptExecutionJobType {
|
||||
if schedule.CronExpression == "0 0 * * *" {
|
||||
schedule.CronExpression = "0 * * * *"
|
||||
} else if schedule.CronExpression == "0 0 0/2 * *" {
|
||||
schedule.CronExpression = "0 */2 * * *"
|
||||
} else if schedule.CronExpression == "0 0 0 * *" {
|
||||
schedule.CronExpression = "0 0 * * *"
|
||||
} else {
|
||||
revisedCronExpression := strings.Split(schedule.CronExpression, " ")
|
||||
if len(revisedCronExpression) == 5 {
|
||||
continue
|
||||
}
|
||||
|
||||
revisedCronExpression = revisedCronExpression[1:]
|
||||
schedule.CronExpression = strings.Join(revisedCronExpression, " ")
|
||||
}
|
||||
|
||||
err := migrator.store.ScheduleService.UpdateSchedule(schedule.ID, &schedule)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func v19_down_schedules_to_db_20() error {
|
||||
return nil
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"github.com/portainer/portainer/api/datastore/migrations/types"
|
||||
)
|
||||
|
||||
func init() {
|
||||
migrator.AddMigration(types.Migration{
|
||||
Version: 20,
|
||||
Timestamp: 1646090591,
|
||||
Up: v20_up_resource_control_to_22,
|
||||
Down: v20_down_resource_control_to_22,
|
||||
Name: "resource control to 22",
|
||||
})
|
||||
}
|
||||
|
||||
func v20_up_resource_control_to_22() error {
|
||||
legacyResourceControls, err := migrator.store.ResourceControlService.ResourceControls()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, resourceControl := range legacyResourceControls {
|
||||
resourceControl.AdministratorsOnly = false
|
||||
|
||||
err := migrator.store.ResourceControlService.UpdateResourceControl(resourceControl.ID, &resourceControl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func v20_down_resource_control_to_22() error {
|
||||
return nil
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/datastore/migrations/types"
|
||||
"github.com/portainer/portainer/api/internal/authorization"
|
||||
)
|
||||
|
||||
func init() {
|
||||
migrator.AddMigration(types.Migration{
|
||||
Version: 20,
|
||||
Timestamp: 1646090646,
|
||||
Up: v20_up_user_and_roles_to_22,
|
||||
Down: v20_down_user_and_roles_to_22,
|
||||
Name: "user and roles to 22",
|
||||
})
|
||||
}
|
||||
|
||||
func v20_up_user_and_roles_to_22() error {
|
||||
legacyUsers, err := migrator.store.UserService.Users()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings, err := migrator.store.SettingsService.Settings()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, user := range legacyUsers {
|
||||
user.PortainerAuthorizations = authorization.DefaultPortainerAuthorizations()
|
||||
err = migrator.store.UserService.UpdateUser(user.ID, &user)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
endpointAdministratorRole, err := migrator.store.RoleService.Role(portainer.RoleID(1))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
endpointAdministratorRole.Priority = 1
|
||||
endpointAdministratorRole.Authorizations = authorization.DefaultEndpointAuthorizationsForEndpointAdministratorRole()
|
||||
|
||||
err = migrator.store.RoleService.UpdateRole(endpointAdministratorRole.ID, endpointAdministratorRole)
|
||||
|
||||
helpDeskRole, err := migrator.store.RoleService.Role(portainer.RoleID(2))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
helpDeskRole.Priority = 2
|
||||
helpDeskRole.Authorizations = authorization.DefaultEndpointAuthorizationsForHelpDeskRole(settings.AllowVolumeBrowserForRegularUsers)
|
||||
|
||||
err = migrator.store.RoleService.UpdateRole(helpDeskRole.ID, helpDeskRole)
|
||||
|
||||
standardUserRole, err := migrator.store.RoleService.Role(portainer.RoleID(3))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
standardUserRole.Priority = 3
|
||||
standardUserRole.Authorizations = authorization.DefaultEndpointAuthorizationsForStandardUserRole(settings.AllowVolumeBrowserForRegularUsers)
|
||||
|
||||
err = migrator.store.RoleService.UpdateRole(standardUserRole.ID, standardUserRole)
|
||||
|
||||
readOnlyUserRole, err := migrator.store.RoleService.Role(portainer.RoleID(4))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
readOnlyUserRole.Priority = 4
|
||||
readOnlyUserRole.Authorizations = authorization.DefaultEndpointAuthorizationsForReadOnlyUserRole(settings.AllowVolumeBrowserForRegularUsers)
|
||||
|
||||
err = migrator.store.RoleService.UpdateRole(readOnlyUserRole.ID, readOnlyUserRole)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return migrator.store.AuthorizationService.UpdateUsersAuthorizations()
|
||||
}
|
||||
|
||||
func v20_down_user_and_roles_to_22() error {
|
||||
return nil
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/datastore/migrations/types"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func init() {
|
||||
migrator.AddMigration(types.Migration{
|
||||
Version: 22,
|
||||
Timestamp: 1646090838,
|
||||
Up: v22_up_tags_to_23,
|
||||
Down: v22_down_tags_to_23,
|
||||
Name: "tags to 23",
|
||||
})
|
||||
}
|
||||
|
||||
func v22_up_tags_to_23() error {
|
||||
logrus.Info("Updating tags")
|
||||
tags, err := migrator.store.TagService.Tags()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, tag := range tags {
|
||||
tag.EndpointGroups = make(map[portainer.EndpointGroupID]bool)
|
||||
tag.Endpoints = make(map[portainer.EndpointID]bool)
|
||||
err = migrator.store.TagService.UpdateTag(tag.ID, &tag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func v22_down_tags_to_23() error {
|
||||
return nil
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/datastore/migrations/types"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func init() {
|
||||
migrator.AddMigration(types.Migration{
|
||||
Version: 22,
|
||||
Timestamp: 1646091011,
|
||||
Up: v22_up_endpoints_and_endpoint_groups_to_23,
|
||||
Down: v22_down_endpoints_and_endpoint_groups_to_23,
|
||||
Name: "endpoints and endpoint groups to 23",
|
||||
})
|
||||
}
|
||||
|
||||
func v22_up_endpoints_and_endpoint_groups_to_23() error {
|
||||
logrus.Info("Updating endpoints and endpoint groups")
|
||||
tags, err := migrator.store.TagService.Tags()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tagsNameMap := make(map[string]portainer.Tag)
|
||||
for _, tag := range tags {
|
||||
tagsNameMap[tag.Name] = tag
|
||||
}
|
||||
|
||||
endpoints, err := migrator.store.EndpointService.Endpoints()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, endpoint := range endpoints {
|
||||
endpointTags := make([]portainer.TagID, 0)
|
||||
for _, tagName := range endpoint.Tags {
|
||||
tag, ok := tagsNameMap[tagName]
|
||||
if ok {
|
||||
endpointTags = append(endpointTags, tag.ID)
|
||||
tag.Endpoints[endpoint.ID] = true
|
||||
}
|
||||
}
|
||||
endpoint.TagIDs = endpointTags
|
||||
err = migrator.store.EndpointService.UpdateEndpoint(endpoint.ID, &endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
relation := &portainer.EndpointRelation{
|
||||
EndpointID: endpoint.ID,
|
||||
EdgeStacks: map[portainer.EdgeStackID]bool{},
|
||||
}
|
||||
|
||||
err = migrator.store.EndpointRelationService.Create(relation)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
endpointGroups, err := migrator.store.EndpointGroupService.EndpointGroups()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, endpointGroup := range endpointGroups {
|
||||
endpointGroupTags := make([]portainer.TagID, 0)
|
||||
for _, tagName := range endpointGroup.Tags {
|
||||
tag, ok := tagsNameMap[tagName]
|
||||
if ok {
|
||||
endpointGroupTags = append(endpointGroupTags, tag.ID)
|
||||
tag.EndpointGroups[endpointGroup.ID] = true
|
||||
}
|
||||
}
|
||||
endpointGroup.TagIDs = endpointGroupTags
|
||||
err = migrator.store.EndpointGroupService.UpdateEndpointGroup(endpointGroup.ID, &endpointGroup)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, tag := range tagsNameMap {
|
||||
err = migrator.store.TagService.UpdateTag(tag.ID, &tag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func v22_down_endpoints_and_endpoint_groups_to_23() error {
|
||||
return nil
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/datastore/migrations/types"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func init() {
|
||||
migrator.AddMigration(types.Migration{
|
||||
Version: 23,
|
||||
Timestamp: 1646091296,
|
||||
Up: v23_up_settings_to_24,
|
||||
Down: v23_down_settings_to_24,
|
||||
Name: "settings to 25",
|
||||
})
|
||||
}
|
||||
|
||||
func v23_up_settings_to_24() error {
|
||||
logrus.Info("Updating settings")
|
||||
|
||||
legacySettings, err := migrator.store.SettingsService.Settings()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if legacySettings.TemplatesURL == "" {
|
||||
legacySettings.TemplatesURL = portainer.DefaultTemplatesURL
|
||||
}
|
||||
|
||||
legacySettings.UserSessionTimeout = portainer.DefaultUserSessionTimeout
|
||||
legacySettings.EnableTelemetry = true
|
||||
|
||||
legacySettings.AllowContainerCapabilitiesForRegularUsers = true
|
||||
|
||||
return migrator.store.SettingsService.UpdateSettings(legacySettings)
|
||||
}
|
||||
|
||||
func v23_down_settings_to_24() error {
|
||||
return nil
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/datastore/migrations/types"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func init() {
|
||||
migrator.AddMigration(types.Migration{
|
||||
Version: 24,
|
||||
Timestamp: 1646095944,
|
||||
Up: v24_up_endpoint_settings_to_25,
|
||||
Down: v24_down_endpoint_settings_to_25,
|
||||
Name: "endpoint settings to 25",
|
||||
})
|
||||
}
|
||||
|
||||
func v24_up_endpoint_settings_to_25() error {
|
||||
logrus.Info("Updating endpoint settings")
|
||||
settings, err := migrator.store.SettingsService.Settings()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
endpoints, err := migrator.store.EndpointService.Endpoints()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i := range endpoints {
|
||||
endpoint := endpoints[i]
|
||||
|
||||
securitySettings := portainer.EndpointSecuritySettings{}
|
||||
|
||||
if endpoint.Type == portainer.EdgeAgentOnDockerEnvironment ||
|
||||
endpoint.Type == portainer.AgentOnDockerEnvironment ||
|
||||
endpoint.Type == portainer.DockerEnvironment {
|
||||
|
||||
securitySettings = portainer.EndpointSecuritySettings{
|
||||
AllowBindMountsForRegularUsers: settings.AllowBindMountsForRegularUsers,
|
||||
AllowContainerCapabilitiesForRegularUsers: settings.AllowContainerCapabilitiesForRegularUsers,
|
||||
AllowDeviceMappingForRegularUsers: settings.AllowDeviceMappingForRegularUsers,
|
||||
AllowHostNamespaceForRegularUsers: settings.AllowHostNamespaceForRegularUsers,
|
||||
AllowPrivilegedModeForRegularUsers: settings.AllowPrivilegedModeForRegularUsers,
|
||||
AllowStackManagementForRegularUsers: settings.AllowStackManagementForRegularUsers,
|
||||
}
|
||||
|
||||
if endpoint.Type == portainer.AgentOnDockerEnvironment || endpoint.Type == portainer.EdgeAgentOnDockerEnvironment {
|
||||
securitySettings.AllowVolumeBrowserForRegularUsers = settings.AllowVolumeBrowserForRegularUsers
|
||||
securitySettings.EnableHostManagementFeatures = settings.EnableHostManagementFeatures
|
||||
}
|
||||
}
|
||||
|
||||
endpoint.SecuritySettings = securitySettings
|
||||
|
||||
err = migrator.store.EndpointService.UpdateEndpoint(endpoint.ID, &endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func v24_down_endpoint_settings_to_25() error {
|
||||
return nil
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/dataservices/errors"
|
||||
"github.com/portainer/portainer/api/datastore/migrations/types"
|
||||
"github.com/portainer/portainer/api/internal/stackutils"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func init() {
|
||||
migrator.AddMigration(types.Migration{
|
||||
Version: 26,
|
||||
Timestamp: 1646096711,
|
||||
Up: v26_up_stack_resource_control_to_27,
|
||||
Down: v26_down_stack_resource_control_to_27,
|
||||
Name: "stack resource control to 27",
|
||||
})
|
||||
}
|
||||
|
||||
func v26_up_stack_resource_control_to_27() error {
|
||||
logrus.Info("Updating stack resource controls")
|
||||
resourceControls, err := migrator.store.ResourceControlService.ResourceControls()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, resource := range resourceControls {
|
||||
if resource.Type != portainer.StackResourceControl {
|
||||
continue
|
||||
}
|
||||
|
||||
stackName := resource.ResourceID
|
||||
|
||||
stack, err := migrator.store.StackService.StackByName(stackName)
|
||||
if err != nil {
|
||||
if err == errors.ErrObjectNotFound {
|
||||
continue
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
resource.ResourceID = stackutils.ResourceControlID(stack.EndpointID, stack.Name)
|
||||
|
||||
err = migrator.store.ResourceControlService.UpdateResourceControl(resource.ID, &resource)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func v26_down_stack_resource_control_to_27() error {
|
||||
return nil
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"github.com/portainer/portainer/api/datastore/migrations/types"
|
||||
)
|
||||
|
||||
func init() {
|
||||
migrator.AddMigration(types.Migration{
|
||||
Version: 29,
|
||||
Timestamp: 1646096869,
|
||||
Up: v29_up_settings_to_30,
|
||||
Down: v29_down_settings_to_30,
|
||||
Name: "settings to 30",
|
||||
})
|
||||
}
|
||||
|
||||
// so setting to false and "", is what would happen without this code
|
||||
// I'm going to bet there's zero point to changing the value inthe DB
|
||||
// Public for testing
|
||||
func v29_up_settings_to_30() error {
|
||||
legacySettings, err := migrator.store.SettingsService.Settings()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
legacySettings.OAuthSettings.SSO = false
|
||||
legacySettings.OAuthSettings.LogoutURI = ""
|
||||
return migrator.store.SettingsService.UpdateSettings(legacySettings)
|
||||
}
|
||||
|
||||
func v29_down_settings_to_30() error {
|
||||
return nil
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/datastore/migrations/types"
|
||||
)
|
||||
|
||||
func init() {
|
||||
migrator.AddMigration(types.Migration{
|
||||
Version: 31,
|
||||
Timestamp: 1646097709,
|
||||
Up: v31_up_registries_to_32,
|
||||
Down: v31_down_registries_to_32,
|
||||
Name: "registries to 32",
|
||||
})
|
||||
}
|
||||
|
||||
func v31_up_registries_to_32() error {
|
||||
registries, err := migrator.store.RegistryService.Registries()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
endpoints, err := migrator.store.EndpointService.Endpoints()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, registry := range registries {
|
||||
|
||||
registry.RegistryAccesses = portainer.RegistryAccesses{}
|
||||
|
||||
for _, endpoint := range endpoints {
|
||||
|
||||
filteredUserAccessPolicies := portainer.UserAccessPolicies{}
|
||||
for userId, registryPolicy := range registry.UserAccessPolicies {
|
||||
if _, found := endpoint.UserAccessPolicies[userId]; found {
|
||||
filteredUserAccessPolicies[userId] = registryPolicy
|
||||
}
|
||||
}
|
||||
|
||||
filteredTeamAccessPolicies := portainer.TeamAccessPolicies{}
|
||||
for teamId, registryPolicy := range registry.TeamAccessPolicies {
|
||||
if _, found := endpoint.TeamAccessPolicies[teamId]; found {
|
||||
filteredTeamAccessPolicies[teamId] = registryPolicy
|
||||
}
|
||||
}
|
||||
|
||||
registry.RegistryAccesses[endpoint.ID] = portainer.RegistryAccessPolicies{
|
||||
UserAccessPolicies: filteredUserAccessPolicies,
|
||||
TeamAccessPolicies: filteredTeamAccessPolicies,
|
||||
Namespaces: []string{},
|
||||
}
|
||||
}
|
||||
migrator.store.RegistryService.UpdateRegistry(registry.ID, ®istry)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func v31_down_registries_to_32() error {
|
||||
return nil
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/dataservices/errors"
|
||||
"github.com/portainer/portainer/api/datastore/migrations/types"
|
||||
)
|
||||
|
||||
func init() {
|
||||
migrator.AddMigration(types.Migration{
|
||||
Version: 31,
|
||||
Timestamp: 1646097896,
|
||||
Up: v31_up_dockerhub_to_32,
|
||||
Down: v31_down_dockerhub_to_32,
|
||||
Name: "dockerhub to 32",
|
||||
})
|
||||
}
|
||||
|
||||
func v31_up_dockerhub_to_32() error {
|
||||
dockerhub, err := migrator.store.DockerHubService.DockerHub()
|
||||
if err == errors.ErrObjectNotFound {
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !dockerhub.Authentication {
|
||||
return nil
|
||||
}
|
||||
|
||||
registry := &portainer.Registry{
|
||||
Type: portainer.DockerHubRegistry,
|
||||
Name: "Dockerhub (authenticated - migrated)",
|
||||
URL: "docker.io",
|
||||
Authentication: true,
|
||||
Username: dockerhub.Username,
|
||||
Password: dockerhub.Password,
|
||||
RegistryAccesses: portainer.RegistryAccesses{},
|
||||
}
|
||||
|
||||
// The following code will make this function idempotent.
|
||||
// i.e. if run again, it will not change the data. It will ensure that
|
||||
// we only have one migrated registry entry. Duplicates will be removed
|
||||
// if they exist and which has been happening due to earlier migration bugs
|
||||
migrated := false
|
||||
registries, _ := migrator.store.RegistryService.Registries()
|
||||
for _, r := range registries {
|
||||
if r.Type == registry.Type &&
|
||||
r.Name == registry.Name &&
|
||||
r.URL == registry.URL &&
|
||||
r.Authentication == registry.Authentication {
|
||||
|
||||
if !migrated {
|
||||
// keep this one entry
|
||||
migrated = true
|
||||
} else {
|
||||
// delete subsequent duplicates
|
||||
migrator.store.RegistryService.DeleteRegistry(portainer.RegistryID(r.ID))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if migrated {
|
||||
return nil
|
||||
}
|
||||
|
||||
endpoints, err := migrator.store.EndpointService.Endpoints()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, endpoint := range endpoints {
|
||||
|
||||
if endpoint.Type != portainer.KubernetesLocalEnvironment &&
|
||||
endpoint.Type != portainer.AgentOnKubernetesEnvironment &&
|
||||
endpoint.Type != portainer.EdgeAgentOnKubernetesEnvironment {
|
||||
|
||||
userAccessPolicies := portainer.UserAccessPolicies{}
|
||||
for userId := range endpoint.UserAccessPolicies {
|
||||
if _, found := endpoint.UserAccessPolicies[userId]; found {
|
||||
userAccessPolicies[userId] = portainer.AccessPolicy{
|
||||
RoleID: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
teamAccessPolicies := portainer.TeamAccessPolicies{}
|
||||
for teamId := range endpoint.TeamAccessPolicies {
|
||||
if _, found := endpoint.TeamAccessPolicies[teamId]; found {
|
||||
teamAccessPolicies[teamId] = portainer.AccessPolicy{
|
||||
RoleID: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
registry.RegistryAccesses[endpoint.ID] = portainer.RegistryAccessPolicies{
|
||||
UserAccessPolicies: userAccessPolicies,
|
||||
TeamAccessPolicies: teamAccessPolicies,
|
||||
Namespaces: []string{},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return migrator.store.RegistryService.Create(registry)
|
||||
}
|
||||
|
||||
func v31_down_dockerhub_to_32() error {
|
||||
return nil
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/datastore/migrations/types"
|
||||
"github.com/portainer/portainer/api/internal/endpointutils"
|
||||
snapshotutils "github.com/portainer/portainer/api/internal/snapshot"
|
||||
)
|
||||
|
||||
func init() {
|
||||
migrator.AddMigration(types.Migration{
|
||||
Version: 31,
|
||||
Timestamp: 1646097916,
|
||||
Up: v31_up_volume_resource_control_to_32,
|
||||
Down: v31_down_volume_resource_control_to_32,
|
||||
Name: "volume resource control to 32",
|
||||
})
|
||||
}
|
||||
|
||||
func findResourcesToUpdateForDB32(dockerID string, volumesData map[string]interface{}, toUpdate map[portainer.ResourceControlID]string, volumeResourceControls map[string]*portainer.ResourceControl) {
|
||||
volumes := volumesData["Volumes"].([]interface{})
|
||||
for _, volumeMeta := range volumes {
|
||||
volume := volumeMeta.(map[string]interface{})
|
||||
volumeName, nameExist := volume["Name"].(string)
|
||||
if !nameExist {
|
||||
continue
|
||||
}
|
||||
createTime, createTimeExist := volume["CreatedAt"].(string)
|
||||
if !createTimeExist {
|
||||
continue
|
||||
}
|
||||
|
||||
oldResourceID := fmt.Sprintf("%s%s", volumeName, createTime)
|
||||
resourceControl, ok := volumeResourceControls[oldResourceID]
|
||||
|
||||
if ok {
|
||||
toUpdate[resourceControl.ID] = fmt.Sprintf("%s_%s", volumeName, dockerID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func v31_up_volume_resource_control_to_32() error {
|
||||
endpoints, err := migrator.store.EndpointService.Endpoints()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed fetching environments: %w", err)
|
||||
}
|
||||
|
||||
resourceControls, err := migrator.store.ResourceControlService.ResourceControls()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed fetching resource controls: %w", err)
|
||||
}
|
||||
|
||||
toUpdate := map[portainer.ResourceControlID]string{}
|
||||
volumeResourceControls := map[string]*portainer.ResourceControl{}
|
||||
|
||||
for i := range resourceControls {
|
||||
resourceControl := resourceControls[i]
|
||||
if resourceControl.Type == portainer.VolumeResourceControl {
|
||||
volumeResourceControls[resourceControl.ResourceID] = &resourceControl
|
||||
}
|
||||
}
|
||||
|
||||
for _, endpoint := range endpoints {
|
||||
if !endpointutils.IsDockerEndpoint(&endpoint) {
|
||||
continue
|
||||
}
|
||||
|
||||
totalSnapshots := len(endpoint.Snapshots)
|
||||
if totalSnapshots == 0 {
|
||||
log.Println("[DEBUG] [volume migration] [message: no snapshot found]")
|
||||
continue
|
||||
}
|
||||
|
||||
snapshot := endpoint.Snapshots[totalSnapshots-1]
|
||||
|
||||
endpointDockerID, err := snapshotutils.FetchDockerID(snapshot)
|
||||
if err != nil {
|
||||
log.Printf("[WARN] [database,migrator,v31] [message: failed fetching environment docker id] [err: %s]", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if volumesData, done := snapshot.SnapshotRaw.Volumes.(map[string]interface{}); done {
|
||||
if volumesData["Volumes"] == nil {
|
||||
log.Println("[DEBUG] [volume migration] [message: no volume data found]")
|
||||
continue
|
||||
}
|
||||
|
||||
findResourcesToUpdateForDB32(endpointDockerID, volumesData, toUpdate, volumeResourceControls)
|
||||
}
|
||||
}
|
||||
|
||||
for _, resourceControl := range volumeResourceControls {
|
||||
if newResourceID, ok := toUpdate[resourceControl.ID]; ok {
|
||||
resourceControl.ResourceID = newResourceID
|
||||
err := migrator.store.ResourceControlService.UpdateResourceControl(resourceControl.ID, resourceControl)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed updating resource control %d: %w", resourceControl.ID, err)
|
||||
}
|
||||
|
||||
} else {
|
||||
err := migrator.store.ResourceControlService.DeleteResourceControl(resourceControl.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed deleting resource control %d: %w", resourceControl.ID, err)
|
||||
}
|
||||
log.Printf("[DEBUG] [volume migration] [message: legacy resource control(%s) has been deleted]", resourceControl.ResourceID)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func v31_down_volume_resource_control_to_32() error {
|
||||
return nil
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/datastore/migrations/types"
|
||||
)
|
||||
|
||||
func init() {
|
||||
migrator.AddMigration(types.Migration{
|
||||
Version: 31,
|
||||
Timestamp: 1646097944,
|
||||
Up: v31_up_kubeconfig_expiry_to_32,
|
||||
Down: v31_down_kubeconfig_expiry_to_32,
|
||||
Name: "kubeconfig expiry to 32",
|
||||
})
|
||||
}
|
||||
|
||||
func v31_up_kubeconfig_expiry_to_32() error {
|
||||
settings, err := migrator.store.SettingsService.Settings()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
settings.KubeconfigExpiry = portainer.DefaultKubeconfigExpiry
|
||||
return migrator.store.SettingsService.UpdateSettings(settings)
|
||||
}
|
||||
|
||||
func v31_down_kubeconfig_expiry_to_32() error {
|
||||
return nil
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/datastore/migrations/types"
|
||||
)
|
||||
|
||||
func init() {
|
||||
migrator.AddMigration(types.Migration{
|
||||
Version: 31,
|
||||
Timestamp: 1646097962,
|
||||
Up: v31_up_helm_repo_url_to_32,
|
||||
Down: v31_down_helm_repo_url_to_32,
|
||||
Name: "helm repo url to 32",
|
||||
})
|
||||
}
|
||||
|
||||
func v31_up_helm_repo_url_to_32() error {
|
||||
settings, err := migrator.store.SettingsService.Settings()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
settings.HelmRepositoryURL = portainer.DefaultHelmRepositoryURL
|
||||
return migrator.store.SettingsService.UpdateSettings(settings)
|
||||
}
|
||||
|
||||
func v31_down_helm_repo_url_to_32() error {
|
||||
return nil
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
#!/bin/sh
|
||||
die () {
|
||||
echo >&2 "$@"
|
||||
exit 1
|
||||
}
|
||||
|
||||
[ "$#" -eq 2 ] || die "Usage - version \"space separated context\""
|
||||
|
||||
TIMESTAMP=$(date +%s)
|
||||
VERSION=$1
|
||||
CONTEXT=$2
|
||||
|
||||
CONTEXT_SLUG="${CONTEXT// /_}"
|
||||
|
||||
cat << EOF >${TIMESTAMP}_${CONTEXT_SLUG}.go
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"github.com/portainer/portainer/api/datastore/migrations/types"
|
||||
)
|
||||
|
||||
func init() {
|
||||
migrator.AddMigration(types.Migration{
|
||||
Version: ${VERSION},
|
||||
Timestamp: ${TIMESTAMP},
|
||||
Up: v${VERSION}_up_${CONTEXT_SLUG},
|
||||
Down: v${VERSION}_down_${CONTEXT_SLUG},
|
||||
Name: "${CONTEXT}",
|
||||
})
|
||||
}
|
||||
|
||||
func v${VERSION}_up_${CONTEXT_SLUG}() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func v${VERSION}_down_${CONTEXT_SLUG}() error {
|
||||
return nil
|
||||
}
|
||||
EOF
|
||||
@@ -1,111 +0,0 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/portainer/portainer/api/datastore"
|
||||
"github.com/portainer/portainer/api/datastore/migrations/types"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type apiDBVersion struct {
|
||||
api string
|
||||
db int
|
||||
}
|
||||
type apiVersionsMap []apiDBVersion
|
||||
|
||||
type migrations []*types.Migration
|
||||
|
||||
type Migrator struct {
|
||||
store datastore.Store
|
||||
Versions []int
|
||||
Migrations map[int]migrations
|
||||
}
|
||||
|
||||
var migrator *Migrator = &Migrator{
|
||||
Versions: []int{},
|
||||
Migrations: map[int]migrations{},
|
||||
}
|
||||
|
||||
var versionsMap apiVersionsMap = apiVersionsMap{
|
||||
{"2.12.0", 36},
|
||||
{"2.9.3", 35},
|
||||
{"2.10.0", 34},
|
||||
{"2.9.2", 33},
|
||||
{"2.9.1", 33},
|
||||
{"2.9.0", 32},
|
||||
{"2.7.0", 31},
|
||||
{"2.6.0", 30},
|
||||
{"2.4.0", 29},
|
||||
{"2.4.0", 28},
|
||||
{"2.2.0", 27},
|
||||
{"2.1.0", 26},
|
||||
}
|
||||
|
||||
func NewMigrator(m datastore.Store) *Migrator {
|
||||
migrator.store = m
|
||||
return migrator
|
||||
}
|
||||
|
||||
func (m *Migrator) AddMigration(mg types.Migration) {
|
||||
// Add the migration to the hash with version as key
|
||||
if m.Migrations[mg.Version] == nil {
|
||||
m.Migrations[mg.Version] = make(migrations, 0)
|
||||
}
|
||||
m.Migrations[mg.Version] = append(m.Migrations[mg.Version], &mg)
|
||||
|
||||
if !contains(m.Versions, mg.Version) {
|
||||
// Insert version into versions array using insertion sort
|
||||
index := 0
|
||||
for index < len(m.Versions) {
|
||||
if m.Versions[index] > mg.Version {
|
||||
break
|
||||
}
|
||||
index++
|
||||
}
|
||||
|
||||
m.Versions = append(m.Versions, mg.Version)
|
||||
copy(m.Versions[index+1:], m.Versions[index:])
|
||||
m.Versions[index] = mg.Version
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Migrator) Migrate(currentVersion int) error {
|
||||
migrationsToRun := &Migrator{
|
||||
Versions: []int{},
|
||||
Migrations: map[int]migrations{},
|
||||
}
|
||||
for _, v := range m.Versions {
|
||||
mg := m.Migrations[v]
|
||||
// if migration version is below current version
|
||||
if v < currentVersion {
|
||||
continue
|
||||
}
|
||||
migrationsToRun.Versions = append(migrationsToRun.Versions, v)
|
||||
migrationsToRun.Migrations[v] = mg
|
||||
}
|
||||
|
||||
// TODO: Sort by Timestamp
|
||||
for _, v := range migrationsToRun.Versions {
|
||||
mg := m.Migrations[v]
|
||||
for _, m := range mg {
|
||||
logger := logrus.WithFields(logrus.Fields{"version": m.Version, "migration": m.Name})
|
||||
logger.Info("starting migration")
|
||||
err := m.Up()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("while running migration for version %d, name `%s`", m.Version, m.Name))
|
||||
}
|
||||
m.Completed = true
|
||||
logger.Info("migration completed successfully")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: move to utils
|
||||
func contains(s []int, searchterm int) bool {
|
||||
i := sort.SearchInts(s, searchterm)
|
||||
return i < len(s) && s[i] == searchterm
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package types
|
||||
|
||||
type Migration struct {
|
||||
Version int
|
||||
Up func() error
|
||||
Down func() error
|
||||
Completed bool
|
||||
Timestamp int32
|
||||
Name string
|
||||
}
|
||||
@@ -33,7 +33,6 @@ import (
|
||||
"github.com/portainer/portainer/api/dataservices/user"
|
||||
"github.com/portainer/portainer/api/dataservices/version"
|
||||
"github.com/portainer/portainer/api/dataservices/webhook"
|
||||
"github.com/portainer/portainer/api/internal/authorization"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
@@ -69,8 +68,6 @@ type Store struct {
|
||||
UserService *user.Service
|
||||
VersionService *version.Service
|
||||
WebhookService *webhook.Service
|
||||
|
||||
AuthorizationService *authorization.Service // TODO: validate why it is not part of store
|
||||
}
|
||||
|
||||
func (store *Store) initServices() error {
|
||||
@@ -230,13 +227,6 @@ func (store *Store) initServices() error {
|
||||
}
|
||||
store.ScheduleService = scheduleService
|
||||
|
||||
authService := authorization.NewService(store)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
store.AuthorizationService = authService
|
||||
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -379,7 +369,6 @@ type storeExport struct {
|
||||
User []portainer.User `json:"users,omitempty"`
|
||||
Version map[string]string `json:"version,omitempty"`
|
||||
Webhook []portainer.Webhook `json:"webhooks,omitempty"`
|
||||
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
func (store *Store) Export(filename string) (err error) {
|
||||
@@ -572,11 +561,6 @@ func (store *Store) Export(filename string) (err error) {
|
||||
"INSTANCE_ID": instance,
|
||||
}
|
||||
|
||||
backup.Metadata, err = store.connection.BackupMetadata()
|
||||
if err != nil {
|
||||
logrus.WithError(err).Errorf("Exporting Metadata")
|
||||
}
|
||||
|
||||
b, err := json.MarshalIndent(backup, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -585,7 +569,6 @@ func (store *Store) Export(filename string) (err error) {
|
||||
}
|
||||
|
||||
func (store *Store) Import(filename string) (err error) {
|
||||
|
||||
backup := storeExport{}
|
||||
|
||||
s, err := ioutil.ReadFile(filename)
|
||||
@@ -686,5 +669,5 @@ func (store *Store) Import(filename string) (err error) {
|
||||
store.Webhook().UpdateWebhook(v.ID, &v)
|
||||
}
|
||||
|
||||
return store.connection.RestoreMetadata(backup.Metadata)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -60,6 +60,8 @@ const (
|
||||
DefaultSSLCertFilename = "cert.pem"
|
||||
// DefaultSSLKeyFilename represents the default ssl key file name
|
||||
DefaultSSLKeyFilename = "key.pem"
|
||||
// DefaultSSLCacertFilename represents the default CA ssl certificate file name for mTLS
|
||||
DefaultSSLCacertFilename = "ca-cert.pem"
|
||||
)
|
||||
|
||||
// ErrUndefinedTLSFileType represents an error returned on undefined TLS file type
|
||||
@@ -161,7 +163,7 @@ func (service *Service) Copy(fromFilePath string, toFilePath string, deleteIfExi
|
||||
}
|
||||
|
||||
if !exists {
|
||||
return errors.New("File doesn't exist")
|
||||
return errors.New(fmt.Sprintf("File (%s) doesn't exist", fromFilePath))
|
||||
}
|
||||
|
||||
finput, err := os.Open(fromFilePath)
|
||||
@@ -352,6 +354,9 @@ func (service *Service) DeleteTLSFile(folder string, fileType portainer.TLSFileT
|
||||
|
||||
// GetFileContent returns the content of a file as bytes.
|
||||
func (service *Service) GetFileContent(trustedRoot, filePath string) ([]byte, error) {
|
||||
if trustedRoot == "" {
|
||||
trustedRoot = "/"
|
||||
}
|
||||
content, err := os.ReadFile(JoinPaths(trustedRoot, filePath))
|
||||
if err != nil {
|
||||
if filePath == "" {
|
||||
@@ -627,6 +632,25 @@ func (service *Service) CopySSLCertPair(certPath, keyPath string) (string, strin
|
||||
return defCertPath, defKeyPath, nil
|
||||
}
|
||||
|
||||
// GetDefaultSSLCacertsPath returns the ssl cacert path
|
||||
func (service *Service) GetDefaultSSLCacertsPath() string {
|
||||
cacertPath := JoinPaths(SSLCertPath, DefaultSSLCacertFilename)
|
||||
|
||||
return service.wrapFileStore(cacertPath)
|
||||
}
|
||||
|
||||
// CopySSLCacert copies the specified cacert pem file
|
||||
func (service *Service) CopySSLCacert(cacertPath string) (string, error) {
|
||||
defCacertPath := service.GetDefaultSSLCacertsPath()
|
||||
|
||||
err := service.Copy(cacertPath, defCacertPath, true)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return defCacertPath, nil
|
||||
}
|
||||
|
||||
// FileExists checks for the existence of the specified file.
|
||||
func FileExists(filePath string) (bool, error) {
|
||||
if _, err := os.Stat(filePath); err != nil {
|
||||
|
||||
26
api/go.mod
26
api/go.mod
@@ -19,10 +19,12 @@ require (
|
||||
github.com/go-git/go-git/v5 v5.3.0
|
||||
github.com/go-ldap/ldap/v3 v3.1.8
|
||||
github.com/gofrs/uuid v4.0.0+incompatible
|
||||
github.com/google/gops v0.3.22
|
||||
github.com/gorilla/handlers v1.5.1
|
||||
github.com/gorilla/mux v1.7.3
|
||||
github.com/gorilla/securecookie v1.1.1
|
||||
github.com/gorilla/websocket v1.4.2
|
||||
github.com/hashicorp/go-version v1.4.0
|
||||
github.com/hashicorp/golang-lru v0.5.4
|
||||
github.com/joho/godotenv v1.3.0
|
||||
github.com/jpillora/chisel v0.0.0-20190724232113-f3a8df20e389
|
||||
@@ -34,10 +36,12 @@ require (
|
||||
github.com/portainer/libcrypto v0.0.0-20210422035235-c652195c5c3a
|
||||
github.com/portainer/libhelm v0.0.0-20210929000907-825e93d62108
|
||||
github.com/portainer/libhttp v0.0.0-20211208103139-07a5f798eb3f
|
||||
github.com/prometheus/client_golang v1.7.1
|
||||
github.com/rkl-/digest v0.0.0-20180419075440-8316caa4a777
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/swaggo/swag v1.7.8
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||
@@ -50,6 +54,9 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||
github.com/PuerkitoBio/purell v1.1.1 // indirect
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect
|
||||
github.com/alecthomas/units v0.0.0-20210208195552-ff826a37aa15 // indirect
|
||||
github.com/andrew-d/go-termutil v0.0.0-20150726205930-009166a695a2 // indirect
|
||||
@@ -57,6 +64,8 @@ require (
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.0.1 // indirect
|
||||
github.com/aws/smithy-go v1.9.0 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.1 // indirect
|
||||
github.com/containerd/containerd v1.5.7 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/docker/distribution v2.7.1+incompatible // indirect
|
||||
@@ -70,6 +79,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 v0.4.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||
github.com/go-openapi/jsonreference v0.19.6 // indirect
|
||||
github.com/go-openapi/spec v0.20.4 // indirect
|
||||
github.com/go-openapi/swag v0.19.15 // 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
|
||||
@@ -79,10 +92,13 @@ require (
|
||||
github.com/imdario/mergo v0.3.12 // indirect
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/jpillora/ansi v0.0.0-20170202005112-f496b27cd669 // indirect
|
||||
github.com/jpillora/requestlog v0.0.0-20181015073026-df8817be5f82 // indirect
|
||||
github.com/jpillora/sizestr v0.0.0-20160130011556-e2ea2fa42fb9 // indirect
|
||||
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // indirect
|
||||
github.com/mailru/easyjson v0.7.6 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // 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
|
||||
@@ -93,6 +109,9 @@ require (
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.0.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_model v0.2.0 // indirect
|
||||
github.com/prometheus/common v0.10.0 // indirect
|
||||
github.com/prometheus/procfs v0.6.0 // indirect
|
||||
github.com/sergi/go-diff v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce // indirect
|
||||
@@ -101,11 +120,12 @@ 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-20210520170846-37e1c6afe023 // indirect
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 // indirect
|
||||
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d // indirect
|
||||
golang.org/x/sys v0.0.0-20210902050250-f475640dd07b // indirect
|
||||
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d // indirect
|
||||
golang.org/x/text v0.3.6 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
|
||||
golang.org/x/tools v0.1.7 // indirect
|
||||
google.golang.org/appengine v1.6.5 // indirect
|
||||
google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a // indirect
|
||||
google.golang.org/grpc v1.33.2 // indirect
|
||||
|
||||
68
api/go.sum
68
api/go.sum
@@ -41,6 +41,8 @@ github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZ
|
||||
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
||||
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
||||
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
|
||||
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
|
||||
github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw=
|
||||
@@ -62,9 +64,14 @@ github.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5
|
||||
github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:mw7qgWloBUl75W/gVH3cQszUg1+gUITj7D6NY7ywVnY=
|
||||
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
|
||||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=
|
||||
github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8=
|
||||
github.com/agiledragon/gomonkey/v2 v2.3.1 h1:k+UnUY0EMNYUFUAQVETGY9uUTxjMdnUkP0ARyJS1zzs=
|
||||
github.com/agiledragon/gomonkey/v2 v2.3.1/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY=
|
||||
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs=
|
||||
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
@@ -105,6 +112,7 @@ github.com/aws/smithy-go v1.9.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAm
|
||||
github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
|
||||
@@ -120,7 +128,9 @@ github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8n
|
||||
github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50=
|
||||
github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw=
|
||||
github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M=
|
||||
@@ -334,13 +344,23 @@ github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7
|
||||
github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
|
||||
github.com/go-logr/logr v0.4.0 h1:K7/B1jt6fIBQVd4Owv2MqGQClcgf0R266+7C/QjRcLc=
|
||||
github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
|
||||
github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-ole/go-ole v1.2.6-0.20210915003542-8b1f7f90f6b1/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
|
||||
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
|
||||
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
|
||||
github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
|
||||
github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs=
|
||||
github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns=
|
||||
github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
|
||||
github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M=
|
||||
github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I=
|
||||
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.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
|
||||
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
|
||||
github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
|
||||
@@ -404,6 +424,8 @@ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g=
|
||||
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gops v0.3.22 h1:lyvhDxfPLHAOR2xIYwjPhN387qHxyU21Sk9sz/GhmhQ=
|
||||
github.com/google/gops v0.3.22/go.mod h1:7diIdLsqpCihPSX3fQagksT/Ku/y4RL9LHTlKyEUDl8=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
@@ -446,6 +468,8 @@ github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FK
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I=
|
||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||
github.com/hashicorp/go-version v1.4.0 h1:aAQzgqIrRKRa7w75CKpbBxYsmUoPjzVm1W59ca1L0J4=
|
||||
github.com/hashicorp/go-version v1.4.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
|
||||
@@ -473,6 +497,8 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfC
|
||||
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
|
||||
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/jpillora/ansi v0.0.0-20170202005112-f496b27cd669 h1:l5rH/CnVVu+HPxjtxjM90nHrm4nov3j3RF9/62UjgLs=
|
||||
github.com/jpillora/ansi v0.0.0-20170202005112-f496b27cd669/go.mod h1:kOeLNvjNBGSV3uYtFjvb72+fnZCMFJF1XDvRIjdom0g=
|
||||
github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7/go.mod h1:2iMrUgbbvHEiQClaW2NsSzMyGHqN+rDFqY705q49KG0=
|
||||
@@ -493,6 +519,7 @@ github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfV
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 h1:DowS9hvgyYSX4TO5NpyC606/Z4SxnNYbT+WX27or6Ck=
|
||||
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
||||
github.com/keybase/go-ps v0.0.0-20190827175125-91aafc93ba19/go.mod h1:hY+WOq6m2FpbvyrI93sMaypsttvaIL5nhVR92dTMUcQ=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
@@ -518,12 +545,15 @@ github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czP
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
|
||||
github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
|
||||
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
||||
github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
|
||||
github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4=
|
||||
@@ -603,6 +633,12 @@ github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3
|
||||
github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8=
|
||||
github.com/orcaman/concurrent-map v0.0.0-20190826125027-8c72a8bb44f6 h1:lNCW6THrCKBiJBpz8kbVGjC7MgdCGKwuvBgc7LoD6sw=
|
||||
github.com/orcaman/concurrent-map v0.0.0-20190826125027-8c72a8bb44f6/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI=
|
||||
github.com/otiai10/copy v1.7.0 h1:hVoPiN+t+7d2nzzwMiDHPSOogsWAStewq3TwU05+clE=
|
||||
github.com/otiai10/copy v1.7.0/go.mod h1:rmRl6QPdJj6EiUqXQ/4Nn2lLXoNQjFCQbbNrxgc/t3U=
|
||||
github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
|
||||
github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs=
|
||||
github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
|
||||
github.com/otiai10/mint v1.3.3/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
||||
@@ -627,17 +663,20 @@ github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP
|
||||
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=
|
||||
github.com/prometheus/client_golang v1.7.1 h1:NTGy1Ja9pByO+xAeH/qiWnLrKtr3hJPNjaVUwnjpdpA=
|
||||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||
github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
|
||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=
|
||||
github.com/prometheus/common v0.10.0 h1:RyRA7RzGXQZiW+tGMr7sxa85G1z0yOpM1qq5c8lNawc=
|
||||
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
||||
github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
@@ -649,6 +688,7 @@ github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDa
|
||||
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4=
|
||||
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||
github.com/rkl-/digest v0.0.0-20180419075440-8316caa4a777 h1:rDj3WeO+TiWyxfcydUnKegWAZoR5kQsnW0wzhggdOrw=
|
||||
@@ -663,6 +703,7 @@ github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdh
|
||||
github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo=
|
||||
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/shirou/gopsutil/v3 v3.21.9/go.mod h1:YWp/H8Qs5fVmf17v7JNZzA0mPJ+mS2e9JdiUF9LlKzQ=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
|
||||
github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
|
||||
@@ -675,6 +716,7 @@ github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE
|
||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
@@ -705,10 +747,14 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/swaggo/swag v1.7.8 h1:w249t0l/kc/DKMGlS0fppNJQxKyJ8heNaUWB6nsH3zc=
|
||||
github.com/swaggo/swag v1.7.8/go.mod h1:gZ+TJ2w/Ve1RwQsA2IRoSOTidHz6DX+PIG8GWvbnoLU=
|
||||
github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||
github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||
github.com/tchap/go-patricia v2.2.6+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I=
|
||||
github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ3LSFUzyeuhs=
|
||||
github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcyE6boqnA8=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce h1:fb190+cK2Xz/dvi9Hv8eCYJYvIGUTN2/KLq1pT6CjEc=
|
||||
@@ -718,6 +764,7 @@ github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/
|
||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
||||
github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk=
|
||||
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
|
||||
github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
|
||||
@@ -738,9 +785,11 @@ github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/xlab/treeprint v1.1.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs=
|
||||
github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA=
|
||||
github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg=
|
||||
@@ -804,6 +853,8 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB
|
||||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -841,8 +892,10 @@ golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwY
|
||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k=
|
||||
golang.org/x/net v0.0.0-20210520170846-37e1c6afe023 h1:ADo5wSpq2gqaCGQWzk7S5vd//0iyyLeAratkEoG5dLE=
|
||||
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
|
||||
golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d h1:20cMwl2fHAzkJMEA+8J4JgqBQcQGzbisXo31MIeenXI=
|
||||
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
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=
|
||||
@@ -922,10 +975,14 @@ golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 h1:RqytpXGR1iVNX7psjB3ff8y7sNFinVFvkx1c8SjBkio=
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/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-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210902050250-f475640dd07b h1:S7hKs0Flbq0bbc9xgYt4stIEG1zNDFqyrPwAX2Wj/sE=
|
||||
golang.org/x/sys v0.0.0-20210902050250-f475640dd07b/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 h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE=
|
||||
@@ -936,8 +993,9 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
@@ -982,6 +1040,8 @@ golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapK
|
||||
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.7 h1:6j8CgantCy3yc8JGBqkDLMKWqZ0RDU2g1HVgacojGWQ=
|
||||
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -1085,6 +1145,7 @@ gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRN
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
@@ -1144,6 +1205,7 @@ k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/
|
||||
k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a h1:8dYfu/Fc9Gz2rNJKB9IQRGgQOh2clmRzNIPPY1xLY5g=
|
||||
k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/goversion v1.2.0/go.mod h1:Eih9y/uIBS3ulggl7KNJ09xGSLcuNaLgmvvqa07sgfo=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg=
|
||||
|
||||
393
api/http/handler/endpointedge/async.go
Normal file
393
api/http/handler/endpointedge/async.go
Normal file
@@ -0,0 +1,393 @@
|
||||
package endpointedge
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
version "github.com/hashicorp/go-version"
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/internal/endpointutils"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// https://datatracker.ietf.org/doc/html/rfc6902
|
||||
// Doing this manually, because at this point, i don't want to marshal to json, make a diff - for now, just using 'add' (as its really an upsert)
|
||||
type JSONPatch struct {
|
||||
Operation string `json:"op"`
|
||||
Path string `json:"path"`
|
||||
Value interface{} `json:"value"`
|
||||
}
|
||||
|
||||
// TODO: copied from edgestack_status_update
|
||||
type updateStatusPayload struct {
|
||||
Error string
|
||||
Status *portainer.EdgeStackStatusType
|
||||
EndpointID *portainer.EndpointID
|
||||
}
|
||||
type edgeJobResponse struct {
|
||||
// EdgeJob Identifier
|
||||
ID portainer.EdgeJobID `json:"Id" example:"2"`
|
||||
// Whether to collect logs
|
||||
CollectLogs bool `json:"CollectLogs" example:"true"`
|
||||
// A cron expression to schedule this job
|
||||
CronExpression string `json:"CronExpression" example:"* * * * *"`
|
||||
// Script to run
|
||||
Script string `json:"Script" example:"echo hello"`
|
||||
// Version of this EdgeJob
|
||||
Version int `json:"Version" example:"2"`
|
||||
}
|
||||
|
||||
// An empty request ~~ just a ping.
|
||||
type Snapshot struct {
|
||||
Docker *portainer.DockerSnapshot
|
||||
Kubernetes *portainer.KubernetesSnapshot
|
||||
}
|
||||
type AsyncRequest struct {
|
||||
CommandId string `json: optional`
|
||||
Snapshot *Snapshot `json: optional` // todo
|
||||
StackStatus map[portainer.EdgeStackID]updateStatusPayload
|
||||
}
|
||||
|
||||
func (payload *AsyncRequest) Validate(r *http.Request) error {
|
||||
// TODO:
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type AsyncResponse struct {
|
||||
CommandInterval time.Duration `json: optional`
|
||||
PingInterval time.Duration `json: optional`
|
||||
SnapshotInterval time.Duration `json: optional`
|
||||
|
||||
ServerCommandId string // should be easy to detect if its larger / smaller: this is the response that tells the agent there are new commands waiting for it
|
||||
SendDiffSnapshotTime time.Time `json: optional` // might be optional
|
||||
Commands []JSONPatch `json: optional` // todo
|
||||
Status string // give the agent some idea if the server thinks its OK, or if it should STOP
|
||||
}
|
||||
|
||||
// Yup, this should be environment specific, and not global
|
||||
var lastcheckinStatusMutex sync.Mutex
|
||||
|
||||
// for testing with mTLS..:
|
||||
//sven@p1:~/src/portainer/portainer$ curl -k --cacert ~/.config/portainer/certs/ca.pem --cert ~/.config/portainer/certs/agent-cert.pem --key ~/.config/portainer/certs/agent-key.pem -X POST --header "X-PortainerAgent-EdgeID: 7e2b0143-c511-43c3-844c-a7a91ab0bedc" --data '{"CommandId": "okok", "Snapshot": {}}' https://p1:9443/api/endpoints/edge/async/
|
||||
//{"CommandInterval":0,"PingInterval":0,"SnapshotInterval":0,"ServerCommandId":"8888","SendDiffSnapshotTime":"0001-01-01T00:00:00Z","Commands":{}}
|
||||
|
||||
// @id endpointAsync
|
||||
// @summary Get environment(endpoint) status
|
||||
// @description Environment(Endpoint) for edge agent to check status of environment(endpoint)
|
||||
// @description **Access policy**: restricted only to Edge environments(endpoints) TODO: with mTLS cert....
|
||||
// @tags endpoints
|
||||
// @security ApiKeyAuth
|
||||
// @security jwt
|
||||
// @param id path int true "Environment(Endpoint) identifier"
|
||||
// @success 200 {object} AsyncResponse "Success"
|
||||
// @failure 400 "Invalid request"
|
||||
// @failure 403 "Permission denied to access environment(endpoint)"
|
||||
// @failure 404 "Environment(Endpoint) not found"
|
||||
// @failure 500 "Server error"
|
||||
// @router /endpoints/edge/async/ [post]
|
||||
func (handler *Handler) endpointAsync(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
// TODO: get endpointID from the mTLS cert info
|
||||
edgeIdentifier := r.Header.Get(portainer.PortainerAgentEdgeIDHeader)
|
||||
if edgeIdentifier == "" {
|
||||
logrus.WithField("portainer.PortainerAgentEdgeIDHeader", edgeIdentifier).Debug("missing agent edge id")
|
||||
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "missing Edge identifier", errors.New("missing Edge identifier")}
|
||||
}
|
||||
|
||||
// TODO: if the mTLS certs are valid, and we don't have a matching environment registered, CREATE IT (and maybe untrusted...)
|
||||
endpoint, err := handler.getEdgeEndpoint(edgeIdentifier)
|
||||
if err != nil {
|
||||
// TODO: if its a valid cert, or the user hasn't limited to mTLS / portainer set id, the
|
||||
// create new untrusted environment
|
||||
// portainer.HTTPResponseAgentPlatform tells us what platform it is too...
|
||||
logrus.WithField("portainer.PortainerAgentEdgeIDHeader", edgeIdentifier).WithField("Agent Addr", r.RemoteAddr).Debug("edge id not found in existing endpoints!")
|
||||
}
|
||||
// if agent mTLS is on, drop the connection if the client cert isn't CA'd (or if its revoked)
|
||||
err = handler.requestBouncer.AuthorizedEdgeEndpointOperation(r, endpoint)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access environment", err}
|
||||
}
|
||||
|
||||
// TODO: an assumption that needs testing, is that using the right EdgeId means we're also talking to the same DOckerd / Kube cluster.
|
||||
// I suspect that without getting the Dockerd UUID, or the "by convention" kube uuid, we can't know if someone's re-used info which then would cause some kind of state flapping.
|
||||
// Don't ask me what happens in non-async mode if you have more than one agent running - surely that would make the tunnel go boom?
|
||||
requestLogger := logrus.
|
||||
WithField("Agent Addr", r.RemoteAddr).
|
||||
WithField("Agent Version", r.Header.Get(portainer.HTTPAgentVersionHeaderName)).
|
||||
WithField("Agent PID", r.Header.Get(portainer.HTTPAgentPIDName)).
|
||||
WithField("Agent EdgeID", r.Header.Get(portainer.PortainerAgentEdgeIDHeader)) //.
|
||||
//WithField("Agent UniqueID", r.Header.Get(portainer.HTTPAgentUUIDHeaderName))
|
||||
|
||||
// Any request we can identify as coming from a valid agent is treated as a Ping
|
||||
endpoint.LastCheckInDate = time.Now().Unix()
|
||||
endpoint.Status = portainer.EndpointStatusUp
|
||||
|
||||
// TODO: update endpoint contact time
|
||||
lastcheckinStatusMutex.Lock()
|
||||
if endpoint.AgentHistory == nil {
|
||||
endpoint.AgentHistory = make(map[string]portainer.AgentInfo)
|
||||
}
|
||||
info, ok := endpoint.AgentHistory[r.RemoteAddr]
|
||||
if !ok {
|
||||
info = portainer.AgentInfo{
|
||||
LastCheckInDate: endpoint.LastCheckInDate,
|
||||
Version: r.Header.Get(portainer.HTTPAgentVersionHeaderName),
|
||||
RemoteAddr: r.RemoteAddr,
|
||||
Status: "OK",
|
||||
}
|
||||
}
|
||||
info.CheckInCount = info.CheckInCount + 1
|
||||
info.Status = "OK"
|
||||
info.LastCheckInDate = endpoint.LastCheckInDate
|
||||
requestLogger.Debugf("Checkin count = %d", info.CheckInCount)
|
||||
|
||||
endpoint.AgentHistory[r.RemoteAddr] = info
|
||||
|
||||
// Determine if there's more than one active agent, and if so, tell them to STOP
|
||||
okCount := 0
|
||||
infoVersion, _ := version.NewVersion(info.Version)
|
||||
for key, val := range endpoint.AgentHistory {
|
||||
timeToLastCheckIn := time.Second * time.Duration(endpoint.LastCheckInDate-val.LastCheckInDate)
|
||||
// Timeout for last best agent (currently based on version number)
|
||||
if timeToLastCheckIn > time.Second*time.Duration(endpoint.EdgeCheckinInterval*100) {
|
||||
delete(endpoint.AgentHistory, key)
|
||||
continue
|
||||
}
|
||||
if timeToLastCheckIn > time.Second*time.Duration(endpoint.EdgeCheckinInterval*10) {
|
||||
val.Status = "GONE"
|
||||
endpoint.AgentHistory[key] = val
|
||||
continue
|
||||
}
|
||||
if timeToLastCheckIn > time.Second*time.Duration(endpoint.EdgeCheckinInterval*2) {
|
||||
val.Status = "TROUBLED"
|
||||
endpoint.AgentHistory[key] = val
|
||||
continue
|
||||
}
|
||||
if val.Status == "OK" {
|
||||
// if there's a info.Version difference, choose the more up to date agent...
|
||||
valVersion, _ := version.NewVersion(val.Version)
|
||||
if infoVersion.GreaterThan(valVersion) {
|
||||
val.Status = "STOP VERSION - " + info.Version
|
||||
endpoint.AgentHistory[val.RemoteAddr] = val
|
||||
continue
|
||||
}
|
||||
if valVersion.GreaterThan(infoVersion) {
|
||||
info.Status = "STOP VERSION - " + val.Version
|
||||
endpoint.AgentHistory[info.RemoteAddr] = info
|
||||
info = val
|
||||
continue
|
||||
}
|
||||
|
||||
// TODO: UX question - trade off between user expectation on upgrade, vs stability at staeday state.
|
||||
// if we have two or more otherwise just as good agents, pick the newer one, on the presumption that it was started on purpose
|
||||
// the risk with this is the flapping you get if you gave two identical agents with --restart always - so maybe it should get tuned
|
||||
// for eg, only use the younger one if the user has initiated an upgrade? otherwise prefer the old?
|
||||
if val.CheckInCount < info.CheckInCount {
|
||||
info.Status = "STOP AGE"
|
||||
endpoint.AgentHistory[info.RemoteAddr] = info
|
||||
info = val
|
||||
continue
|
||||
}
|
||||
okCount++
|
||||
}
|
||||
}
|
||||
|
||||
err = handler.DataStore.Endpoint().UpdateEndpoint(endpoint.ID, endpoint)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to Unable to persist environment changes inside the database", err}
|
||||
}
|
||||
lastcheckinStatusMutex.Unlock()
|
||||
|
||||
var payload AsyncRequest
|
||||
err = request.DecodeAndValidateJSONPayload(r, &payload)
|
||||
if err != nil {
|
||||
// an "" request ~~ same as {}
|
||||
requestLogger.WithError(err).WithField("payload", r).Debug("decode payload")
|
||||
}
|
||||
|
||||
//if endpoint.AgentHistory[r.RemoteAddr].Status != "OK" {
|
||||
// okCount of zero can happen - basically, we havn't timed out a newer agent's last contact, and so we're hoping it will come back, so we refuse the old version
|
||||
// we could instead allow this, cos in some circumstances, it may be better to have more than one agent giving the user control
|
||||
requestLogger.Debugf("Checkin STATUS = %s (okCount = %d)", endpoint.AgentHistory[r.RemoteAddr].Status, okCount)
|
||||
//}
|
||||
|
||||
asyncResponse := AsyncResponse{
|
||||
ServerCommandId: "8888", // the most current id of a new command on the server
|
||||
Status: endpoint.AgentHistory[r.RemoteAddr].Status,
|
||||
}
|
||||
|
||||
// TODO: need a way to detect that these are changed, and send them to the agent...
|
||||
// CommandInterval time.Duration `json: optional`
|
||||
// PingInterval time.Duration `json: optional`
|
||||
// SnapshotInterval time.Duration `json: optional`
|
||||
|
||||
if payload.CommandId == "" && payload.Snapshot == nil {
|
||||
// just a ping.
|
||||
return response.JSON(w, asyncResponse)
|
||||
}
|
||||
|
||||
if payload.Snapshot != nil {
|
||||
asyncResponse.SendDiffSnapshotTime = handler.saveSnapshot(requestLogger, endpoint, payload)
|
||||
}
|
||||
if payload.CommandId != "" {
|
||||
asyncResponse.Commands = handler.sendCommandsSince(requestLogger, endpoint, payload.CommandId)
|
||||
}
|
||||
|
||||
return response.JSON(w, asyncResponse)
|
||||
}
|
||||
|
||||
// TODO: yup, next step is for these to be JSONDiff's and to be rehydrated
|
||||
func (handler *Handler) saveSnapshot(requestLogger *logrus.Entry, endpoint *portainer.Endpoint, payload AsyncRequest) time.Time {
|
||||
for stackID, status := range payload.StackStatus {
|
||||
stack, err := handler.DataStore.EdgeStack().EdgeStack(portainer.EdgeStackID(stackID))
|
||||
// TODO: work out what we can do with the errors
|
||||
if err == nil {
|
||||
stack.Status[*status.EndpointID] = portainer.EdgeStackStatus{
|
||||
Type: *status.Status,
|
||||
Error: status.Error,
|
||||
EndpointID: *status.EndpointID,
|
||||
}
|
||||
err = handler.DataStore.EdgeStack().UpdateEdgeStack(stack.ID, stack)
|
||||
}
|
||||
}
|
||||
|
||||
switch endpoint.Type {
|
||||
// case portainer.AzureEnvironment:
|
||||
// return time.Now()
|
||||
case portainer.KubernetesLocalEnvironment, portainer.AgentOnKubernetesEnvironment, portainer.EdgeAgentOnKubernetesEnvironment:
|
||||
requestLogger.Debug("Got a Kubernetes Snapshot")
|
||||
endpoint.Kubernetes.Snapshots = []portainer.KubernetesSnapshot{*payload.Snapshot.Kubernetes}
|
||||
return time.Unix(payload.Snapshot.Kubernetes.Time, 0)
|
||||
case portainer.DockerEnvironment, portainer.AgentOnDockerEnvironment, portainer.EdgeAgentOnDockerEnvironment:
|
||||
requestLogger.Debug("Got a Docker Snapshot")
|
||||
endpoint.Snapshots = []portainer.DockerSnapshot{*payload.Snapshot.Docker}
|
||||
return time.Unix(payload.Snapshot.Docker.Time, 0)
|
||||
default:
|
||||
return time.Time{}
|
||||
}
|
||||
}
|
||||
|
||||
func (handler *Handler) sendCommandsSince(requestLogger *logrus.Entry, endpoint *portainer.Endpoint, commandId string) []JSONPatch {
|
||||
var commandList []JSONPatch
|
||||
|
||||
// TODO: later, figure out if it is scalable to do diff's, as it means the server needs to store what it sent to all million agents (if the database had time based versioning, this would be trivial...)
|
||||
// I suspect the easiest thing will be to add a "modified timestamp" to edge stacks and edge jobs, and to send them only when the modified time > requested time
|
||||
requestLogger.WithField("endpoint", endpoint.Name).WithField("from command", commandId).Debug("Sending commands")
|
||||
|
||||
// schedules := []edgeJobResponse{}
|
||||
tunnel := handler.ReverseTunnelService.GetTunnelDetails(endpoint.ID)
|
||||
for _, job := range tunnel.Jobs {
|
||||
schedule := edgeJobResponse{
|
||||
ID: job.ID,
|
||||
CronExpression: job.CronExpression,
|
||||
CollectLogs: job.Endpoints[endpoint.ID].CollectLogs,
|
||||
Version: job.Version,
|
||||
}
|
||||
|
||||
file, err := handler.FileService.GetFileContent("/", job.ScriptPath)
|
||||
if err != nil {
|
||||
// TODO: this should maybe just skip thi job?
|
||||
requestLogger.WithError(err).Error("Unable to retrieve Edge job script file")
|
||||
continue
|
||||
}
|
||||
|
||||
schedule.Script = base64.RawStdEncoding.EncodeToString(file)
|
||||
cmd := JSONPatch{
|
||||
Operation: "add",
|
||||
Path: fmt.Sprintf("/edgejob/%d", schedule.ID),
|
||||
Value: schedule,
|
||||
}
|
||||
commandList = append(commandList, cmd)
|
||||
}
|
||||
|
||||
relation, err := handler.DataStore.EndpointRelation().EndpointRelation(endpoint.ID)
|
||||
if err != nil {
|
||||
requestLogger.WithError(err).Error("Unable to retrieve relation object from the database")
|
||||
return commandList
|
||||
}
|
||||
|
||||
// TODO: this is the datatype the agent uses in the end
|
||||
type edgeStackData struct {
|
||||
ID portainer.EdgeStackID
|
||||
Version int
|
||||
StackFileContent string
|
||||
Name string
|
||||
}
|
||||
|
||||
for stackID := range relation.EdgeStacks {
|
||||
stack, err := handler.DataStore.EdgeStack().EdgeStack(stackID)
|
||||
if err != nil {
|
||||
requestLogger.WithError(err).Error("Unable to retrieve edge stack from the database")
|
||||
continue
|
||||
}
|
||||
|
||||
edgeStack, err := handler.DataStore.EdgeStack().EdgeStack(portainer.EdgeStackID(stackID))
|
||||
if handler.DataStore.IsErrObjectNotFound(err) {
|
||||
requestLogger.WithError(err).Error("Unable to find an edge stack with the specified identifier inside the database")
|
||||
continue
|
||||
} else if err != nil {
|
||||
requestLogger.WithError(err).Error("Unable to find an edge stack with the specified identifier inside the database")
|
||||
continue
|
||||
}
|
||||
|
||||
fileName := edgeStack.EntryPoint
|
||||
if endpointutils.IsDockerEndpoint(endpoint) {
|
||||
if fileName == "" {
|
||||
requestLogger.Error("Docker is not supported by this stack")
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if endpointutils.IsKubernetesEndpoint(endpoint) {
|
||||
fileName = edgeStack.ManifestPath
|
||||
|
||||
if fileName == "" {
|
||||
requestLogger.Error("Kubernetes is not supported by this stack")
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
stackFileContent, err := handler.FileService.GetFileContent(edgeStack.ProjectPath, fileName)
|
||||
if err != nil {
|
||||
requestLogger.WithError(err).Error("Unable to retrieve Compose file from disk")
|
||||
continue
|
||||
}
|
||||
|
||||
stackStatus := edgeStackData{
|
||||
StackFileContent: string(stackFileContent),
|
||||
Name: edgeStack.Name,
|
||||
ID: stack.ID,
|
||||
Version: stack.Version,
|
||||
}
|
||||
|
||||
cmd := JSONPatch{
|
||||
Operation: "add",
|
||||
Path: fmt.Sprintf("/edgestack/%d", stack.ID),
|
||||
Value: stackStatus,
|
||||
}
|
||||
commandList = append(commandList, cmd)
|
||||
}
|
||||
return commandList
|
||||
}
|
||||
|
||||
// TODO: this probably should be in the data layer.. (like, somewhere that depends dataservices/errors)
|
||||
func (handler *Handler) getEdgeEndpoint(edgeIdentifier string) (*portainer.Endpoint, error) {
|
||||
endpoints, err := handler.DataStore.Endpoint().Endpoints()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, endpoint := range endpoints {
|
||||
if endpoint.EdgeID == edgeIdentifier {
|
||||
return &endpoint, nil
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
@@ -31,5 +31,7 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler {
|
||||
bouncer.PublicAccess(httperror.LoggerHandler(h.endpointEdgeStackInspect))).Methods(http.MethodGet)
|
||||
h.Handle("/{id}/edge/jobs/{jobID}/logs",
|
||||
bouncer.PublicAccess(httperror.LoggerHandler(h.endpointEdgeJobsLogs))).Methods(http.MethodPost)
|
||||
h.Handle("/edge/async/",
|
||||
bouncer.PublicAccess(httperror.LoggerHandler(h.endpointAsync))).Methods(http.MethodPost)
|
||||
return h
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ func (handler *Handler) endpointSnapshot(w http.ResponseWriter, r *http.Request)
|
||||
|
||||
snapshotError := handler.SnapshotService.SnapshotEndpoint(endpoint)
|
||||
|
||||
// TODO: so huh? why are we getting the endpoint a second time? if there's a reason to do this - please add that as a comment!
|
||||
latestEndpointReference, err := handler.DataStore.Endpoint().Endpoint(endpoint.ID)
|
||||
if latestEndpointReference == nil {
|
||||
return &httperror.HandlerError{http.StatusNotFound, "Unable to find an environment with the specified identifier inside the database", err}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type stackStatusResponse struct {
|
||||
@@ -48,6 +49,7 @@ type endpointStatusInspectResponse struct {
|
||||
Stacks []stackStatusResponse `json:"stacks"`
|
||||
}
|
||||
|
||||
// TODO: first up, why is this not in ../endpointedge/???
|
||||
// @id EndpointStatusInspect
|
||||
// @summary Get environment(endpoint) status
|
||||
// @description Environment(Endpoint) for edge agent to check status of environment(endpoint)
|
||||
@@ -70,6 +72,7 @@ func (handler *Handler) endpointStatusInspect(w http.ResponseWriter, r *http.Req
|
||||
|
||||
endpoint, err := handler.DataStore.Endpoint().Endpoint(portainer.EndpointID(endpointID))
|
||||
if handler.DataStore.IsErrObjectNotFound(err) {
|
||||
logrus.WithError(err).WithField("env", endpointID).WithField("remote", r.RemoteAddr).Error("Unable to find an environment with the specified identifier inside the database")
|
||||
return &httperror.HandlerError{http.StatusNotFound, "Unable to find an environment with the specified identifier inside the database", err}
|
||||
} else if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find an environment with the specified identifier inside the database", err}
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"github.com/portainer/portainer/api/http/handler/hostmanagement/openamt"
|
||||
"github.com/portainer/portainer/api/http/handler/kubernetes"
|
||||
"github.com/portainer/portainer/api/http/handler/ldap"
|
||||
"github.com/portainer/portainer/api/http/handler/metrics"
|
||||
"github.com/portainer/portainer/api/http/handler/motd"
|
||||
"github.com/portainer/portainer/api/http/handler/registries"
|
||||
"github.com/portainer/portainer/api/http/handler/resourcecontrols"
|
||||
@@ -77,6 +78,7 @@ type Handler struct {
|
||||
UserHandler *users.Handler
|
||||
WebSocketHandler *websocket.Handler
|
||||
WebhookHandler *webhooks.Handler
|
||||
MetricsHandler *metrics.Handler
|
||||
}
|
||||
|
||||
// @title PortainerCE API
|
||||
@@ -241,6 +243,10 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
http.StripPrefix("/api", h.WebhookHandler).ServeHTTP(w, r)
|
||||
case strings.HasPrefix(r.URL.Path, "/storybook"):
|
||||
http.StripPrefix("/storybook", h.StorybookHandler).ServeHTTP(w, r)
|
||||
case strings.HasPrefix(r.URL.Path, "/metrics"):
|
||||
if h.MetricsHandler != nil {
|
||||
h.MetricsHandler.ServeHTTP(w, r)
|
||||
}
|
||||
case strings.HasPrefix(r.URL.Path, "/"):
|
||||
h.FileHandler.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
33
api/http/handler/metrics/handler.go
Normal file
33
api/http/handler/metrics/handler.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"github.com/google/gops/agent"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/portainer/portainer/api/http/security"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Handler is the HTTP handler used to handle Prometheus metrics operations.
|
||||
type Handler struct {
|
||||
*mux.Router
|
||||
}
|
||||
|
||||
// NewHandler creates a handler to manage settings operations.
|
||||
func NewHandler(bouncer *security.RequestBouncer) *Handler {
|
||||
h := &Handler{
|
||||
Router: mux.NewRouter(),
|
||||
}
|
||||
h.Handle("/metrics", promhttp.Handler())
|
||||
// h.Handle("/metrics", bouncer.PublicAccess(promhttp.Handler()))
|
||||
logrus.Debugf("metricsHandler creation")
|
||||
|
||||
// also add gops agent support
|
||||
if err := agent.Listen(agent.Options{}); err != nil {
|
||||
logrus.WithError(err).Debugf("failed to start gops agent")
|
||||
} else {
|
||||
logrus.Debug("started gops agent")
|
||||
}
|
||||
|
||||
return h
|
||||
}
|
||||
@@ -211,6 +211,9 @@ func (handler *Handler) deployStack(r *http.Request, stack *portainer.Stack, end
|
||||
}
|
||||
|
||||
case portainer.KubernetesStack:
|
||||
if stack.Namespace == "" {
|
||||
return &httperror.HandlerError{StatusCode: http.StatusInternalServerError, Message: "Invalid namespace", Err: errors.New("Namespace must not be empty when redeploying kubernetes stacks")}
|
||||
}
|
||||
tokenData, err := security.RetrieveTokenData(r)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{StatusCode: http.StatusBadRequest, Message: "Failed to retrieve user token data", Err: err}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package security
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -12,6 +14,9 @@ import (
|
||||
"github.com/portainer/portainer/api/apikey"
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
httperrors "github.com/portainer/portainer/api/http/errors"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -37,6 +42,21 @@ type (
|
||||
|
||||
const apiKeyHeader = "X-API-KEY"
|
||||
|
||||
var (
|
||||
apiProcessed = promauto.NewCounterVec(prometheus.CounterOpts{
|
||||
Name: "portainer_api_bouncer_results",
|
||||
Help: "The total number of request access success/failures",
|
||||
},
|
||||
[]string{"permission", "path"},
|
||||
)
|
||||
agentApiProcessed = promauto.NewCounterVec(prometheus.CounterOpts{
|
||||
Name: "portainer_agent_api_bouncer_results",
|
||||
Help: "The total number of agent request access success/failures",
|
||||
},
|
||||
[]string{"permission", "path"},
|
||||
)
|
||||
)
|
||||
|
||||
// NewRequestBouncer initializes a new RequestBouncer
|
||||
func NewRequestBouncer(dataStore dataservices.DataStore, jwtService dataservices.JWTService, apiKeyService apikey.APIKeyService) *RequestBouncer {
|
||||
return &RequestBouncer{
|
||||
@@ -96,58 +116,111 @@ func (bouncer *RequestBouncer) AuthenticatedAccess(h http.Handler) http.Handler
|
||||
func (bouncer *RequestBouncer) AuthorizedEndpointOperation(r *http.Request, endpoint *portainer.Endpoint) error {
|
||||
tokenData, err := RetrieveTokenData(r)
|
||||
if err != nil {
|
||||
apiProcessed.With(prometheus.Labels{"path": r.RequestURI, "permission": "error"}).Inc()
|
||||
return err
|
||||
}
|
||||
|
||||
if tokenData.Role == portainer.AdministratorRole {
|
||||
apiProcessed.With(prometheus.Labels{"path": r.RequestURI, "permission": "admin"}).Inc()
|
||||
return nil
|
||||
}
|
||||
|
||||
memberships, err := bouncer.dataStore.TeamMembership().TeamMembershipsByUserID(tokenData.ID)
|
||||
if err != nil {
|
||||
apiProcessed.With(prometheus.Labels{"path": r.RequestURI, "permission": "error"}).Inc()
|
||||
return err
|
||||
}
|
||||
|
||||
group, err := bouncer.dataStore.EndpointGroup().EndpointGroup(endpoint.GroupID)
|
||||
if err != nil {
|
||||
apiProcessed.With(prometheus.Labels{"path": r.RequestURI, "permission": "error"}).Inc()
|
||||
return err
|
||||
}
|
||||
|
||||
if !authorizedEndpointAccess(endpoint, group, tokenData.ID, memberships) {
|
||||
apiProcessed.With(prometheus.Labels{"permission": "denied"}).Inc()
|
||||
return httperrors.ErrEndpointAccessDenied
|
||||
}
|
||||
|
||||
apiProcessed.With(prometheus.Labels{"path": r.RequestURI, "permission": "ok"}).Inc()
|
||||
return nil
|
||||
}
|
||||
|
||||
// AuthorizedEdgeEndpointOperation verifies that the request was received from a valid Edge environment(endpoint)
|
||||
func (bouncer *RequestBouncer) AuthorizedEdgeEndpointOperation(r *http.Request, endpoint *portainer.Endpoint) error {
|
||||
// tls.RequireAndVerifyClientCert would be nice, but that would require the same certs for browser and api use
|
||||
sslsettings, _ := bouncer.dataStore.SSLSettings().Settings()
|
||||
if sslsettings.CacertPath != "" {
|
||||
// if a caCert is set, then reject any requests that don't have a client Auth cert signed with it
|
||||
if len(r.TLS.PeerCertificates) == 0 {
|
||||
logrus.Error("No clientAuth Agent certificate offered")
|
||||
return errors.New("No clientAuth Agent certificate offered")
|
||||
}
|
||||
|
||||
caChainIdx := len(r.TLS.VerifiedChains)
|
||||
chainCaCert := r.TLS.VerifiedChains[0][caChainIdx]
|
||||
|
||||
caCert, _ := ioutil.ReadFile(sslsettings.CacertPath)
|
||||
certPool := x509.NewCertPool()
|
||||
certPool.AppendCertsFromPEM(caCert)
|
||||
|
||||
logrus.
|
||||
WithField("chain Subject", chainCaCert.Subject.String()).
|
||||
WithField("tls DNSNames", chainCaCert.DNSNames).
|
||||
WithField("Agent Addr", r.RemoteAddr).
|
||||
WithField("Agent Version", r.Header.Get(portainer.HTTPAgentVersionHeaderName)).
|
||||
WithField("Agent PID", r.Header.Get(portainer.HTTPAgentPIDName)).
|
||||
WithField("Agent EdgeID", r.Header.Get(portainer.PortainerAgentEdgeIDHeader)).
|
||||
Debugf("TLS client chain")
|
||||
|
||||
opts := x509.VerifyOptions{
|
||||
//DNSName: name, // Not normally used on server side - important on the client side
|
||||
Roots: certPool, // as used in ListenAndServe
|
||||
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
|
||||
}
|
||||
remoteCert := r.TLS.PeerCertificates[0]
|
||||
|
||||
if _, err := remoteCert.Verify(opts); err != nil {
|
||||
logrus.WithError(err).Error("Agent certificate not signed by the CACert")
|
||||
return errors.New("Agent certificate wasn't signed by required CA Cert")
|
||||
}
|
||||
|
||||
// TODO: test revoke cert list.
|
||||
}
|
||||
|
||||
if endpoint.Type != portainer.EdgeAgentOnKubernetesEnvironment && endpoint.Type != portainer.EdgeAgentOnDockerEnvironment {
|
||||
agentApiProcessed.With(prometheus.Labels{"path": r.RequestURI, "permission": "edge_error"}).Inc()
|
||||
return errors.New("Invalid environment type")
|
||||
}
|
||||
|
||||
edgeIdentifier := r.Header.Get(portainer.PortainerAgentEdgeIDHeader)
|
||||
if edgeIdentifier == "" {
|
||||
agentApiProcessed.With(prometheus.Labels{"path": r.RequestURI, "permission": "edge_noiderror"}).Inc()
|
||||
return errors.New("missing Edge identifier")
|
||||
}
|
||||
|
||||
if endpoint.EdgeID != "" && endpoint.EdgeID != edgeIdentifier {
|
||||
agentApiProcessed.With(prometheus.Labels{"path": r.RequestURI, "permission": "edge_iderror"}).Inc()
|
||||
return errors.New("invalid Edge identifier")
|
||||
}
|
||||
|
||||
if endpoint.LastCheckInDate > 0 || endpoint.UserTrusted {
|
||||
agentApiProcessed.With(prometheus.Labels{"path": r.RequestURI, "permission": "edge_ok"}).Inc()
|
||||
return nil
|
||||
}
|
||||
|
||||
settings, err := bouncer.dataStore.Settings().Settings()
|
||||
if err != nil {
|
||||
agentApiProcessed.With(prometheus.Labels{"path": r.RequestURI, "permission": "edge_error"}).Inc()
|
||||
return fmt.Errorf("could not retrieve the settings: %w", err)
|
||||
}
|
||||
|
||||
if settings.DisableTrustOnFirstConnect {
|
||||
agentApiProcessed.With(prometheus.Labels{"path": r.RequestURI, "permission": "edge_untrusted"}).Inc()
|
||||
return errors.New("the device has not been trusted yet")
|
||||
}
|
||||
|
||||
agentApiProcessed.With(prometheus.Labels{"path": r.RequestURI, "permission": "edge_ok"}).Inc()
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package http
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
@@ -34,6 +35,7 @@ import (
|
||||
"github.com/portainer/portainer/api/http/handler/hostmanagement/openamt"
|
||||
kubehandler "github.com/portainer/portainer/api/http/handler/kubernetes"
|
||||
"github.com/portainer/portainer/api/http/handler/ldap"
|
||||
"github.com/portainer/portainer/api/http/handler/metrics"
|
||||
"github.com/portainer/portainer/api/http/handler/motd"
|
||||
"github.com/portainer/portainer/api/http/handler/registries"
|
||||
"github.com/portainer/portainer/api/http/handler/resourcecontrols"
|
||||
@@ -61,6 +63,7 @@ import (
|
||||
"github.com/portainer/portainer/api/kubernetes/cli"
|
||||
"github.com/portainer/portainer/api/scheduler"
|
||||
stackdeployer "github.com/portainer/portainer/api/stacks"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Server implements the portainer.Server interface
|
||||
@@ -263,6 +266,11 @@ func (server *Server) Start() error {
|
||||
webhookHandler.DataStore = server.DataStore
|
||||
webhookHandler.DockerClientFactory = server.DockerClientFactory
|
||||
|
||||
var metricsHandler *metrics.Handler
|
||||
if server.DataStore.Settings().IsFeatureFlagEnabled("dev-metrics") {
|
||||
metricsHandler = metrics.NewHandler(requestBouncer)
|
||||
}
|
||||
|
||||
server.Handler = &handler.Handler{
|
||||
RoleHandler: roleHandler,
|
||||
AuthHandler: authHandler,
|
||||
@@ -299,6 +307,7 @@ func (server *Server) Start() error {
|
||||
UserHandler: userHandler,
|
||||
WebSocketHandler: websocketHandler,
|
||||
WebhookHandler: webhookHandler,
|
||||
MetricsHandler: metricsHandler,
|
||||
}
|
||||
|
||||
handler := offlineGate.WaitingMiddleware(time.Minute, server.Handler)
|
||||
@@ -330,6 +339,17 @@ func (server *Server) Start() error {
|
||||
return server.SSLService.GetRawCertificate(), nil
|
||||
}
|
||||
|
||||
if caCert := server.SSLService.GetCacertificatePem(); len(caCert) > 0 {
|
||||
logrus.Debugf("using CA certificate for %s", server.BindAddressHTTPS)
|
||||
certPool := x509.NewCertPool()
|
||||
certPool.AppendCertsFromPEM(caCert)
|
||||
|
||||
httpsServer.TLSConfig.ClientCAs = certPool
|
||||
// can't use tls.RequireAndVerifyClientCert, and this port is also used for the browser (though it would be a strong feature to allow the user to enable)
|
||||
httpsServer.TLSConfig.ClientAuth = tls.VerifyClientCertIfGiven
|
||||
httpsServer.TLSConfig.BuildNameToCertificate()
|
||||
}
|
||||
|
||||
go shutdown(server.ShutdownCtx, httpsServer)
|
||||
return httpsServer.ListenAndServeTLS("", "")
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package ssl
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
@@ -31,7 +32,7 @@ func NewService(fileService portainer.FileService, dataStore dataservices.DataSt
|
||||
}
|
||||
|
||||
// Init initializes the service
|
||||
func (service *Service) Init(host, certPath, keyPath string) error {
|
||||
func (service *Service) Init(host, certPath, keyPath, cacertPath string) error {
|
||||
pathSupplied := certPath != "" && keyPath != ""
|
||||
if pathSupplied {
|
||||
newCertPath, newKeyPath, err := service.fileService.CopySSLCertPair(certPath, keyPath)
|
||||
@@ -39,7 +40,19 @@ func (service *Service) Init(host, certPath, keyPath string) error {
|
||||
return errors.Wrap(err, "failed copying supplied certs")
|
||||
}
|
||||
|
||||
return service.cacheInfo(newCertPath, newKeyPath, false)
|
||||
newCacertPath := ""
|
||||
if cacertPath != "" {
|
||||
newCacertPath, err = service.fileService.CopySSLCacert(cacertPath)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed copying supplied cacert")
|
||||
}
|
||||
}
|
||||
|
||||
return service.cacheInfo(newCertPath, newKeyPath, newCacertPath, false)
|
||||
}
|
||||
if cacertPath != "" {
|
||||
return errors.Errorf("supplying a CA cert path (%s) requires an SSL cert and key file", cacertPath)
|
||||
|
||||
}
|
||||
|
||||
settings, err := service.GetSSLSettings()
|
||||
@@ -68,10 +81,24 @@ func (service *Service) Init(host, certPath, keyPath string) error {
|
||||
return errors.Wrap(err, "failed generating self signed certs")
|
||||
}
|
||||
|
||||
return service.cacheInfo(certPath, keyPath, true)
|
||||
return service.cacheInfo(certPath, keyPath, "", true)
|
||||
|
||||
}
|
||||
|
||||
// GetRawCertificate gets the raw certificate
|
||||
func (service *Service) GetCacertificatePem() (pemData []byte) {
|
||||
settings, _ := service.GetSSLSettings()
|
||||
if settings.CacertPath == "" {
|
||||
return pemData
|
||||
}
|
||||
caCert, err := ioutil.ReadFile(settings.CacertPath)
|
||||
if err != nil {
|
||||
log.Printf("reading ca cert: %s", err)
|
||||
return pemData
|
||||
}
|
||||
return caCert
|
||||
}
|
||||
|
||||
// GetRawCertificate gets the raw certificate
|
||||
func (service *Service) GetRawCertificate() *tls.Certificate {
|
||||
return service.rawCert
|
||||
@@ -98,7 +125,13 @@ func (service *Service) SetCertificates(certData, keyData []byte) error {
|
||||
return err
|
||||
}
|
||||
|
||||
service.cacheInfo(certPath, keyPath, false)
|
||||
settings, err := service.dataStore.SSLSettings().Settings()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Don't unset the settings.CacertPath when uploading a new cert from the UI
|
||||
// TODO: should also add UI to update thecacert, or to disable it..
|
||||
service.cacheInfo(certPath, keyPath, settings.CacertPath, false)
|
||||
|
||||
service.shutdownTrigger()
|
||||
|
||||
@@ -127,6 +160,7 @@ func (service *Service) SetHTTPEnabled(httpEnabled bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
//TODO: why is this being cached in memory? is it actually loaded more than once?
|
||||
func (service *Service) cacheCertificate(certPath, keyPath string) error {
|
||||
rawCert, err := tls.LoadX509KeyPair(certPath, keyPath)
|
||||
if err != nil {
|
||||
@@ -138,7 +172,7 @@ func (service *Service) cacheCertificate(certPath, keyPath string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (service *Service) cacheInfo(certPath, keyPath string, selfSigned bool) error {
|
||||
func (service *Service) cacheInfo(certPath, keyPath, cacertPath string, selfSigned bool) error {
|
||||
err := service.cacheCertificate(certPath, keyPath)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -151,6 +185,7 @@ func (service *Service) cacheInfo(certPath, keyPath string, selfSigned bool) err
|
||||
|
||||
settings.CertPath = certPath
|
||||
settings.KeyPath = keyPath
|
||||
settings.CacertPath = cacertPath
|
||||
settings.SelfSigned = selfSigned
|
||||
|
||||
err = service.dataStore.SSLSettings().UpdateSettings(settings)
|
||||
|
||||
@@ -116,6 +116,7 @@ type (
|
||||
HTTPDisabled *bool
|
||||
HTTPEnabled *bool
|
||||
SSL *bool
|
||||
SSLCacert *string
|
||||
SSLCert *string
|
||||
SSLKey *string
|
||||
Rollback *bool
|
||||
@@ -343,6 +344,17 @@ type (
|
||||
|
||||
// Deprecated in DBVersion == 22
|
||||
Tags []string `json:"Tags"`
|
||||
|
||||
AgentHistory map[string]AgentInfo
|
||||
}
|
||||
|
||||
AgentInfo struct {
|
||||
LastCheckInDate int64
|
||||
CheckInCount int64
|
||||
Version string
|
||||
RemoteAddr string
|
||||
Status string
|
||||
//UniqueId string
|
||||
}
|
||||
|
||||
// EndpointAuthorizations represents the authorizations associated to a set of environments(endpoints)
|
||||
@@ -837,6 +849,7 @@ type (
|
||||
SSLSettings struct {
|
||||
CertPath string `json:"certPath"`
|
||||
KeyPath string `json:"keyPath"`
|
||||
CacertPath string `json:"cacertPath"`
|
||||
SelfSigned bool `json:"selfSigned"`
|
||||
HTTPEnabled bool `json:"httpEnabled"`
|
||||
}
|
||||
@@ -1236,6 +1249,7 @@ type (
|
||||
GetDefaultSSLCertsPath() (string, string)
|
||||
StoreSSLCertPair(cert, key []byte) (string, string, error)
|
||||
CopySSLCertPair(certPath, keyPath string) (string, string, error)
|
||||
CopySSLCacert(cacertPath string) (string, error)
|
||||
StoreFDOProfileFileFromBytes(fdoProfileIdentifier string, data []byte) (string, error)
|
||||
}
|
||||
|
||||
@@ -1363,6 +1377,10 @@ const (
|
||||
PortainerAgentKubernetesSATokenHeader = "X-PortainerAgent-SA-Token"
|
||||
// PortainerAgentSignatureMessage represents the message used to create a digital signature
|
||||
// to be used when communicating with an agent
|
||||
HTTPAgentVersionHeaderName = "X-Portainer-Agent-Version"
|
||||
HTTPAgentPIDName = "X-Portainer-Process-Id"
|
||||
HTTPAgentUUIDHeaderName = "X-Portainer-Agent-UUID"
|
||||
|
||||
PortainerAgentSignatureMessage = "Portainer-App"
|
||||
// DefaultSnapshotInterval represents the default interval between each environment snapshot job
|
||||
DefaultSnapshotInterval = "5m"
|
||||
@@ -1383,7 +1401,7 @@ const (
|
||||
)
|
||||
|
||||
// List of supported features
|
||||
var SupportedFeatureFlags = []Feature{}
|
||||
var SupportedFeatureFlags = []Feature{"dev-metrics"}
|
||||
|
||||
const (
|
||||
_ AuthenticationMethod = iota
|
||||
|
||||
@@ -3,7 +3,6 @@ import _ from 'lodash-es';
|
||||
import { PorImageRegistryModel } from 'Docker/models/porImageRegistry';
|
||||
|
||||
import * as envVarsUtils from '@/portainer/helpers/env-vars';
|
||||
import { FeatureId } from 'Portainer/feature-flags/enums';
|
||||
import { ContainerCapabilities, ContainerCapability } from '../../../models/containerCapabilities';
|
||||
import { AccessControlFormData } from '../../../../portainer/components/accessControlForm/porAccessControlFormModel';
|
||||
import { ContainerDetailsViewModel } from '../../../models/container';
|
||||
@@ -66,7 +65,7 @@ angular.module('portainer.docker').controller('CreateContainerController', [
|
||||
$scope.create = create;
|
||||
$scope.update = update;
|
||||
$scope.endpoint = endpoint;
|
||||
$scope.containerWebhookFeature = FeatureId.CONTAINER_WEBHOOK;
|
||||
|
||||
$scope.formValues = {
|
||||
alwaysPull: true,
|
||||
Console: 'none',
|
||||
|
||||
@@ -65,28 +65,6 @@
|
||||
</por-image-registry>
|
||||
<!-- !image-and-registry -->
|
||||
</div>
|
||||
<!-- create-webhook -->
|
||||
<div ng-if="isAdmin && applicationState.endpoint.type !== 4">
|
||||
<div class="col-sm-12 form-section-title"> Webhooks </div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<label class="control-label text-left">
|
||||
Create a container webhook
|
||||
<portainer-tooltip
|
||||
position="top"
|
||||
message="Create a webhook (or callback URI) to automate the recreate this container. Sending a POST request to this callback URI (without requiring any authentication) will pull the most up-to-date version of the associated image and recreate this container."
|
||||
></portainer-tooltip>
|
||||
</label>
|
||||
<label class="switch box-selector-item limited business" style="margin-left: 20px">
|
||||
<input type="checkbox" ng-model="formValues.EnableWebhook" disabled="disabled" ng-checked="true" />
|
||||
<i class="orange-icon" aria-hidden="true" style="margin-right: 2px"></i>
|
||||
</label>
|
||||
<be-feature-indicator feature="containerWebhookFeature"></be-feature-indicator>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !create-webhook -->
|
||||
|
||||
<div class="col-sm-12 form-section-title"> Network ports configuration </div>
|
||||
<!-- publish-exposed-ports -->
|
||||
<div class="form-group">
|
||||
|
||||
@@ -110,19 +110,6 @@
|
||||
<td>Finished</td>
|
||||
<td>{{ container.State.FinishedAt | getisodate }}</td>
|
||||
</tr>
|
||||
<tr ng-if="isAdmin && displayRecreateButton && applicationState.endpoint.type !== 4">
|
||||
<td colspan="1">
|
||||
Container webhook
|
||||
<portainer-tooltip
|
||||
position="top"
|
||||
message="Webhook (or callback URI) used to automate the recreate this container. Sending a POST request to this callback URI (without requiring any authentication) will pull the most up-to-date version of the associated image and recreate this container."
|
||||
></portainer-tooltip>
|
||||
<label class="switch box-selector-item limited business" style="margin-left: 20px">
|
||||
<input disable-authorization="DockerContainerUpdate" type="checkbox" ng-model="WebhookExists" disabled="disabled" ng-checked="true" /><i></i>
|
||||
</label>
|
||||
<be-feature-indicator feature="containerWebhookFeature"></be-feature-indicator>
|
||||
</td>
|
||||
</tr>
|
||||
<tr authorization="DockerContainerLogs, DockerContainerInspect, DockerContainerStats, DockerExecStart">
|
||||
<td colspan="2">
|
||||
<div class="btn-group" role="group" aria-label="...">
|
||||
|
||||
@@ -2,7 +2,6 @@ import moment from 'moment';
|
||||
import _ from 'lodash-es';
|
||||
import { PorImageRegistryModel } from 'Docker/models/porImageRegistry';
|
||||
import { confirmContainerDeletion } from '@/portainer/services/modal.service/prompt';
|
||||
import { FeatureId } from 'Portainer/feature-flags/enums';
|
||||
|
||||
angular.module('portainer.docker').controller('ContainerController', [
|
||||
'$q',
|
||||
@@ -50,7 +49,6 @@ angular.module('portainer.docker').controller('ContainerController', [
|
||||
$scope.activityTime = 0;
|
||||
$scope.portBindings = [];
|
||||
$scope.displayRecreateButton = false;
|
||||
$scope.containerWebhookFeature = FeatureId.CONTAINER_WEBHOOK;
|
||||
|
||||
$scope.config = {
|
||||
RegistryModel: new PorImageRegistryModel(),
|
||||
|
||||
@@ -206,7 +206,6 @@ export function EdgeDevicesDatatable({
|
||||
<RowProvider
|
||||
key={key}
|
||||
disableTrustOnFirstConnect={disableTrustOnFirstConnect}
|
||||
isOpenAmtEnabled={isOpenAmtEnabled}
|
||||
>
|
||||
<TableRow<Environment>
|
||||
cells={row.cells}
|
||||
|
||||
@@ -2,24 +2,21 @@ import { createContext, useContext, useMemo, PropsWithChildren } from 'react';
|
||||
|
||||
interface RowContextState {
|
||||
disableTrustOnFirstConnect: boolean;
|
||||
isOpenAmtEnabled: boolean;
|
||||
}
|
||||
|
||||
const RowContext = createContext<RowContextState | null>(null);
|
||||
|
||||
export interface RowProviderProps {
|
||||
disableTrustOnFirstConnect: boolean;
|
||||
isOpenAmtEnabled: boolean;
|
||||
}
|
||||
|
||||
export function RowProvider({
|
||||
disableTrustOnFirstConnect,
|
||||
isOpenAmtEnabled,
|
||||
children,
|
||||
}: PropsWithChildren<RowProviderProps>) {
|
||||
const state = useMemo(
|
||||
() => ({ disableTrustOnFirstConnect, isOpenAmtEnabled }),
|
||||
[disableTrustOnFirstConnect, isOpenAmtEnabled]
|
||||
() => ({ disableTrustOnFirstConnect }),
|
||||
[disableTrustOnFirstConnect]
|
||||
);
|
||||
|
||||
return <RowContext.Provider value={state}>{children}</RowContext.Provider>;
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { CellProps, Column } from 'react-table';
|
||||
import { CellProps, Column, TableInstance } from 'react-table';
|
||||
|
||||
import { Environment } from '@/portainer/environments/types';
|
||||
import { Link } from '@/portainer/components/Link';
|
||||
import { ExpandingCell } from '@/portainer/components/datatables/components/ExpandingCell';
|
||||
import { useRowContext } from '@/edge/devices/components/EdgeDevicesDatatable/columns/RowContext';
|
||||
|
||||
export const name: Column<Environment> = {
|
||||
Header: 'Name',
|
||||
@@ -16,15 +15,9 @@ export const name: Column<Environment> = {
|
||||
sortType: 'string',
|
||||
};
|
||||
|
||||
export function NameCell({ value: name, row }: CellProps<Environment>) {
|
||||
const { isOpenAmtEnabled } = useRowContext();
|
||||
const showExpandedRow = !!(
|
||||
isOpenAmtEnabled &&
|
||||
row.original.AMTDeviceGUID &&
|
||||
row.original.AMTDeviceGUID.length > 0
|
||||
);
|
||||
export function NameCell({ value: name, row }: CellProps<TableInstance>) {
|
||||
return (
|
||||
<ExpandingCell row={row} showExpandArrow={showExpandedRow}>
|
||||
<ExpandingCell row={row}>
|
||||
<Link
|
||||
to="portainer.endpoints.endpoint"
|
||||
params={{ id: row.original.Id }}
|
||||
|
||||
@@ -8,18 +8,18 @@
|
||||
}
|
||||
|
||||
.table-actions-menu-list {
|
||||
padding: 0 10px 0 10px;
|
||||
background: var(--bg-widget-color);
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.table-actions-menu-list [data-reach-menu-item] {
|
||||
padding: 5px 15px;
|
||||
padding: 5px 5px !important;
|
||||
}
|
||||
|
||||
.table-actions-menu-btn {
|
||||
border: none;
|
||||
background: none;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
[data-reach-menu-link] {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
.table-actions-title {
|
||||
color: var(--blue-2);
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
.expand-button {
|
||||
background: none;
|
||||
color: inherit;
|
||||
border: none;
|
||||
padding: 0;
|
||||
font: inherit;
|
||||
cursor: pointer;
|
||||
outline: inherit;
|
||||
}
|
||||
@@ -1,30 +1,20 @@
|
||||
import { PropsWithChildren } from 'react';
|
||||
import { Row } from 'react-table';
|
||||
import { Row, TableInstance } from 'react-table';
|
||||
|
||||
import styles from './ExpandingCell.module.css';
|
||||
|
||||
interface Props<D extends Record<string, unknown> = Record<string, unknown>> {
|
||||
row: Row<D>;
|
||||
showExpandArrow: boolean;
|
||||
interface Props {
|
||||
row: Row<TableInstance>;
|
||||
}
|
||||
|
||||
export function ExpandingCell<
|
||||
D extends Record<string, unknown> = Record<string, unknown>
|
||||
>({ row, showExpandArrow, children }: PropsWithChildren<Props<D>>) {
|
||||
export function ExpandingCell({ children, row }: PropsWithChildren<Props>) {
|
||||
return (
|
||||
<>
|
||||
{showExpandArrow && (
|
||||
<button type="button" className={styles.expandButton}>
|
||||
<i
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...row.getToggleRowExpandedProps()}
|
||||
className={`fas ${arrowClass(row.isExpanded)} space-right`}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</button>
|
||||
)}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
<div {...row.getToggleRowExpandedProps()}>
|
||||
<i
|
||||
className={`fas ${arrowClass(row.isExpanded)} space-right`}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{children}
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
|
||||
function arrowClass(isExpanded: boolean) {
|
||||
|
||||
@@ -10,7 +10,6 @@ class GitFormAutoUpdateFieldsetController {
|
||||
this.onChangeInterval = this.onChangeField('RepositoryFetchInterval');
|
||||
|
||||
this.limitedFeature = FeatureId.FORCE_REDEPLOYMENT;
|
||||
this.stackPullImageFeature = FeatureId.STACK_PULL_IMAGE;
|
||||
}
|
||||
|
||||
copyWebhook() {
|
||||
|
||||
@@ -51,11 +51,6 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-if="$ctrl.model.ShowForcePullImage && $ctrl.model.RepositoryAutomaticUpdates">
|
||||
<div class="col-sm-12">
|
||||
<por-switch-field name="forcePullImage" feature-id="$ctrl.stackPullImageFeature" checked="$ctrl.model.ForcePullImage" label="'Pull latest image'"> </por-switch-field>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-if="$ctrl.model.RepositoryAutomaticUpdates">
|
||||
<div class="col-sm-12">
|
||||
<por-switch-field
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
import uuidv4 from 'uuid/v4';
|
||||
import { RepositoryMechanismTypes } from 'Kubernetes/models/deploy';
|
||||
import { FeatureId } from 'Portainer/feature-flags/enums';
|
||||
class StackRedeployGitFormController {
|
||||
/* @ngInject */
|
||||
constructor($async, $state, $compile, $scope, StackService, ModalService, Notifications, WebhookHelper, FormHelper) {
|
||||
constructor($async, $state, StackService, ModalService, Notifications, WebhookHelper, FormHelper) {
|
||||
this.$async = $async;
|
||||
this.$state = $state;
|
||||
this.$compile = $compile;
|
||||
this.$scope = $scope;
|
||||
this.StackService = StackService;
|
||||
this.ModalService = ModalService;
|
||||
this.Notifications = Notifications;
|
||||
this.WebhookHelper = WebhookHelper;
|
||||
this.FormHelper = FormHelper;
|
||||
$scope.stackPullImageFeature = FeatureId.STACK_PULL_IMAGE;
|
||||
|
||||
this.state = {
|
||||
inProgress: false,
|
||||
redeployInProgress: false,
|
||||
@@ -34,7 +31,6 @@ class StackRedeployGitFormController {
|
||||
RepositoryMechanism: RepositoryMechanismTypes.INTERVAL,
|
||||
RepositoryFetchInterval: '5m',
|
||||
RepositoryWebhookURL: '',
|
||||
ShowForcePullImage: false,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -90,21 +86,27 @@ class StackRedeployGitFormController {
|
||||
}
|
||||
|
||||
async submit() {
|
||||
const tplCrop =
|
||||
'<div>Any changes to this stack or application made locally in Portainer will be overridden, which may cause service interruption.</div>' +
|
||||
'<div"><div style="position: absolute; right: 110px; top: 68px; z-index: 999">' +
|
||||
'<be-feature-indicator feature="stackPullImageFeature"></be-feature-indicator></div></div>';
|
||||
const template = angular.element(tplCrop);
|
||||
const html = this.$compile(template)(this.$scope);
|
||||
this.ModalService.confirmStackUpdate(html, true, true, 'btn-warning', function (result) {
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
return this.$async(async () => {
|
||||
try {
|
||||
const confirmed = await this.ModalService.confirmAsync({
|
||||
title: 'Are you sure?',
|
||||
message: 'Any changes to this stack or application made locally in Portainer will be overridden, which may cause service interruption.',
|
||||
buttons: {
|
||||
confirm: {
|
||||
label: 'Update',
|
||||
className: 'btn-warning',
|
||||
},
|
||||
},
|
||||
});
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.state.redeployInProgress = true;
|
||||
this.StackService.updateGit(this.stack.Id, this.stack.EndpointId, this.FormHelper.removeInvalidEnvVars(this.formValues.Env), false, this.formValues);
|
||||
|
||||
await this.StackService.updateGit(this.stack.Id, this.stack.EndpointId, this.FormHelper.removeInvalidEnvVars(this.formValues.Env), false, this.formValues);
|
||||
this.Notifications.success('Pulled and redeployed stack successfully');
|
||||
this.$state.reload();
|
||||
await this.$state.reload();
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Failed redeploying stack');
|
||||
} finally {
|
||||
@@ -152,7 +154,6 @@ class StackRedeployGitFormController {
|
||||
// Init auto update
|
||||
if (this.stack.AutoUpdate && (this.stack.AutoUpdate.Interval || this.stack.AutoUpdate.Webhook)) {
|
||||
this.formValues.AutoUpdate.RepositoryAutomaticUpdates = true;
|
||||
this.formValues.AutoUpdate.ShowForcePullImage = this.stack.Type !== 3;
|
||||
|
||||
if (this.stack.AutoUpdate.Interval) {
|
||||
this.formValues.AutoUpdate.RepositoryMechanism = RepositoryMechanismTypes.INTERVAL;
|
||||
|
||||
@@ -16,26 +16,12 @@ function TemplateListController($async, $state, DatatableService, Notifications,
|
||||
DatatableService.setDataTableTextFilters(this.tableKey, this.state.textFilter);
|
||||
};
|
||||
|
||||
this.filterByTemplateType = function (item) {
|
||||
switch (item.Type) {
|
||||
case 1: // container
|
||||
return ctrl.state.showContainerTemplates;
|
||||
case 2: // swarm stack
|
||||
return ctrl.showSwarmStacks;
|
||||
case 3: // docker compose
|
||||
return !ctrl.showSwarmStacks || (ctrl.showSwarmStacks && ctrl.state.showContainerTemplates);
|
||||
case 4: // Edge stack templates
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
this.updateCategories = function () {
|
||||
var availableCategories = [];
|
||||
|
||||
for (var i = 0; i < ctrl.templates.length; i++) {
|
||||
var template = ctrl.templates[i];
|
||||
if (this.filterByTemplateType(template)) {
|
||||
if ((template.Type === 1 && ctrl.state.showContainerTemplates) || (template.Type === 2 && ctrl.showSwarmStacks) || (template.Type === 3 && !ctrl.showSwarmStacks)) {
|
||||
availableCategories = availableCategories.concat(template.Categories);
|
||||
}
|
||||
}
|
||||
@@ -51,6 +37,13 @@ function TemplateListController($async, $state, DatatableService, Notifications,
|
||||
return _.includes(item.Categories, ctrl.state.selectedCategory);
|
||||
};
|
||||
|
||||
this.filterByType = function (item) {
|
||||
if ((item.Type === 1 && ctrl.state.showContainerTemplates) || (item.Type === 2 && ctrl.showSwarmStacks) || (item.Type === 3 && !ctrl.showSwarmStacks)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
this.duplicateTemplate = duplicateTemplate.bind(this);
|
||||
this.duplicateTemplateAsync = duplicateTemplateAsync.bind(this);
|
||||
function duplicateTemplate(template) {
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
|
||||
<div class="blocklist">
|
||||
<template-item
|
||||
ng-repeat="template in $ctrl.templates | filter: $ctrl.filterByTemplateType | filter:$ctrl.filterByCategory | filter:$ctrl.state.textFilter"
|
||||
ng-repeat="template in $ctrl.templates | filter: $ctrl.filterByType | filter:$ctrl.filterByCategory | filter:$ctrl.state.textFilter"
|
||||
model="template"
|
||||
type-label="{{ template.Type === 1 ? 'container' : 'stack' }}"
|
||||
on-select="($ctrl.selectAction)"
|
||||
@@ -55,10 +55,7 @@
|
||||
</template-item-actions>
|
||||
</template-item>
|
||||
<div ng-if="!$ctrl.templates" class="text-center text-muted"> Loading... </div>
|
||||
<div
|
||||
ng-if="($ctrl.templates | filter: $ctrl.filterByTemplateType | filter: $ctrl.filterByCategory | filter: $ctrl.state.textFilter).length === 0"
|
||||
class="text-center text-muted"
|
||||
>
|
||||
<div ng-if="($ctrl.templates | filter: $ctrl.filterByType | filter: $ctrl.filterByCategory | filter: $ctrl.state.textFilter).length === 0" class="text-center text-muted">
|
||||
No templates available.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -23,7 +23,4 @@ export enum FeatureId {
|
||||
ACTIVITY_AUDIT = 'activity-audit',
|
||||
FORCE_REDEPLOYMENT = 'force-redeployment',
|
||||
HIDE_AUTO_UPDATE_WINDOW = 'hide-auto-update-window',
|
||||
STACK_PULL_IMAGE = 'stack-pull-image',
|
||||
STACK_WEBHOOK = 'stack-webhook',
|
||||
CONTAINER_WEBHOOK = 'container-webhook',
|
||||
}
|
||||
|
||||
@@ -27,9 +27,6 @@ export async function init(edition: Edition) {
|
||||
[FeatureId.TEAM_MEMBERSHIP]: Edition.BE,
|
||||
[FeatureId.FORCE_REDEPLOYMENT]: Edition.BE,
|
||||
[FeatureId.HIDE_AUTO_UPDATE_WINDOW]: Edition.BE,
|
||||
[FeatureId.STACK_PULL_IMAGE]: Edition.BE,
|
||||
[FeatureId.STACK_WEBHOOK]: Edition.BE,
|
||||
[FeatureId.CONTAINER_WEBHOOK]: Edition.BE,
|
||||
};
|
||||
|
||||
state.currentEdition = currentEdition;
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
export const K8S_RESOURCE_POOL_LB_QUOTA = 'k8s-resourcepool-Ibquota';
|
||||
export const K8S_RESOURCE_POOL_STORAGE_QUOTA = 'k8s-resourcepool-storagequota';
|
||||
export const RBAC_ROLES = 'rbac-roles';
|
||||
export const REGISTRY_MANAGEMENT = 'registry-management';
|
||||
export const K8S_SETUP_DEFAULT = 'k8s-setup-default';
|
||||
export const S3_BACKUP_SETTING = 's3-backup-setting';
|
||||
export const HIDE_INTERNAL_AUTHENTICATION_PROMPT = 'hide-internal-authentication-prompt';
|
||||
export const TEAM_MEMBERSHIP = 'team-membership';
|
||||
export const HIDE_INTERNAL_AUTH = 'hide-internal-auth';
|
||||
export const EXTERNAL_AUTH_LDAP = 'external-auth-ldap';
|
||||
export const ACTIVITY_AUDIT = 'activity-audit';
|
||||
export const HIDE_AUTO_UPDATE_WINDOW = 'hide-auto-update-window';
|
||||
export const FORCE_REDEPLOYMENT = 'force-redeployment';
|
||||
export const STACK_PULL_IMAGE = 'stack-pull-image';
|
||||
export const STACK_WEBHOOK = 'stack-webhook';
|
||||
export const CONTAINER_WEBHOOK = 'container-webhook';
|
||||
@@ -22,7 +22,6 @@ import {
|
||||
confirmContainerDeletion,
|
||||
confirmContainerRecreation,
|
||||
confirmServiceForceUpdate,
|
||||
confirmStackUpdate,
|
||||
confirmKubeconfigSelection,
|
||||
selectRegistry,
|
||||
} from './prompt';
|
||||
@@ -58,7 +57,6 @@ export function ModalServiceAngular() {
|
||||
confirmChangePassword,
|
||||
confirmImageExport,
|
||||
confirmServiceForceUpdate,
|
||||
confirmStackUpdate,
|
||||
selectRegistry,
|
||||
confirmContainerDeletion,
|
||||
confirmKubeconfigSelection,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import sanitize from 'sanitize-html';
|
||||
import bootbox from 'bootbox';
|
||||
import '@/portainer/components/BoxSelector/BoxSelectorItem.css';
|
||||
|
||||
import { applyBoxCSS, ButtonsOptions, confirmButtons } from './utils';
|
||||
|
||||
@@ -137,46 +136,6 @@ export function confirmServiceForceUpdate(
|
||||
customizeCheckboxPrompt(box, sanitizedMessage);
|
||||
}
|
||||
|
||||
export function confirmStackUpdate(
|
||||
message: string,
|
||||
defaultDisabled: boolean,
|
||||
defaultToggle: boolean,
|
||||
confirmButtonClassName: string | undefined,
|
||||
callback: PromptCallback
|
||||
) {
|
||||
const box = prompt({
|
||||
title: 'Are you sure?',
|
||||
inputType: 'checkbox',
|
||||
inputOptions: [
|
||||
{
|
||||
text: 'Pull latest image version<i></i>',
|
||||
value: '1',
|
||||
},
|
||||
],
|
||||
buttons: {
|
||||
confirm: {
|
||||
label: 'Update',
|
||||
className: confirmButtonClassName || 'btn-primary',
|
||||
},
|
||||
},
|
||||
callback,
|
||||
});
|
||||
box.find('.bootbox-body').prepend(message);
|
||||
const checkbox = box.find('.bootbox-input-checkbox');
|
||||
checkbox.prop('checked', defaultToggle);
|
||||
checkbox.prop('disabled', defaultDisabled);
|
||||
const checkboxDiv = box.find('.checkbox');
|
||||
checkboxDiv.removeClass('checkbox');
|
||||
checkboxDiv.prop(
|
||||
'style',
|
||||
'position: relative; display: block; margin-top: 10px; margin-bottom: 10px;'
|
||||
);
|
||||
const checkboxLabel = box.find('.form-check-label');
|
||||
checkboxLabel.addClass('switch box-selector-item limited business');
|
||||
const switchEle = checkboxLabel.find('i');
|
||||
switchEle.prop('style', 'margin-left:20px');
|
||||
}
|
||||
|
||||
export function confirmKubeconfigSelection(
|
||||
options: InputOption[],
|
||||
expiryMessage: string,
|
||||
|
||||
@@ -54,44 +54,29 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<por-switch-field
|
||||
label="'Allow self-signed certs'"
|
||||
checked="state.allowSelfSignedCerts"
|
||||
tooltip="'When allowing self-signed certificates the edge agent will ignore the domain validation when connecting to Portainer via HTTPS'"
|
||||
on-change="(onToggleAllowSelfSignedCerts)"
|
||||
></por-switch-field>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-if="!isKubernetesDeploymentTabSelected()" style="margin-bottom: 60px">
|
||||
<label for="env_vars" class="col-sm-3 col-lg-2 control-label text-left" style="padding-left: 0; padding-top: 5px">
|
||||
Environment variables
|
||||
<portainer-tooltip
|
||||
position="bottom"
|
||||
message="Comma separated list of environment variables that will be sourced from the host where the agent is deployed."
|
||||
></portainer-tooltip>
|
||||
</label>
|
||||
<div class="col-sm-9 col-lg-10">
|
||||
<input type="text" class="form-control" id="env_vars" ng-model="formValues.EnvVarSource" placeholder="foo=bar,myvar" />
|
||||
</div>
|
||||
</div>
|
||||
<por-switch-field
|
||||
label="'Allow self-signed certs'"
|
||||
checked="state.allowSelfSignedCerts"
|
||||
tooltip="'When allowing self-signed certificates the edge agent will ignore the domain validation when connecting to Portainer via HTTPS'"
|
||||
on-change="(onToggleAllowSelfSignedCerts)"
|
||||
></por-switch-field>
|
||||
|
||||
<div style="margin-top: 10px">
|
||||
<uib-tabset active="state.deploymentTab">
|
||||
<uib-tab index="'kubernetes'" heading="Kubernetes" ng-if="state.platformType === 'linux'">
|
||||
<code style="display: block; white-space: pre-wrap; padding: 16px 45px">{{
|
||||
dockerCommands[state.deploymentTab][state.platformType](agentShortVersion, endpoint.EdgeID, endpoint.EdgeKey, state.allowSelfSignedCerts)
|
||||
}}</code>
|
||||
<code style="display: block; white-space: pre-wrap; padding: 16px 45px">
|
||||
{{ dockerCommands[state.deploymentTab][state.platformType](agentVersion, agentShortVersion, endpoint.EdgeID, endpoint.EdgeKey, state.allowSelfSignedCerts) }}
|
||||
</code>
|
||||
</uib-tab>
|
||||
<uib-tab index="'swarm'" heading="Docker Swarm">
|
||||
<code style="display: block; white-space: pre-wrap; padding: 16px 45px">{{
|
||||
dockerCommands[state.deploymentTab][state.platformType](agentVersion, endpoint.EdgeID, endpoint.EdgeKey, state.allowSelfSignedCerts)
|
||||
}}</code>
|
||||
<code style="display: block; white-space: pre-wrap; padding: 16px 45px">
|
||||
{{ dockerCommands[state.deploymentTab][state.platformType](agentVersion, agentShortVersion, endpoint.EdgeID, endpoint.EdgeKey, state.allowSelfSignedCerts) }}
|
||||
</code>
|
||||
</uib-tab>
|
||||
<uib-tab index="'standalone'" heading="Docker Standalone">
|
||||
<code style="display: block; white-space: pre-wrap; padding: 16px 45px">{{
|
||||
dockerCommands[state.deploymentTab][state.platformType](agentVersion, endpoint.EdgeID, endpoint.EdgeKey, state.allowSelfSignedCerts)
|
||||
}}</code>
|
||||
<code style="display: block; white-space: pre-wrap; padding: 16px 45px">
|
||||
{{ dockerCommands[state.deploymentTab][state.platformType](agentVersion, agentShortVersion, endpoint.EdgeID, endpoint.EdgeKey, state.allowSelfSignedCerts) }}
|
||||
</code>
|
||||
</uib-tab>
|
||||
</uib-tabset>
|
||||
<div style="margin-top: 10px">
|
||||
|
||||
@@ -90,22 +90,13 @@ function EndpointController(
|
||||
|
||||
$scope.formValues = {
|
||||
SecurityFormData: new EndpointSecurityFormData(),
|
||||
EnvVarSource: '',
|
||||
};
|
||||
|
||||
$scope.isKubernetesDeploymentTabSelected = function () {
|
||||
return $scope.state.deploymentTab === DEPLOYMENT_TABS.KUBERNETES;
|
||||
};
|
||||
|
||||
$scope.copyEdgeAgentDeploymentCommand = copyEdgeAgentDeploymentCommand;
|
||||
function copyEdgeAgentDeploymentCommand() {
|
||||
let agentVersion = $scope.agentVersion;
|
||||
if ($scope.state.deploymentTab == DEPLOYMENT_TABS.KUBERNETES) {
|
||||
agentVersion = $scope.agentShortVersion;
|
||||
}
|
||||
|
||||
const command = $scope.dockerCommands[$scope.state.deploymentTab][$scope.state.platformType](
|
||||
agentVersion,
|
||||
$scope.agentVersion,
|
||||
$scope.agentShortVersion,
|
||||
$scope.endpoint.EdgeID,
|
||||
$scope.endpoint.EdgeKey,
|
||||
$scope.state.allowSelfSignedCerts
|
||||
@@ -323,120 +314,89 @@ function EndpointController(
|
||||
$scope.endpoint.ManagementInfo['DNS Suffix'] = '-';
|
||||
}
|
||||
|
||||
function buildEnvironmentSubCommand() {
|
||||
if ($scope.formValues.EnvVarSource === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $scope.formValues.EnvVarSource.split(',')
|
||||
.map(function (s) {
|
||||
if (s !== '') {
|
||||
return `-e ${s} \\`;
|
||||
}
|
||||
})
|
||||
.filter((s) => s !== undefined);
|
||||
function buildLinuxStandaloneCommand(agentVersion, agentShortVersion, edgeId, edgeKey, allowSelfSignedCerts) {
|
||||
return `
|
||||
docker run -d \\
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \\
|
||||
-v /var/lib/docker/volumes:/var/lib/docker/volumes \\
|
||||
-v /:/host \\
|
||||
-v portainer_agent_data:/data \\
|
||||
--restart always \\
|
||||
-e EDGE=1 \\
|
||||
-e EDGE_ID=${edgeId} \\
|
||||
-e EDGE_KEY=${edgeKey} \\
|
||||
-e CAP_HOST_MANAGEMENT=1 \\
|
||||
-e EDGE_INSECURE_POLL=${allowSelfSignedCerts ? 1 : 0} \\
|
||||
--name portainer_edge_agent \\
|
||||
portainer/agent:${agentVersion}`;
|
||||
}
|
||||
|
||||
function buildLinuxStandaloneCommand(agentVersion, edgeId, edgeKey, allowSelfSignedCerts) {
|
||||
const env = buildEnvironmentSubCommand();
|
||||
|
||||
return [
|
||||
'docker run -d \\',
|
||||
'-v /var/run/docker.sock:/var/run/docker.sock \\',
|
||||
'-v /var/lib/docker/volumes:/var/lib/docker/volumes \\',
|
||||
'-v /:/host \\',
|
||||
'-v portainer_agent_data:/data \\',
|
||||
'--restart always \\',
|
||||
'-e EDGE=1 \\',
|
||||
`-e EDGE_ID=${edgeId} \\`,
|
||||
`-e EDGE_KEY=${edgeKey} \\`,
|
||||
'-e CAP_HOST_MANAGEMENT=1 \\',
|
||||
`-e EDGE_INSECURE_POLL=${allowSelfSignedCerts ? 1 : 0} \\`,
|
||||
...env,
|
||||
'--name portainer_edge_agent \\',
|
||||
`portainer/agent:${agentVersion}`,
|
||||
].join('\r\n');
|
||||
function buildWindowsStandaloneCommand(agentVersion, agentShortVersion, edgeId, edgeKey, allowSelfSignedCerts) {
|
||||
return `
|
||||
docker run -d \\
|
||||
--mount type=npipe,src=\\\\.\\pipe\\docker_engine,dst=\\\\.\\pipe\\docker_engine \\
|
||||
--mount type=bind,src=C:\\ProgramData\\docker\\volumes,dst=C:\\ProgramData\\docker\\volumes \\
|
||||
--mount type=volume,src=portainer_agent_data,dst=C:\\data \\
|
||||
--restart always \\
|
||||
-e EDGE=1 \\
|
||||
-e EDGE_ID=${edgeId} \\
|
||||
-e EDGE_KEY=${edgeKey} \\
|
||||
-e CAP_HOST_MANAGEMENT=1 \\
|
||||
-e EDGE_INSECURE_POLL=${allowSelfSignedCerts ? 1 : 0} \\
|
||||
--name portainer_edge_agent \\
|
||||
portainer/agent:${agentVersion}`;
|
||||
}
|
||||
|
||||
function buildWindowsStandaloneCommand(agentVersion, edgeId, edgeKey, allowSelfSignedCerts) {
|
||||
const env = buildEnvironmentSubCommand();
|
||||
function buildLinuxSwarmCommand(agentVersion, agentShortVersion, edgeId, edgeKey, allowSelfSignedCerts) {
|
||||
return `
|
||||
docker network create \\
|
||||
--driver overlay \\
|
||||
portainer_agent_network;
|
||||
|
||||
return [
|
||||
'docker run -d \\',
|
||||
'--mount type=npipe,src=\\\\.\\pipe\\docker_engine,dst=\\\\.\\pipe\\docker_engine \\',
|
||||
'--mount type=bind,src=C:\\ProgramData\\docker\\volumes,dst=C:\\ProgramData\\docker\\volumes \\',
|
||||
'--mount type=volume,src=portainer_agent_data,dst=C:\\data \\',
|
||||
'--restart always \\',
|
||||
'-e EDGE=1 \\',
|
||||
`-e EDGE_ID=${edgeId} \\`,
|
||||
`-e EDGE_KEY=${edgeKey} \\`,
|
||||
'-e CAP_HOST_MANAGEMENT=1 \\',
|
||||
`-e EDGE_INSECURE_POLL=${allowSelfSignedCerts ? 1 : 0} \\`,
|
||||
...env,
|
||||
'--name portainer_edge_agent \\',
|
||||
`portainer/agent:${agentVersion}`,
|
||||
].join('\r\n');
|
||||
docker service create \\
|
||||
--name portainer_edge_agent \\
|
||||
--network portainer_agent_network \\
|
||||
-e AGENT_CLUSTER_ADDR=tasks.portainer_edge_agent \\
|
||||
-e EDGE=1 \\
|
||||
-e EDGE_ID=${edgeId} \\
|
||||
-e EDGE_KEY=${edgeKey} \\
|
||||
-e CAP_HOST_MANAGEMENT=1 \\
|
||||
-e EDGE_INSECURE_POLL=${allowSelfSignedCerts ? 1 : 0} \\
|
||||
--mode global \\
|
||||
--constraint 'node.platform.os == linux' \\
|
||||
--mount type=bind,src=//var/run/docker.sock,dst=/var/run/docker.sock \\
|
||||
--mount type=bind,src=//var/lib/docker/volumes,dst=/var/lib/docker/volumes \\
|
||||
--mount type=bind,src=//,dst=/host \\
|
||||
--mount type=volume,src=portainer_agent_data,dst=/data \\
|
||||
portainer/agent:${agentVersion}`;
|
||||
}
|
||||
|
||||
function buildLinuxSwarmCommand(agentVersion, edgeId, edgeKey, allowSelfSignedCerts) {
|
||||
const env = buildEnvironmentSubCommand();
|
||||
|
||||
return [
|
||||
'docker network create \\',
|
||||
'--driver overlay \\',
|
||||
'portainer_agent_network;',
|
||||
'',
|
||||
|
||||
'docker service create \\',
|
||||
'--name portainer_edge_agent \\',
|
||||
'--network portainer_agent_network \\',
|
||||
'-e AGENT_CLUSTER_ADDR=tasks.portainer_edge_agent \\',
|
||||
'-e EDGE=1 \\',
|
||||
`-e EDGE_ID=${edgeId} \\`,
|
||||
`-e EDGE_KEY=${edgeKey} \\`,
|
||||
'-e CAP_HOST_MANAGEMENT=1 \\',
|
||||
`-e EDGE_INSECURE_POLL=${allowSelfSignedCerts ? 1 : 0} \\`,
|
||||
...env,
|
||||
'--mode global \\',
|
||||
"--constraint 'node.platform.os == linux' \\",
|
||||
'--mount type=bind,src=//var/run/docker.sock,dst=/var/run/docker.sock \\',
|
||||
'--mount type=bind,src=//var/lib/docker/volumes,dst=/var/lib/docker/volumes \\',
|
||||
'--mount type=bind,src=//,dst=/host \\',
|
||||
'--mount type=volume,src=portainer_agent_data,dst=/data \\',
|
||||
`portainer/agent:${agentVersion}`,
|
||||
].join('\r\n');
|
||||
function buildWindowsSwarmCommand(agentVersion, agentShortVersion, edgeId, edgeKey, allowSelfSignedCerts) {
|
||||
return `
|
||||
docker network create \\
|
||||
--driver overlay \\
|
||||
portainer_edge_agent_network && \\
|
||||
docker service create \\
|
||||
--name portainer_edge_agent \\
|
||||
--network portainer_edge_agent_network \\
|
||||
-e AGENT_CLUSTER_ADDR=tasks.portainer_edge_agent \\
|
||||
-e EDGE=1 \\
|
||||
-e EDGE_ID=${edgeId} \\
|
||||
-e EDGE_KEY=${edgeKey} \\
|
||||
-e CAP_HOST_MANAGEMENT=1 \\
|
||||
-e EDGE_INSECURE_POLL=${allowSelfSignedCerts ? 1 : 0} \\
|
||||
--mode global \\
|
||||
--constraint node.platform.os==windows \\
|
||||
--mount type=npipe,src=\\\\.\\pipe\\docker_engine,dst=\\\\.\\pipe\\docker_engine \\
|
||||
--mount type=bind,src=C:\\ProgramData\\docker\\volumes,dst=C:\\ProgramData\\docker\\volumes \\
|
||||
--mount type=volume,src=portainer_agent_data,dst=C:\\data \\
|
||||
portainer/agent:${agentVersion}`;
|
||||
}
|
||||
|
||||
function buildWindowsSwarmCommand(agentVersion, edgeId, edgeKey, allowSelfSignedCerts) {
|
||||
const env = buildEnvironmentSubCommand();
|
||||
|
||||
return [
|
||||
'docker network create \\',
|
||||
'--driver overlay \\',
|
||||
'portainer_agent_network;',
|
||||
'',
|
||||
|
||||
'docker service create \\',
|
||||
'--name portainer_edge_agent \\',
|
||||
'--network portainer_agent_network \\',
|
||||
'-e AGENT_CLUSTER_ADDR=tasks.portainer_edge_agent \\',
|
||||
'-e EDGE=1 \\',
|
||||
`-e EDGE_ID=${edgeId} \\`,
|
||||
`-e EDGE_KEY=${edgeKey} \\`,
|
||||
'-e CAP_HOST_MANAGEMENT=1 \\',
|
||||
`-e EDGE_INSECURE_POLL=${allowSelfSignedCerts ? 1 : 0} \\`,
|
||||
...env,
|
||||
'--mode global \\',
|
||||
"--constraint 'node.platform.os == windows' \\",
|
||||
'--mount type=npipe,src=\\\\.\\pipe\\docker_engine,dst=\\\\.\\pipe\\docker_engine \\',
|
||||
'--mount type=bind,src=C:\\ProgramData\\docker\\volumes,dst=C:\\ProgramData\\docker\\volumes \\',
|
||||
'--mount type=volume,src=portainer_agent_data,dst=C:\\data \\',
|
||||
`portainer/agent:${agentVersion}`,
|
||||
].join('\r\n');
|
||||
}
|
||||
|
||||
function buildKubernetesCommand(agentVersion, edgeId, edgeKey, allowSelfSignedCerts) {
|
||||
return `curl https://downloads.portainer.io/portainer-ce${agentVersion}-edge-agent-setup.sh | bash -s -- ${edgeId} ${edgeKey} ${allowSelfSignedCerts ? '1' : '0'}`;
|
||||
function buildKubernetesCommand(agentVersion, agentShortVersion, edgeId, edgeKey, allowSelfSignedCerts) {
|
||||
return `
|
||||
curl https://downloads.portainer.io/portainer-ce${agentShortVersion}-edge-agent-setup.sh | bash -s -- ${edgeId} ${edgeKey} ${allowSelfSignedCerts ? '1' : ''}
|
||||
`;
|
||||
}
|
||||
|
||||
initView();
|
||||
|
||||
@@ -109,7 +109,7 @@
|
||||
<div class="panel-body">
|
||||
<!-- toggle -->
|
||||
<div style="padding-bottom: 12px">
|
||||
<a ng-click="togglePanel()" data-cy="init-installPortainerFromBackup">
|
||||
<a ng-click="togglePanel()">
|
||||
<i ng-class="{ true: 'glyphicon glyphicon-chevron-down', false: 'glyphicon glyphicon-chevron-right' }[state.showRestorePortainer]" aria-hidden="true"></i
|
||||
><span style="padding-left: 10px">Restore Portainer from backup</span>
|
||||
</a>
|
||||
@@ -131,7 +131,7 @@
|
||||
<div class="boxselector_wrapper">
|
||||
<div>
|
||||
<input type="radio" id="restore_file" checked="checked" />
|
||||
<label for="restore_file" style="padding-bottom: 20px" data-cy="init-selectLocalFile">
|
||||
<label for="restore_file" style="padding-bottom: 20px">
|
||||
<div class="boxselector_header">
|
||||
<i class="fa fa-upload" aria-hidden="true" style="margin-right: 2px"></i>
|
||||
Upload backup file
|
||||
@@ -168,7 +168,6 @@
|
||||
ngf-accept="'application/x-tar,application/x-gzip'"
|
||||
ng-model="formValues.BackupFile"
|
||||
auto-focus
|
||||
data-cy="init-selectBackupFileButton"
|
||||
>Select file</button
|
||||
>
|
||||
<span style="margin-left: 5px">
|
||||
@@ -188,7 +187,7 @@
|
||||
></portainer-tooltip>
|
||||
</label>
|
||||
<div class="col-sm-4">
|
||||
<input type="password" class="form-control" ng-model="formValues.Password" id="password" data-cy="init-backupPasswordInput" />
|
||||
<input type="password" class="form-control" ng-model="formValues.Password" id="password" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- !password-input -->
|
||||
@@ -211,7 +210,6 @@
|
||||
ng-disabled="!formValues.BackupFile || state.backupInProgress"
|
||||
ng-click="uploadBackup()"
|
||||
button-spinner="state.backupInProgress"
|
||||
data-cy="init-restorePortainerButton"
|
||||
>
|
||||
<span ng-hide="state.backupInProgress">Restore Portainer</span>
|
||||
<span ng-show="state.backupInProgress">Restoring Portainer...</span>
|
||||
|
||||
@@ -4,19 +4,19 @@
|
||||
</rd-header>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12" ng-if="$ctrl.settings">
|
||||
<div class="col-sm-12">
|
||||
<settings-edge-compute on-submit="($ctrl.onSubmitEdgeCompute)" settings="($ctrl.settings)"></settings-edge-compute>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12" ng-if="$ctrl.settings">
|
||||
<div class="col-sm-12">
|
||||
<settings-open-amt on-submit="($ctrl.onSubmitOpenAMT)" settings="($ctrl.settings)"></settings-open-amt>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12" ng-if="$ctrl.settings">
|
||||
<div class="col-sm-12">
|
||||
<settings-fdo on-submit="($ctrl.onSubmitFDO)" settings="($ctrl.settings)"></settings-fdo>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -310,7 +310,7 @@
|
||||
<div class="form-group">
|
||||
<label for="password_protect" class="col-sm-1 control-label text-left">Password protect</label>
|
||||
<div class="col-sm-1">
|
||||
<label class="switch" data-cy="settings-s3PasswordToggle">
|
||||
<label class="switch">
|
||||
<input type="checkbox" id="password_protect_s3" name="password_protect_s3" ng-model="formValues.passwordProtectS3" disabled /><i></i>
|
||||
</label>
|
||||
</div>
|
||||
@@ -320,7 +320,7 @@
|
||||
<div class="form-group" ng-if="formValues.passwordProtectS3">
|
||||
<label for="password" class="col-sm-1 control-label text-left">Password</label>
|
||||
<div class="col-sm-3">
|
||||
<input type="password" class="form-control" ng-model="formValues.passwordS3" id="password_S3" name="password_S3" required data-cy="settings-backups3pw" />
|
||||
<input type="password" class="form-control" ng-model="formValues.passwordS3" id="password_S3" name="password_S3" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-md-12" ng-show="backupPortainerForm.password_S3.$invalid">
|
||||
@@ -369,9 +369,7 @@
|
||||
<div class="form-group">
|
||||
<label for="password_protect" class="col-sm-1 control-label text-left">Password protect</label>
|
||||
<div class="col-sm-1">
|
||||
<label class="switch" data-cy="settings-passwordProtectLocal">
|
||||
<input type="checkbox" id="password_protect" name="password_protect" ng-model="formValues.passwordProtect" /><i></i>
|
||||
</label>
|
||||
<label class="switch"> <input type="checkbox" id="password_protect" name="password_protect" ng-model="formValues.passwordProtect" /><i></i> </label>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !Password protect -->
|
||||
@@ -380,7 +378,7 @@
|
||||
<div class="form-group" ng-if="formValues.passwordProtect">
|
||||
<label for="password" class="col-sm-1 control-label text-left">Password</label>
|
||||
<div class="col-sm-3">
|
||||
<input type="password" class="form-control" ng-model="formValues.password" id="password" name="password" required data-cy="settings-backupLocalPassword" />
|
||||
<input type="password" class="form-control" ng-model="formValues.password" id="password" name="password" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-md-12" ng-show="backupPortainerForm.password.$invalid">
|
||||
@@ -401,7 +399,6 @@
|
||||
ng-click="downloadBackup()"
|
||||
ng-disabled="backupPortainerForm.$invalid || state.backupInProgress || state.featureLimited"
|
||||
button-spinner="state.backupInProgress"
|
||||
data-cy="settings-downloadLocalBackup"
|
||||
>
|
||||
<span ng-hide="state.backupInProgress">Download backup</span>
|
||||
<span ng-show="state.backupInProgress">Downloading backup</span>
|
||||
|
||||
@@ -4,7 +4,6 @@ import uuidv4 from 'uuid/v4';
|
||||
import { AccessControlFormData } from '@/portainer/components/accessControlForm/porAccessControlFormModel';
|
||||
import { STACK_NAME_VALIDATION_REGEX } from '@/constants';
|
||||
import { RepositoryMechanismTypes } from '@/kubernetes/models/deploy';
|
||||
import { FeatureId } from 'Portainer/feature-flags/enums';
|
||||
|
||||
angular
|
||||
.module('portainer.app')
|
||||
@@ -32,9 +31,8 @@ angular
|
||||
) {
|
||||
$scope.onChangeTemplateId = onChangeTemplateId;
|
||||
$scope.buildAnalyticsProperties = buildAnalyticsProperties;
|
||||
$scope.stackWebhookFeature = FeatureId.STACK_WEBHOOK;
|
||||
|
||||
$scope.STACK_NAME_VALIDATION_REGEX = STACK_NAME_VALIDATION_REGEX;
|
||||
$scope.isAdmin = Authentication.isAdmin();
|
||||
|
||||
$scope.formValues = {
|
||||
Name: '',
|
||||
@@ -53,7 +51,6 @@ angular
|
||||
RepositoryMechanism: RepositoryMechanismTypes.INTERVAL,
|
||||
RepositoryFetchInterval: '5m',
|
||||
RepositoryWebhookURL: WebhookHelper.returnStackWebhookUrl(uuidv4()),
|
||||
ShowForcePullImage: false,
|
||||
};
|
||||
|
||||
$scope.state = {
|
||||
@@ -313,7 +310,7 @@ angular
|
||||
}
|
||||
|
||||
$scope.composeSyntaxMaxVersion = endpoint.ComposeSyntaxMaxVersion;
|
||||
$scope.formValues.ShowForcePullImage = $scope.state.StackType !== 3;
|
||||
|
||||
try {
|
||||
const containers = await ContainerService.containers(true);
|
||||
$scope.containerNames = ContainerHelper.getContainerNames(containers);
|
||||
|
||||
@@ -157,25 +157,6 @@
|
||||
</editor-description>
|
||||
</web-editor-form>
|
||||
|
||||
<div ng-if="state.Method !== 'repository' && isAdmin && applicationState.endpoint.type !== 4">
|
||||
<div class="col-sm-12 form-section-title"> Webhooks </div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<label class="control-label text-left">
|
||||
Create a Stack webhook
|
||||
<portainer-tooltip
|
||||
position="top"
|
||||
message="Create a webhook (or callback URI) to automate the update of this stack. Sending a POST request to this callback URI (without requiring any authentication) will pull the most up-to-date version of the associated image and re-deploy this stack."
|
||||
></portainer-tooltip>
|
||||
</label>
|
||||
<label class="switch box-selector-item limited business" style="margin-left: 20px">
|
||||
<input type="checkbox" ng-model="formValues.EnableWebhook" disabled="disabled" ng-checked="true" /><i></i>
|
||||
</label>
|
||||
<be-feature-indicator feature="stackWebhookFeature"></be-feature-indicator>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- environment-variables -->
|
||||
<environment-variables-panel ng-model="formValues.Env" explanation="These values will be used as substitutions in the stack file" on-change="(handleEnvVarChange)">
|
||||
</environment-variables-panel>
|
||||
|
||||
@@ -160,26 +160,6 @@
|
||||
></code-editor>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-if="isAdmin && applicationState.endpoint.type !== 4">
|
||||
<div class="col-sm-12 form-section-title"> Webhooks </div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<label class="control-label text-left">
|
||||
Create a Stack webhook
|
||||
<portainer-tooltip
|
||||
position="top"
|
||||
message="Create a webhook (or callback URI) to automate the update of this stack. Sending a POST request to this callback URI (without requiring any authentication) will pull the most up-to-date version of the associated image and re-deploy this stack."
|
||||
></portainer-tooltip>
|
||||
</label>
|
||||
<label class="switch box-selector-item limited business" style="margin-left: 20px">
|
||||
<input type="checkbox" ng-model="formValues.EnableWebhook" disabled="disabled" ng-checked="true" /><i></i>
|
||||
</label>
|
||||
<be-feature-indicator feature="stackWebhookFeature"></be-feature-indicator>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- environment-variables -->
|
||||
<div ng-if="stack">
|
||||
<environment-variables-panel
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { AccessControlFormData } from 'Portainer/components/accessControlForm/porAccessControlFormModel';
|
||||
import { FeatureId } from 'Portainer/feature-flags/enums';
|
||||
|
||||
angular.module('portainer.app').controller('StackController', [
|
||||
'$async',
|
||||
'$compile',
|
||||
'$q',
|
||||
'$scope',
|
||||
'$state',
|
||||
@@ -29,7 +27,6 @@ angular.module('portainer.app').controller('StackController', [
|
||||
'endpoint',
|
||||
function (
|
||||
$async,
|
||||
$compile,
|
||||
$q,
|
||||
$scope,
|
||||
$state,
|
||||
@@ -55,9 +52,6 @@ angular.module('portainer.app').controller('StackController', [
|
||||
endpoint
|
||||
) {
|
||||
$scope.endpoint = endpoint;
|
||||
$scope.isAdmin = Authentication.isAdmin();
|
||||
$scope.stackWebhookFeature = FeatureId.STACK_WEBHOOK;
|
||||
$scope.stackPullImageFeature = FeatureId.STACK_PULL_IMAGE;
|
||||
$scope.state = {
|
||||
actionInProgress: false,
|
||||
migrationInProgress: false,
|
||||
@@ -222,43 +216,32 @@ angular.module('portainer.app').controller('StackController', [
|
||||
};
|
||||
|
||||
$scope.deployStack = function () {
|
||||
const stack = $scope.stack;
|
||||
const tplCrop =
|
||||
'<div>Do you want to force an update of the stack?</div>' +
|
||||
'<div style="position: absolute; right: 110px; top: 48px; z-index: 999"><be-feature-indicator feature="stackPullImageFeature"></be-feature-indicator></div>';
|
||||
const template = angular.element(tplCrop);
|
||||
const html = $compile(template)($scope);
|
||||
// 'Do you want to force an update of the stack?'
|
||||
ModalService.confirmStackUpdate(html, true, true, null, function (result) {
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
var stackFile = $scope.stackFileContent;
|
||||
var env = FormHelper.removeInvalidEnvVars($scope.formValues.Env);
|
||||
var prune = $scope.formValues.Prune;
|
||||
var stackFile = $scope.stackFileContent;
|
||||
var env = FormHelper.removeInvalidEnvVars($scope.formValues.Env);
|
||||
var prune = $scope.formValues.Prune;
|
||||
var stack = $scope.stack;
|
||||
|
||||
// TODO: this is a work-around for stacks created with Portainer version >= 1.17.1
|
||||
// The EndpointID property is not available for these stacks, we can pass
|
||||
// the current endpoint identifier as a part of the update request. It will be used if
|
||||
// the EndpointID property is not defined on the stack.
|
||||
if (stack.EndpointId === 0) {
|
||||
stack.EndpointId = endpoint.Id;
|
||||
}
|
||||
// TODO: this is a work-around for stacks created with Portainer version >= 1.17.1
|
||||
// The EndpointID property is not available for these stacks, we can pass
|
||||
// the current endpoint identifier as a part of the update request. It will be used if
|
||||
// the EndpointID property is not defined on the stack.
|
||||
if (stack.EndpointId === 0) {
|
||||
stack.EndpointId = endpoint.Id;
|
||||
}
|
||||
|
||||
$scope.state.actionInProgress = true;
|
||||
StackService.updateStack(stack, stackFile, env, prune)
|
||||
.then(function success() {
|
||||
Notifications.success('Stack successfully deployed');
|
||||
$scope.state.isEditorDirty = false;
|
||||
$state.reload();
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to create stack');
|
||||
})
|
||||
.finally(function final() {
|
||||
$scope.state.actionInProgress = false;
|
||||
});
|
||||
});
|
||||
$scope.state.actionInProgress = true;
|
||||
StackService.updateStack(stack, stackFile, env, prune)
|
||||
.then(function success() {
|
||||
Notifications.success('Stack successfully deployed');
|
||||
$scope.state.isEditorDirty = false;
|
||||
$state.reload();
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to create stack');
|
||||
})
|
||||
.finally(function final() {
|
||||
$scope.state.actionInProgress = false;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.editorUpdate = function (cm) {
|
||||
|
||||
@@ -259,7 +259,7 @@ angular.module('portainer.app').controller('TemplatesController', [
|
||||
deployable = endpoint.mode.provider === DOCKER_SWARM_MODE;
|
||||
break;
|
||||
case 3:
|
||||
deployable = endpoint.mode.provider === DOCKER_SWARM_MODE || endpoint.mode.provider === DOCKER_STANDALONE;
|
||||
deployable = endpoint.mode.provider === DOCKER_STANDALONE;
|
||||
break;
|
||||
}
|
||||
return deployable;
|
||||
|
||||
50
build/mtlscerts.sh
Executable file
50
build/mtlscerts.sh
Executable file
@@ -0,0 +1,50 @@
|
||||
#!/bin/bash
|
||||
|
||||
# very much just copied from https://lemariva.com/blog/2019/12/portainer-managing-docker-engine-remotely
|
||||
# production use should involve a real external certificate management system
|
||||
|
||||
export HOST=portainer.p1.alho.st
|
||||
export CERTDIR=~/.config/portainer/certs/
|
||||
|
||||
mkdir -p ${CERTDIR}
|
||||
cd ${CERTDIR}
|
||||
echo "Generating example mTLS certs into $(pwd)"
|
||||
|
||||
if [[ ! -f "ca.pem" ]]; then
|
||||
echo "Generate the CA Cert"
|
||||
openssl genrsa -aes256 -out ca-key.pem 4096
|
||||
# enter a pass phrase to protect the ca-key
|
||||
|
||||
openssl req -new -x509 -days 365 -key ca-key.pem -sha256 -out ca.pem
|
||||
else
|
||||
echo "ca.pem ca cert already exists"
|
||||
fi
|
||||
|
||||
if [[ ! -f "server-cert.pem" ]]; then
|
||||
echo "Generate the Portainer server cert"
|
||||
openssl genrsa -out server-key.pem 4096
|
||||
|
||||
openssl req -subj "/CN=$HOST" -sha256 -new -key server-key.pem -out server.csr
|
||||
echo subjectAltName = DNS:$HOST,IP:10.0.0.200,IP:127.0.0.1,IP:10.10.10.189 >> extfile.cnf
|
||||
echo extendedKeyUsage = serverAuth >> extfile.cnf
|
||||
|
||||
openssl x509 -req -days 365 -sha256 -in server.csr -CA ca.pem -CAkey ca-key.pem \
|
||||
-CAcreateserial -out server-cert.pem -extfile extfile.cnf
|
||||
else
|
||||
echo "server-cert.pem ca cert already exists"
|
||||
fi
|
||||
|
||||
if [[ ! -f "agent-cert.pem" ]]; then
|
||||
echo "Generate an Agent cert"
|
||||
openssl genrsa -out agent-key.pem 4096
|
||||
|
||||
openssl req -subj '/CN=client' -new -key agent-key.pem -out agent-client.csr
|
||||
echo extendedKeyUsage = clientAuth > agent-extfile.cnf
|
||||
|
||||
openssl x509 -req -days 365 -sha256 -in agent-client.csr -CA ca.pem -CAkey ca-key.pem \
|
||||
-CAcreateserial -out agent-cert.pem -extfile agent-extfile.cnf
|
||||
else
|
||||
echo "agent-cert.pem ca cert already exists"
|
||||
fi
|
||||
|
||||
echo "done: Generated example mTLS certs into $(pwd)"
|
||||
@@ -159,6 +159,7 @@ function shell_run_container() {
|
||||
const portainerData = '${PORTAINER_DATA:-/tmp/portainer}';
|
||||
const portainerRoot = process.env.PORTAINER_PROJECT ? process.env.PORTAINER_PROJECT : process.env.PWD;
|
||||
const portainerFlags = '${PORTAINER_FLAGS:-}';
|
||||
const portainerDockerFlags = '${PORTAINER_DOCKER_FLAGS:-}';
|
||||
|
||||
return `
|
||||
docker rm -f portainer
|
||||
@@ -166,6 +167,7 @@ function shell_run_container() {
|
||||
-p 8000:8000 \
|
||||
-p 9000:9000 \
|
||||
-p 9443:9443 \
|
||||
${portainerDockerFlags} \
|
||||
-v ${portainerRoot}/dist:/app \
|
||||
-v ${portainerData}:/data \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock:z \
|
||||
|
||||
@@ -12599,7 +12599,7 @@ locate-path@^6.0.0:
|
||||
|
||||
lodash-es@^4.17.15, lodash-es@^4.17.21:
|
||||
version "4.17.21"
|
||||
resolved "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee"
|
||||
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee"
|
||||
integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==
|
||||
|
||||
lodash-webpack-plugin@^0.11.6:
|
||||
|
||||
Reference in New Issue
Block a user