mirror of https://github.com/portainer/portainer
feat(server): remove external endpoint feature (#3837)
* fix(prettier): auto format html files (#3836) * refactor(main): remove reference to external endpoints * refactor(cli): remove parsing of external endpoints param * refactor(portainer): remove types for external endpoints * refactor(endpoints): remove warning for external endpoints * refactor(endpoints): remove endpoint management setting * refactor(endpoints): remove ref to endpoint management * fix(main): remove endpoint management2.0
parent
d9665bc939
commit
c074a714cf
|
@ -20,9 +20,7 @@ const (
|
||||||
errInvalidEndpointProtocol = portainer.Error("Invalid endpoint protocol: Portainer only supports unix://, npipe:// or tcp://")
|
errInvalidEndpointProtocol = portainer.Error("Invalid endpoint protocol: Portainer only supports unix://, npipe:// or tcp://")
|
||||||
errSocketOrNamedPipeNotFound = portainer.Error("Unable to locate Unix socket or named pipe")
|
errSocketOrNamedPipeNotFound = portainer.Error("Unable to locate Unix socket or named pipe")
|
||||||
errEndpointsFileNotFound = portainer.Error("Unable to locate external endpoints file")
|
errEndpointsFileNotFound = portainer.Error("Unable to locate external endpoints file")
|
||||||
errInvalidSyncInterval = portainer.Error("Invalid synchronization interval")
|
|
||||||
errInvalidSnapshotInterval = portainer.Error("Invalid snapshot interval")
|
errInvalidSnapshotInterval = portainer.Error("Invalid snapshot interval")
|
||||||
errEndpointExcludeExternal = portainer.Error("Cannot use the -H flag mutually with --external-endpoints")
|
|
||||||
errNoAuthExcludeAdminPassword = portainer.Error("Cannot use --no-auth with --admin-password or --admin-password-file")
|
errNoAuthExcludeAdminPassword = portainer.Error("Cannot use --no-auth with --admin-password or --admin-password-file")
|
||||||
errAdminPassExcludeAdminPassFile = portainer.Error("Cannot use --admin-password with --admin-password-file")
|
errAdminPassExcludeAdminPassFile = portainer.Error("Cannot use --admin-password with --admin-password-file")
|
||||||
)
|
)
|
||||||
|
@ -38,7 +36,6 @@ func (*Service) ParseFlags(version string) (*portainer.CLIFlags, error) {
|
||||||
Assets: kingpin.Flag("assets", "Path to the assets").Default(defaultAssetsDirectory).Short('a').String(),
|
Assets: kingpin.Flag("assets", "Path to the assets").Default(defaultAssetsDirectory).Short('a').String(),
|
||||||
Data: kingpin.Flag("data", "Path to the folder where the data is stored").Default(defaultDataDirectory).Short('d').String(),
|
Data: kingpin.Flag("data", "Path to the folder where the data is stored").Default(defaultDataDirectory).Short('d').String(),
|
||||||
EndpointURL: kingpin.Flag("host", "Endpoint URL").Short('H').String(),
|
EndpointURL: kingpin.Flag("host", "Endpoint URL").Short('H').String(),
|
||||||
ExternalEndpoints: kingpin.Flag("external-endpoints", "Path to a file defining available endpoints (deprecated)").String(),
|
|
||||||
NoAuth: kingpin.Flag("no-auth", "Disable authentication (deprecated)").Default(defaultNoAuth).Bool(),
|
NoAuth: kingpin.Flag("no-auth", "Disable authentication (deprecated)").Default(defaultNoAuth).Bool(),
|
||||||
NoAnalytics: kingpin.Flag("no-analytics", "Disable Analytics in app").Default(defaultNoAnalytics).Bool(),
|
NoAnalytics: kingpin.Flag("no-analytics", "Disable Analytics in app").Default(defaultNoAnalytics).Bool(),
|
||||||
TLS: kingpin.Flag("tlsverify", "TLS support").Default(defaultTLS).Bool(),
|
TLS: kingpin.Flag("tlsverify", "TLS support").Default(defaultTLS).Bool(),
|
||||||
|
@ -49,7 +46,6 @@ func (*Service) ParseFlags(version string) (*portainer.CLIFlags, error) {
|
||||||
SSL: kingpin.Flag("ssl", "Secure Portainer instance using SSL").Default(defaultSSL).Bool(),
|
SSL: kingpin.Flag("ssl", "Secure Portainer instance using SSL").Default(defaultSSL).Bool(),
|
||||||
SSLCert: kingpin.Flag("sslcert", "Path to the SSL certificate used to secure the Portainer instance").Default(defaultSSLCertPath).String(),
|
SSLCert: kingpin.Flag("sslcert", "Path to the SSL certificate used to secure the Portainer instance").Default(defaultSSLCertPath).String(),
|
||||||
SSLKey: kingpin.Flag("sslkey", "Path to the SSL key used to secure the Portainer instance").Default(defaultSSLKeyPath).String(),
|
SSLKey: kingpin.Flag("sslkey", "Path to the SSL key used to secure the Portainer instance").Default(defaultSSLKeyPath).String(),
|
||||||
SyncInterval: kingpin.Flag("sync-interval", "Duration between each synchronization via the external endpoints source (deprecated)").Default(defaultSyncInterval).String(),
|
|
||||||
SnapshotInterval: kingpin.Flag("snapshot-interval", "Duration between each endpoint snapshot job").Default(defaultSnapshotInterval).String(),
|
SnapshotInterval: kingpin.Flag("snapshot-interval", "Duration between each endpoint snapshot job").Default(defaultSnapshotInterval).String(),
|
||||||
AdminPassword: kingpin.Flag("admin-password", "Hashed admin password").String(),
|
AdminPassword: kingpin.Flag("admin-password", "Hashed admin password").String(),
|
||||||
AdminPasswordFile: kingpin.Flag("admin-password-file", "Path to the file containing the password for the admin user").String(),
|
AdminPasswordFile: kingpin.Flag("admin-password-file", "Path to the file containing the password for the admin user").String(),
|
||||||
|
@ -76,25 +72,11 @@ func (*Service) ValidateFlags(flags *portainer.CLIFlags) error {
|
||||||
|
|
||||||
displayDeprecationWarnings(flags)
|
displayDeprecationWarnings(flags)
|
||||||
|
|
||||||
if *flags.EndpointURL != "" && *flags.ExternalEndpoints != "" {
|
|
||||||
return errEndpointExcludeExternal
|
|
||||||
}
|
|
||||||
|
|
||||||
err := validateEndpointURL(*flags.EndpointURL)
|
err := validateEndpointURL(*flags.EndpointURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = validateExternalEndpoints(*flags.ExternalEndpoints)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = validateSyncInterval(*flags.SyncInterval)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = validateSnapshotInterval(*flags.SnapshotInterval)
|
err = validateSnapshotInterval(*flags.SnapshotInterval)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -112,14 +94,6 @@ func (*Service) ValidateFlags(flags *portainer.CLIFlags) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func displayDeprecationWarnings(flags *portainer.CLIFlags) {
|
func displayDeprecationWarnings(flags *portainer.CLIFlags) {
|
||||||
if *flags.ExternalEndpoints != "" {
|
|
||||||
log.Println("Warning: the --external-endpoint flag is deprecated and will likely be removed in a future version of Portainer.")
|
|
||||||
}
|
|
||||||
|
|
||||||
if *flags.SyncInterval != defaultSyncInterval {
|
|
||||||
log.Println("Warning: the --sync-interval flag is deprecated and will likely be removed in a future version of Portainer.")
|
|
||||||
}
|
|
||||||
|
|
||||||
if *flags.NoAuth {
|
if *flags.NoAuth {
|
||||||
log.Println("Warning: the --no-auth flag is deprecated and will likely be removed in a future version of Portainer.")
|
log.Println("Warning: the --no-auth flag is deprecated and will likely be removed in a future version of Portainer.")
|
||||||
}
|
}
|
||||||
|
@ -145,28 +119,6 @@ func validateEndpointURL(endpointURL string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateExternalEndpoints(externalEndpoints string) error {
|
|
||||||
if externalEndpoints != "" {
|
|
||||||
if _, err := os.Stat(externalEndpoints); err != nil {
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
return errEndpointsFileNotFound
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateSyncInterval(syncInterval string) error {
|
|
||||||
if syncInterval != defaultSyncInterval {
|
|
||||||
_, err := time.ParseDuration(syncInterval)
|
|
||||||
if err != nil {
|
|
||||||
return errInvalidSyncInterval
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateSnapshotInterval(snapshotInterval string) error {
|
func validateSnapshotInterval(snapshotInterval string) error {
|
||||||
if snapshotInterval != defaultSnapshotInterval {
|
if snapshotInterval != defaultSnapshotInterval {
|
||||||
_, err := time.ParseDuration(snapshotInterval)
|
_, err := time.ParseDuration(snapshotInterval)
|
||||||
|
|
|
@ -18,6 +18,5 @@ const (
|
||||||
defaultSSL = "false"
|
defaultSSL = "false"
|
||||||
defaultSSLCertPath = "/certs/portainer.crt"
|
defaultSSLCertPath = "/certs/portainer.crt"
|
||||||
defaultSSLKeyPath = "/certs/portainer.key"
|
defaultSSLKeyPath = "/certs/portainer.key"
|
||||||
defaultSyncInterval = "60s"
|
|
||||||
defaultSnapshotInterval = "5m"
|
defaultSnapshotInterval = "5m"
|
||||||
)
|
)
|
||||||
|
|
|
@ -16,6 +16,5 @@ const (
|
||||||
defaultSSL = "false"
|
defaultSSL = "false"
|
||||||
defaultSSLCertPath = "C:\\certs\\portainer.crt"
|
defaultSSLCertPath = "C:\\certs\\portainer.crt"
|
||||||
defaultSSLKeyPath = "C:\\certs\\portainer.key"
|
defaultSSLKeyPath = "C:\\certs\\portainer.key"
|
||||||
defaultSyncInterval = "60s"
|
|
||||||
defaultSnapshotInterval = "5m"
|
defaultSnapshotInterval = "5m"
|
||||||
)
|
)
|
||||||
|
|
|
@ -157,45 +157,6 @@ func loadSnapshotSystemSchedule(jobScheduler portainer.JobScheduler, snapshotter
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadEndpointSyncSystemSchedule(jobScheduler portainer.JobScheduler, scheduleService portainer.ScheduleService, endpointService portainer.EndpointService, flags *portainer.CLIFlags) error {
|
|
||||||
if *flags.ExternalEndpoints == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Println("Using external endpoint definition. Endpoint management via the API will be disabled.")
|
|
||||||
|
|
||||||
schedules, err := scheduleService.SchedulesByJobType(portainer.EndpointSyncJobType)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(schedules) != 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
endpointSyncJob := &portainer.EndpointSyncJob{}
|
|
||||||
|
|
||||||
endpointSyncSchedule := &portainer.Schedule{
|
|
||||||
ID: portainer.ScheduleID(scheduleService.GetNextIdentifier()),
|
|
||||||
Name: "system_endpointsync",
|
|
||||||
CronExpression: "@every " + *flags.SyncInterval,
|
|
||||||
Recurring: true,
|
|
||||||
JobType: portainer.EndpointSyncJobType,
|
|
||||||
EndpointSyncJob: endpointSyncJob,
|
|
||||||
Created: time.Now().Unix(),
|
|
||||||
}
|
|
||||||
|
|
||||||
endpointSyncJobContext := cron.NewEndpointSyncJobContext(endpointService, *flags.ExternalEndpoints)
|
|
||||||
endpointSyncJobRunner := cron.NewEndpointSyncJobRunner(endpointSyncSchedule, endpointSyncJobContext)
|
|
||||||
|
|
||||||
err = jobScheduler.ScheduleJob(endpointSyncJobRunner)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return scheduleService.CreateSchedule(endpointSyncSchedule)
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadSchedulesFromDatabase(jobScheduler portainer.JobScheduler, jobService portainer.JobService, scheduleService portainer.ScheduleService, endpointService portainer.EndpointService, fileService portainer.FileService, reverseTunnelService portainer.ReverseTunnelService) error {
|
func loadSchedulesFromDatabase(jobScheduler portainer.JobScheduler, jobService portainer.JobService, scheduleService portainer.ScheduleService, endpointService portainer.EndpointService, fileService portainer.FileService, reverseTunnelService portainer.ReverseTunnelService) error {
|
||||||
schedules, err := scheduleService.Schedules()
|
schedules, err := scheduleService.Schedules()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -225,12 +186,11 @@ func loadSchedulesFromDatabase(jobScheduler portainer.JobScheduler, jobService p
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func initStatus(endpointManagement bool, flags *portainer.CLIFlags) *portainer.Status {
|
func initStatus(flags *portainer.CLIFlags) *portainer.Status {
|
||||||
return &portainer.Status{
|
return &portainer.Status{
|
||||||
Analytics: !*flags.NoAnalytics,
|
Analytics: !*flags.NoAnalytics,
|
||||||
Authentication: !*flags.NoAuth,
|
Authentication: !*flags.NoAuth,
|
||||||
EndpointManagement: endpointManagement,
|
Version: portainer.APIVersion,
|
||||||
Version: portainer.APIVersion,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -460,11 +420,6 @@ func main() {
|
||||||
|
|
||||||
snapshotter := initSnapshotter(clientFactory)
|
snapshotter := initSnapshotter(clientFactory)
|
||||||
|
|
||||||
endpointManagement := true
|
|
||||||
if *flags.ExternalEndpoints != "" {
|
|
||||||
endpointManagement = false
|
|
||||||
}
|
|
||||||
|
|
||||||
swarmStackManager, err := initSwarmStackManager(*flags.Assets, *flags.Data, digitalSignatureService, fileService, reverseTunnelService)
|
swarmStackManager, err := initSwarmStackManager(*flags.Assets, *flags.Data, digitalSignatureService, fileService, reverseTunnelService)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
@ -486,11 +441,6 @@ func main() {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = loadEndpointSyncSystemSchedule(jobScheduler, store.ScheduleService, store.EndpointService, flags)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = loadSnapshotSystemSchedule(jobScheduler, snapshotter, store.ScheduleService, store.EndpointService, store.SettingsService)
|
err = loadSnapshotSystemSchedule(jobScheduler, snapshotter, store.ScheduleService, store.EndpointService, store.SettingsService)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
@ -498,7 +448,7 @@ func main() {
|
||||||
|
|
||||||
jobScheduler.Start()
|
jobScheduler.Start()
|
||||||
|
|
||||||
applicationStatus := initStatus(endpointManagement, flags)
|
applicationStatus := initStatus(flags)
|
||||||
|
|
||||||
err = initEndpoint(flags, store.EndpointService, snapshotter)
|
err = initEndpoint(flags, store.EndpointService, snapshotter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -557,7 +507,6 @@ func main() {
|
||||||
BindAddress: *flags.Addr,
|
BindAddress: *flags.Addr,
|
||||||
AssetsPath: *flags.Assets,
|
AssetsPath: *flags.Assets,
|
||||||
AuthDisabled: *flags.NoAuth,
|
AuthDisabled: *flags.NoAuth,
|
||||||
EndpointManagement: endpointManagement,
|
|
||||||
RoleService: store.RoleService,
|
RoleService: store.RoleService,
|
||||||
UserService: store.UserService,
|
UserService: store.UserService,
|
||||||
TeamService: store.TeamService,
|
TeamService: store.TeamService,
|
||||||
|
|
|
@ -1,214 +0,0 @@
|
||||||
package cron
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/portainer/portainer/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
// EndpointSyncJobRunner is used to run a EndpointSyncJob
|
|
||||||
type EndpointSyncJobRunner struct {
|
|
||||||
schedule *portainer.Schedule
|
|
||||||
context *EndpointSyncJobContext
|
|
||||||
}
|
|
||||||
|
|
||||||
// EndpointSyncJobContext represents the context of execution of a EndpointSyncJob
|
|
||||||
type EndpointSyncJobContext struct {
|
|
||||||
endpointService portainer.EndpointService
|
|
||||||
endpointFilePath string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewEndpointSyncJobContext returns a new context that can be used to execute a EndpointSyncJob
|
|
||||||
func NewEndpointSyncJobContext(endpointService portainer.EndpointService, endpointFilePath string) *EndpointSyncJobContext {
|
|
||||||
return &EndpointSyncJobContext{
|
|
||||||
endpointService: endpointService,
|
|
||||||
endpointFilePath: endpointFilePath,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewEndpointSyncJobRunner returns a new runner that can be scheduled
|
|
||||||
func NewEndpointSyncJobRunner(schedule *portainer.Schedule, context *EndpointSyncJobContext) *EndpointSyncJobRunner {
|
|
||||||
return &EndpointSyncJobRunner{
|
|
||||||
schedule: schedule,
|
|
||||||
context: context,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type synchronization struct {
|
|
||||||
endpointsToCreate []*portainer.Endpoint
|
|
||||||
endpointsToUpdate []*portainer.Endpoint
|
|
||||||
endpointsToDelete []*portainer.Endpoint
|
|
||||||
}
|
|
||||||
|
|
||||||
type fileEndpoint struct {
|
|
||||||
Name string `json:"Name"`
|
|
||||||
URL string `json:"URL"`
|
|
||||||
TLS bool `json:"TLS,omitempty"`
|
|
||||||
TLSSkipVerify bool `json:"TLSSkipVerify,omitempty"`
|
|
||||||
TLSCACert string `json:"TLSCACert,omitempty"`
|
|
||||||
TLSCert string `json:"TLSCert,omitempty"`
|
|
||||||
TLSKey string `json:"TLSKey,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSchedule returns the schedule associated to the runner
|
|
||||||
func (runner *EndpointSyncJobRunner) GetSchedule() *portainer.Schedule {
|
|
||||||
return runner.schedule
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run triggers the execution of the endpoint synchronization process.
|
|
||||||
func (runner *EndpointSyncJobRunner) Run() {
|
|
||||||
data, err := ioutil.ReadFile(runner.context.endpointFilePath)
|
|
||||||
if endpointSyncError(err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var fileEndpoints []fileEndpoint
|
|
||||||
err = json.Unmarshal(data, &fileEndpoints)
|
|
||||||
if endpointSyncError(err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(fileEndpoints) == 0 {
|
|
||||||
log.Println("background job error (endpoint synchronization). External endpoint source is empty")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
storedEndpoints, err := runner.context.endpointService.Endpoints()
|
|
||||||
if endpointSyncError(err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
convertedFileEndpoints := convertFileEndpoints(fileEndpoints)
|
|
||||||
|
|
||||||
sync := prepareSyncData(storedEndpoints, convertedFileEndpoints)
|
|
||||||
if sync.requireSync() {
|
|
||||||
err = runner.context.endpointService.Synchronize(sync.endpointsToCreate, sync.endpointsToUpdate, sync.endpointsToDelete)
|
|
||||||
if endpointSyncError(err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
log.Printf("Endpoint synchronization ended. [created: %v] [updated: %v] [deleted: %v]", len(sync.endpointsToCreate), len(sync.endpointsToUpdate), len(sync.endpointsToDelete))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func endpointSyncError(err error) bool {
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("background job error (endpoint synchronization). Unable to synchronize endpoints (err=%s)\n", err)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func isValidEndpoint(endpoint *portainer.Endpoint) bool {
|
|
||||||
if endpoint.Name != "" && endpoint.URL != "" {
|
|
||||||
if !strings.HasPrefix(endpoint.URL, "unix://") && !strings.HasPrefix(endpoint.URL, "tcp://") {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func convertFileEndpoints(fileEndpoints []fileEndpoint) []portainer.Endpoint {
|
|
||||||
convertedEndpoints := make([]portainer.Endpoint, 0)
|
|
||||||
|
|
||||||
for _, e := range fileEndpoints {
|
|
||||||
endpoint := portainer.Endpoint{
|
|
||||||
Name: e.Name,
|
|
||||||
URL: e.URL,
|
|
||||||
TLSConfig: portainer.TLSConfiguration{},
|
|
||||||
}
|
|
||||||
if e.TLS {
|
|
||||||
endpoint.TLSConfig.TLS = true
|
|
||||||
endpoint.TLSConfig.TLSSkipVerify = e.TLSSkipVerify
|
|
||||||
endpoint.TLSConfig.TLSCACertPath = e.TLSCACert
|
|
||||||
endpoint.TLSConfig.TLSCertPath = e.TLSCert
|
|
||||||
endpoint.TLSConfig.TLSKeyPath = e.TLSKey
|
|
||||||
}
|
|
||||||
convertedEndpoints = append(convertedEndpoints, endpoint)
|
|
||||||
}
|
|
||||||
|
|
||||||
return convertedEndpoints
|
|
||||||
}
|
|
||||||
|
|
||||||
func endpointExists(endpoint *portainer.Endpoint, endpoints []portainer.Endpoint) int {
|
|
||||||
for idx, v := range endpoints {
|
|
||||||
if endpoint.Name == v.Name && isValidEndpoint(&v) {
|
|
||||||
return idx
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
func mergeEndpointIfRequired(original, updated *portainer.Endpoint) *portainer.Endpoint {
|
|
||||||
var endpoint *portainer.Endpoint
|
|
||||||
if original.URL != updated.URL || original.TLSConfig.TLS != updated.TLSConfig.TLS ||
|
|
||||||
(updated.TLSConfig.TLS && original.TLSConfig.TLSSkipVerify != updated.TLSConfig.TLSSkipVerify) ||
|
|
||||||
(updated.TLSConfig.TLS && original.TLSConfig.TLSCACertPath != updated.TLSConfig.TLSCACertPath) ||
|
|
||||||
(updated.TLSConfig.TLS && original.TLSConfig.TLSCertPath != updated.TLSConfig.TLSCertPath) ||
|
|
||||||
(updated.TLSConfig.TLS && original.TLSConfig.TLSKeyPath != updated.TLSConfig.TLSKeyPath) {
|
|
||||||
endpoint = original
|
|
||||||
endpoint.URL = updated.URL
|
|
||||||
if updated.TLSConfig.TLS {
|
|
||||||
endpoint.TLSConfig.TLS = true
|
|
||||||
endpoint.TLSConfig.TLSSkipVerify = updated.TLSConfig.TLSSkipVerify
|
|
||||||
endpoint.TLSConfig.TLSCACertPath = updated.TLSConfig.TLSCACertPath
|
|
||||||
endpoint.TLSConfig.TLSCertPath = updated.TLSConfig.TLSCertPath
|
|
||||||
endpoint.TLSConfig.TLSKeyPath = updated.TLSConfig.TLSKeyPath
|
|
||||||
} else {
|
|
||||||
endpoint.TLSConfig.TLS = false
|
|
||||||
endpoint.TLSConfig.TLSSkipVerify = false
|
|
||||||
endpoint.TLSConfig.TLSCACertPath = ""
|
|
||||||
endpoint.TLSConfig.TLSCertPath = ""
|
|
||||||
endpoint.TLSConfig.TLSKeyPath = ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return endpoint
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sync synchronization) requireSync() bool {
|
|
||||||
if len(sync.endpointsToCreate) != 0 || len(sync.endpointsToUpdate) != 0 || len(sync.endpointsToDelete) != 0 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func prepareSyncData(storedEndpoints, fileEndpoints []portainer.Endpoint) *synchronization {
|
|
||||||
endpointsToCreate := make([]*portainer.Endpoint, 0)
|
|
||||||
endpointsToUpdate := make([]*portainer.Endpoint, 0)
|
|
||||||
endpointsToDelete := make([]*portainer.Endpoint, 0)
|
|
||||||
|
|
||||||
for idx := range storedEndpoints {
|
|
||||||
fidx := endpointExists(&storedEndpoints[idx], fileEndpoints)
|
|
||||||
if fidx != -1 {
|
|
||||||
endpoint := mergeEndpointIfRequired(&storedEndpoints[idx], &fileEndpoints[fidx])
|
|
||||||
if endpoint != nil {
|
|
||||||
log.Printf("New definition for a stored endpoint found in file, updating database. [name: %v] [url: %v]\n", endpoint.Name, endpoint.URL)
|
|
||||||
endpointsToUpdate = append(endpointsToUpdate, endpoint)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.Printf("Stored endpoint not found in file (definition might be invalid), removing from database. [name: %v] [url: %v]", storedEndpoints[idx].Name, storedEndpoints[idx].URL)
|
|
||||||
endpointsToDelete = append(endpointsToDelete, &storedEndpoints[idx])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for idx, endpoint := range fileEndpoints {
|
|
||||||
if !isValidEndpoint(&endpoint) {
|
|
||||||
log.Printf("Invalid file endpoint definition, skipping. [name: %v] [url: %v]", endpoint.Name, endpoint.URL)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
sidx := endpointExists(&fileEndpoints[idx], storedEndpoints)
|
|
||||||
if sidx == -1 {
|
|
||||||
log.Printf("File endpoint not found in database, adding to database. [name: %v] [url: %v]", fileEndpoints[idx].Name, fileEndpoints[idx].URL)
|
|
||||||
endpointsToCreate = append(endpointsToCreate, &fileEndpoints[idx])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &synchronization{
|
|
||||||
endpointsToCreate: endpointsToCreate,
|
|
||||||
endpointsToUpdate: endpointsToUpdate,
|
|
||||||
endpointsToDelete: endpointsToDelete,
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -131,10 +131,6 @@ func (payload *endpointCreatePayload) Validate(r *http.Request) error {
|
||||||
|
|
||||||
// POST request on /api/endpoints
|
// POST request on /api/endpoints
|
||||||
func (handler *Handler) endpointCreate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
func (handler *Handler) endpointCreate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||||
if !handler.authorizeEndpointManagement {
|
|
||||||
return &httperror.HandlerError{http.StatusServiceUnavailable, "Endpoint management is disabled", ErrEndpointManagementDisabled}
|
|
||||||
}
|
|
||||||
|
|
||||||
payload := &endpointCreatePayload{}
|
payload := &endpointCreatePayload{}
|
||||||
err := payload.Validate(r)
|
err := payload.Validate(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -12,10 +12,6 @@ import (
|
||||||
|
|
||||||
// DELETE request on /api/endpoints/:id
|
// DELETE request on /api/endpoints/:id
|
||||||
func (handler *Handler) endpointDelete(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
func (handler *Handler) endpointDelete(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||||
if !handler.authorizeEndpointManagement {
|
|
||||||
return &httperror.HandlerError{http.StatusServiceUnavailable, "Endpoint management is disabled", ErrEndpointManagementDisabled}
|
|
||||||
}
|
|
||||||
|
|
||||||
endpointID, err := request.RetrieveNumericRouteVariableValue(r, "id")
|
endpointID, err := request.RetrieveNumericRouteVariableValue(r, "id")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &httperror.HandlerError{http.StatusBadRequest, "Invalid endpoint identifier route variable", err}
|
return &httperror.HandlerError{http.StatusBadRequest, "Invalid endpoint identifier route variable", err}
|
||||||
|
|
|
@ -35,10 +35,6 @@ func (payload *endpointUpdatePayload) Validate(r *http.Request) error {
|
||||||
|
|
||||||
// PUT request on /api/endpoints/:id
|
// PUT request on /api/endpoints/:id
|
||||||
func (handler *Handler) endpointUpdate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
func (handler *Handler) endpointUpdate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||||
if !handler.authorizeEndpointManagement {
|
|
||||||
return &httperror.HandlerError{http.StatusServiceUnavailable, "Endpoint management is disabled", ErrEndpointManagementDisabled}
|
|
||||||
}
|
|
||||||
|
|
||||||
endpointID, err := request.RetrieveNumericRouteVariableValue(r, "id")
|
endpointID, err := request.RetrieveNumericRouteVariableValue(r, "id")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &httperror.HandlerError{http.StatusBadRequest, "Invalid endpoint identifier route variable", err}
|
return &httperror.HandlerError{http.StatusBadRequest, "Invalid endpoint identifier route variable", err}
|
||||||
|
|
|
@ -11,12 +11,6 @@ import (
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
// ErrEndpointManagementDisabled is an error raised when trying to access the endpoints management endpoints
|
|
||||||
// when the server has been started with the --external-endpoints flag
|
|
||||||
ErrEndpointManagementDisabled = portainer.Error("Endpoint management is disabled")
|
|
||||||
)
|
|
||||||
|
|
||||||
func hideFields(endpoint *portainer.Endpoint) {
|
func hideFields(endpoint *portainer.Endpoint) {
|
||||||
endpoint.AzureCredentials = portainer.AzureCredentials{}
|
endpoint.AzureCredentials = portainer.AzureCredentials{}
|
||||||
if len(endpoint.Snapshots) > 0 {
|
if len(endpoint.Snapshots) > 0 {
|
||||||
|
@ -27,7 +21,6 @@ func hideFields(endpoint *portainer.Endpoint) {
|
||||||
// Handler is the HTTP handler used to handle endpoint operations.
|
// Handler is the HTTP handler used to handle endpoint operations.
|
||||||
type Handler struct {
|
type Handler struct {
|
||||||
*mux.Router
|
*mux.Router
|
||||||
authorizeEndpointManagement bool
|
|
||||||
requestBouncer *security.RequestBouncer
|
requestBouncer *security.RequestBouncer
|
||||||
AuthorizationService *portainer.AuthorizationService
|
AuthorizationService *portainer.AuthorizationService
|
||||||
EdgeGroupService portainer.EdgeGroupService
|
EdgeGroupService portainer.EdgeGroupService
|
||||||
|
@ -45,10 +38,9 @@ type Handler struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewHandler creates a handler to manage endpoint operations.
|
// NewHandler creates a handler to manage endpoint operations.
|
||||||
func NewHandler(bouncer *security.RequestBouncer, authorizeEndpointManagement bool) *Handler {
|
func NewHandler(bouncer *security.RequestBouncer) *Handler {
|
||||||
h := &Handler{
|
h := &Handler{
|
||||||
Router: mux.NewRouter(),
|
Router: mux.NewRouter(),
|
||||||
authorizeEndpointManagement: authorizeEndpointManagement,
|
|
||||||
requestBouncer: bouncer,
|
requestBouncer: bouncer,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,6 @@ type Server struct {
|
||||||
BindAddress string
|
BindAddress string
|
||||||
AssetsPath string
|
AssetsPath string
|
||||||
AuthDisabled bool
|
AuthDisabled bool
|
||||||
EndpointManagement bool
|
|
||||||
Status *portainer.Status
|
Status *portainer.Status
|
||||||
ReverseTunnelService portainer.ReverseTunnelService
|
ReverseTunnelService portainer.ReverseTunnelService
|
||||||
ExtensionManager portainer.ExtensionManager
|
ExtensionManager portainer.ExtensionManager
|
||||||
|
@ -170,7 +169,7 @@ func (server *Server) Start() error {
|
||||||
var edgeTemplatesHandler = edgetemplates.NewHandler(requestBouncer)
|
var edgeTemplatesHandler = edgetemplates.NewHandler(requestBouncer)
|
||||||
edgeTemplatesHandler.SettingsService = server.SettingsService
|
edgeTemplatesHandler.SettingsService = server.SettingsService
|
||||||
|
|
||||||
var endpointHandler = endpoints.NewHandler(requestBouncer, server.EndpointManagement)
|
var endpointHandler = endpoints.NewHandler(requestBouncer)
|
||||||
endpointHandler.AuthorizationService = authorizationService
|
endpointHandler.AuthorizationService = authorizationService
|
||||||
endpointHandler.EdgeGroupService = server.EdgeGroupService
|
endpointHandler.EdgeGroupService = server.EdgeGroupService
|
||||||
endpointHandler.EdgeStackService = server.EdgeStackService
|
endpointHandler.EdgeStackService = server.EdgeStackService
|
||||||
|
|
|
@ -42,7 +42,6 @@ type (
|
||||||
Assets *string
|
Assets *string
|
||||||
Data *string
|
Data *string
|
||||||
EndpointURL *string
|
EndpointURL *string
|
||||||
ExternalEndpoints *string
|
|
||||||
Labels *[]Pair
|
Labels *[]Pair
|
||||||
Logo *string
|
Logo *string
|
||||||
NoAuth *bool
|
NoAuth *bool
|
||||||
|
@ -56,7 +55,6 @@ type (
|
||||||
SSL *bool
|
SSL *bool
|
||||||
SSLCert *string
|
SSLCert *string
|
||||||
SSLKey *string
|
SSLKey *string
|
||||||
SyncInterval *string
|
|
||||||
SnapshotInterval *string
|
SnapshotInterval *string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -209,6 +207,8 @@ type (
|
||||||
EndpointStatus int
|
EndpointStatus int
|
||||||
|
|
||||||
// EndpointSyncJob represents a scheduled job that synchronize endpoints based on an external file
|
// EndpointSyncJob represents a scheduled job that synchronize endpoints based on an external file
|
||||||
|
//
|
||||||
|
// Deprecated
|
||||||
EndpointSyncJob struct{}
|
EndpointSyncJob struct{}
|
||||||
|
|
||||||
// EndpointType represents the type of an endpoint
|
// EndpointType represents the type of an endpoint
|
||||||
|
@ -402,7 +402,9 @@ type (
|
||||||
EdgeSchedule *EdgeSchedule
|
EdgeSchedule *EdgeSchedule
|
||||||
ScriptExecutionJob *ScriptExecutionJob
|
ScriptExecutionJob *ScriptExecutionJob
|
||||||
SnapshotJob *SnapshotJob
|
SnapshotJob *SnapshotJob
|
||||||
EndpointSyncJob *EndpointSyncJob
|
|
||||||
|
// Deprecated fields
|
||||||
|
EndpointSyncJob *EndpointSyncJob
|
||||||
}
|
}
|
||||||
|
|
||||||
// ScheduleID represents a schedule identifier.
|
// ScheduleID represents a schedule identifier.
|
||||||
|
@ -490,10 +492,9 @@ type (
|
||||||
|
|
||||||
// Status represents the application status
|
// Status represents the application status
|
||||||
Status struct {
|
Status struct {
|
||||||
Authentication bool `json:"Authentication"`
|
Authentication bool `json:"Authentication"`
|
||||||
EndpointManagement bool `json:"EndpointManagement"`
|
Analytics bool `json:"Analytics"`
|
||||||
Analytics bool `json:"Analytics"`
|
Version string `json:"Version"`
|
||||||
Version string `json:"Version"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tag represents a tag that can be associated to a resource
|
// Tag represents a tag that can be associated to a resource
|
||||||
|
@ -1097,7 +1098,7 @@ const (
|
||||||
// SnapshotJobType is a system job used to create endpoint snapshots
|
// SnapshotJobType is a system job used to create endpoint snapshots
|
||||||
SnapshotJobType
|
SnapshotJobType
|
||||||
// EndpointSyncJobType is a system job used to synchronize endpoints from
|
// EndpointSyncJobType is a system job used to synchronize endpoints from
|
||||||
// an external definition store
|
// an external definition store (Deprecated)
|
||||||
EndpointSyncJobType
|
EndpointSyncJobType
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -3164,10 +3164,6 @@ definitions:
|
||||||
type: "boolean"
|
type: "boolean"
|
||||||
example: true
|
example: true
|
||||||
description: "Is authentication enabled"
|
description: "Is authentication enabled"
|
||||||
EndpointManagement:
|
|
||||||
type: "boolean"
|
|
||||||
example: true
|
|
||||||
description: "Is endpoint management enabled"
|
|
||||||
Analytics:
|
Analytics:
|
||||||
type: "boolean"
|
type: "boolean"
|
||||||
example: true
|
example: true
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<div class="toolBar">
|
<div class="toolBar">
|
||||||
<div class="toolBarTitle"> <i class="fa" ng-class="$ctrl.titleIcon" aria-hidden="true" style="margin-right: 2px;"></i> {{ $ctrl.titleText }} </div>
|
<div class="toolBarTitle"> <i class="fa" ng-class="$ctrl.titleIcon" aria-hidden="true" style="margin-right: 2px;"></i> {{ $ctrl.titleText }} </div>
|
||||||
</div>
|
</div>
|
||||||
<div class="actionBar" ng-if="$ctrl.endpointManagement">
|
<div class="actionBar">
|
||||||
<button type="button" class="btn btn-sm btn-danger" ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems)">
|
<button type="button" class="btn btn-sm btn-danger" ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems)">
|
||||||
<i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Remove
|
<i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Remove
|
||||||
</button>
|
</button>
|
||||||
|
@ -27,7 +27,7 @@
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>
|
<th>
|
||||||
<span class="md-checkbox" ng-if="$ctrl.endpointManagement">
|
<span class="md-checkbox">
|
||||||
<input id="select_all" type="checkbox" ng-model="$ctrl.state.selectAll" ng-change="$ctrl.selectAll()" />
|
<input id="select_all" type="checkbox" ng-model="$ctrl.state.selectAll" ng-change="$ctrl.selectAll()" />
|
||||||
<label for="select_all"></label>
|
<label for="select_all"></label>
|
||||||
</span>
|
</span>
|
||||||
|
@ -68,12 +68,11 @@
|
||||||
ng-class="{ active: item.Checked }"
|
ng-class="{ active: item.Checked }"
|
||||||
>
|
>
|
||||||
<td>
|
<td>
|
||||||
<span class="md-checkbox" ng-if="$ctrl.endpointManagement">
|
<span class="md-checkbox">
|
||||||
<input id="select_{{ $index }}" type="checkbox" ng-model="item.Checked" ng-click="$ctrl.selectItem(item, $event)" />
|
<input id="select_{{ $index }}" type="checkbox" ng-model="item.Checked" ng-click="$ctrl.selectItem(item, $event)" />
|
||||||
<label for="select_{{ $index }}"></label>
|
<label for="select_{{ $index }}"></label>
|
||||||
</span>
|
</span>
|
||||||
<a ui-sref="portainer.endpoints.endpoint({id: item.Id})" ng-if="$ctrl.endpointManagement">{{ item.Name }}</a>
|
<a ui-sref="portainer.endpoints.endpoint({id: item.Id})">{{ item.Name }}</a>
|
||||||
<span ng-if="!$ctrl.endpointManagement">{{ item.Name }}</span>
|
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<span>
|
<span>
|
||||||
|
|
|
@ -7,7 +7,6 @@ angular.module('portainer.app').component('endpointsDatatable', {
|
||||||
tableKey: '@',
|
tableKey: '@',
|
||||||
orderBy: '@',
|
orderBy: '@',
|
||||||
reverseOrder: '<',
|
reverseOrder: '<',
|
||||||
endpointManagement: '<',
|
|
||||||
accessManagement: '<',
|
accessManagement: '<',
|
||||||
removeAction: '<',
|
removeAction: '<',
|
||||||
retrievePage: '<',
|
retrievePage: '<',
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
export function StatusViewModel(data) {
|
export function StatusViewModel(data) {
|
||||||
this.Authentication = data.Authentication;
|
this.Authentication = data.Authentication;
|
||||||
this.Snapshot = data.Snapshot;
|
this.Snapshot = data.Snapshot;
|
||||||
this.EndpointManagement = data.EndpointManagement;
|
|
||||||
this.Analytics = data.Analytics;
|
this.Analytics = data.Analytics;
|
||||||
this.Version = data.Version;
|
this.Version = data.Version;
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,7 +79,6 @@ angular.module('portainer.app').factory('StateManager', [
|
||||||
function assignStateFromStatusAndSettings(status, settings) {
|
function assignStateFromStatusAndSettings(status, settings) {
|
||||||
state.application.authentication = status.Authentication;
|
state.application.authentication = status.Authentication;
|
||||||
state.application.analytics = status.Analytics;
|
state.application.analytics = status.Analytics;
|
||||||
state.application.endpointManagement = status.EndpointManagement;
|
|
||||||
state.application.version = status.Version;
|
state.application.version = status.Version;
|
||||||
state.application.logo = settings.LogoURL;
|
state.application.logo = settings.LogoURL;
|
||||||
state.application.snapshotInterval = settings.SnapshotInterval;
|
state.application.snapshotInterval = settings.SnapshotInterval;
|
||||||
|
|
|
@ -19,10 +19,6 @@ angular
|
||||||
Notifications,
|
Notifications,
|
||||||
Authentication
|
Authentication
|
||||||
) {
|
) {
|
||||||
if (!$scope.applicationState.application.endpointManagement) {
|
|
||||||
$state.go('portainer.endpoints');
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.state = {
|
$scope.state = {
|
||||||
uploadInProgress: false,
|
uploadInProgress: false,
|
||||||
actionInProgress: false,
|
actionInProgress: false,
|
||||||
|
|
|
@ -7,20 +7,6 @@
|
||||||
<rd-header-content>Endpoint management</rd-header-content>
|
<rd-header-content>Endpoint management</rd-header-content>
|
||||||
</rd-header>
|
</rd-header>
|
||||||
|
|
||||||
<div class="row" ng-if="!applicationState.application.endpointManagement">
|
|
||||||
<div class="col-sm-12">
|
|
||||||
<rd-widget>
|
|
||||||
<rd-widget-header icon="fa-exclamation-triangle" title-text="Endpoint management is not available"> </rd-widget-header>
|
|
||||||
<rd-widget-body>
|
|
||||||
<span class="small text-muted"
|
|
||||||
>Portainer has been started using the <code>--external-endpoints</code> flag. Endpoint management via the UI is disabled.
|
|
||||||
<span ng-if="applicationState.application.authentication">You can still manage endpoint access.</span>
|
|
||||||
</span>
|
|
||||||
</rd-widget-body>
|
|
||||||
</rd-widget>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<endpoints-datatable
|
<endpoints-datatable
|
||||||
|
@ -28,7 +14,6 @@
|
||||||
title-icon="fa-plug"
|
title-icon="fa-plug"
|
||||||
table-key="endpoints"
|
table-key="endpoints"
|
||||||
order-by="Name"
|
order-by="Name"
|
||||||
endpoint-management="applicationState.application.endpointManagement"
|
|
||||||
access-management="applicationState.application.authentication"
|
access-management="applicationState.application.authentication"
|
||||||
remove-action="removeAction"
|
remove-action="removeAction"
|
||||||
retrieve-page="getPaginatedEndpoints"
|
retrieve-page="getPaginatedEndpoints"
|
||||||
|
|
Loading…
Reference in New Issue