2017-02-06 05:29:34 +00:00
package cron
import (
"encoding/json"
"io/ioutil"
"log"
2017-02-07 03:26:12 +00:00
"strings"
2017-02-06 05:29:34 +00:00
"github.com/portainer/portainer"
)
2018-11-06 09:49:48 +00:00
// EndpointSyncJobRunner is used to run a EndpointSyncJob
type EndpointSyncJobRunner struct {
2018-11-13 01:39:26 +00:00
schedule * portainer . Schedule
context * EndpointSyncJobContext
2018-11-06 09:49:48 +00:00
}
2017-02-06 05:29:34 +00:00
2018-11-06 09:49:48 +00:00
// EndpointSyncJobContext represents the context of execution of a EndpointSyncJob
type EndpointSyncJobContext struct {
endpointService portainer . EndpointService
endpointFilePath string
}
2017-09-14 06:08:37 +00:00
2018-11-06 09:49:48 +00:00
// 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 ,
2017-09-14 06:08:37 +00:00
}
2018-11-06 09:49:48 +00:00
}
2017-02-06 05:29:34 +00:00
2018-11-06 09:49:48 +00:00
// NewEndpointSyncJobRunner returns a new runner that can be scheduled
2018-11-13 01:39:26 +00:00
func NewEndpointSyncJobRunner ( schedule * portainer . Schedule , context * EndpointSyncJobContext ) * EndpointSyncJobRunner {
2018-11-06 09:49:48 +00:00
return & EndpointSyncJobRunner {
2018-11-13 01:39:26 +00:00
schedule : schedule ,
context : context ,
2018-11-05 20:58:15 +00:00
}
}
2018-11-06 09:49:48 +00:00
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" `
}
2018-11-13 01:39:26 +00:00
// GetSchedule returns the schedule associated to the runner
func ( runner * EndpointSyncJobRunner ) GetSchedule ( ) * portainer . Schedule {
return runner . schedule
2018-11-06 09:49:48 +00:00
}
2018-11-05 20:58:15 +00:00
// Run triggers the execution of the endpoint synchronization process.
2018-11-06 09:49:48 +00:00
func ( runner * EndpointSyncJobRunner ) Run ( ) {
data , err := ioutil . ReadFile ( runner . context . endpointFilePath )
2018-11-05 20:58:15 +00:00
if endpointSyncError ( err ) {
return
}
var fileEndpoints [ ] fileEndpoint
err = json . Unmarshal ( data , & fileEndpoints )
if endpointSyncError ( err ) {
return
}
2017-02-06 05:29:34 +00:00
2018-11-05 20:58:15 +00:00
if len ( fileEndpoints ) == 0 {
2018-11-06 09:49:48 +00:00
log . Println ( "background job error (endpoint synchronization). External endpoint source is empty" )
2018-11-05 20:58:15 +00:00
return
}
2018-11-06 09:49:48 +00:00
storedEndpoints , err := runner . context . endpointService . Endpoints ( )
2018-11-05 20:58:15 +00:00
if endpointSyncError ( err ) {
return
}
convertedFileEndpoints := convertFileEndpoints ( fileEndpoints )
sync := prepareSyncData ( storedEndpoints , convertedFileEndpoints )
if sync . requireSync ( ) {
2018-11-06 09:49:48 +00:00
err = runner . context . endpointService . Synchronize ( sync . endpointsToCreate , sync . endpointsToUpdate , sync . endpointsToDelete )
2018-11-05 20:58:15 +00:00
if endpointSyncError ( err ) {
return
}
log . Printf ( "Endpoint synchronization ended. [created: %v] [updated: %v] [deleted: %v]" , len ( sync . endpointsToCreate ) , len ( sync . endpointsToUpdate ) , len ( sync . endpointsToDelete ) )
2017-02-06 05:29:34 +00:00
}
}
2018-07-11 08:39:20 +00:00
func endpointSyncError ( err error ) bool {
2017-02-06 05:29:34 +00:00
if err != nil {
2018-11-06 09:49:48 +00:00
log . Printf ( "background job error (endpoint synchronization). Unable to synchronize endpoints (err=%s)\n" , err )
2017-02-06 05:29:34 +00:00
return true
}
return false
}
func isValidEndpoint ( endpoint * portainer . Endpoint ) bool {
if endpoint . Name != "" && endpoint . URL != "" {
2017-02-07 03:26:12 +00:00
if ! strings . HasPrefix ( endpoint . URL , "unix://" ) && ! strings . HasPrefix ( endpoint . URL , "tcp://" ) {
return false
}
2017-02-06 05:29:34 +00:00
return true
}
return false
}
2017-09-14 06:08:37 +00:00
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
}
2017-02-06 05:29:34 +00:00
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
2017-09-14 06:08:37 +00:00
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 ) {
2017-02-06 05:29:34 +00:00
endpoint = original
endpoint . URL = updated . URL
2017-09-14 06:08:37 +00:00
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
2017-02-06 05:29:34 +00:00
} else {
2017-09-14 06:08:37 +00:00
endpoint . TLSConfig . TLS = false
endpoint . TLSConfig . TLSSkipVerify = false
endpoint . TLSConfig . TLSCACertPath = ""
endpoint . TLSConfig . TLSCertPath = ""
endpoint . TLSConfig . TLSKeyPath = ""
2017-02-06 05:29:34 +00:00
}
}
return endpoint
}
func ( sync synchronization ) requireSync ( ) bool {
if len ( sync . endpointsToCreate ) != 0 || len ( sync . endpointsToUpdate ) != 0 || len ( sync . endpointsToDelete ) != 0 {
return true
}
return false
}
2018-11-05 20:58:15 +00:00
func prepareSyncData ( storedEndpoints , fileEndpoints [ ] portainer . Endpoint ) * synchronization {
2017-02-06 05:29:34 +00:00
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 {
2018-07-11 08:39:20 +00:00
log . Printf ( "New definition for a stored endpoint found in file, updating database. [name: %v] [url: %v]\n" , endpoint . Name , endpoint . URL )
2017-02-06 05:29:34 +00:00
endpointsToUpdate = append ( endpointsToUpdate , endpoint )
}
} else {
2018-07-11 08:39:20 +00:00
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 )
2017-02-06 05:29:34 +00:00
endpointsToDelete = append ( endpointsToDelete , & storedEndpoints [ idx ] )
}
}
for idx , endpoint := range fileEndpoints {
2017-07-12 13:15:42 +00:00
if ! isValidEndpoint ( & endpoint ) {
2018-07-11 08:39:20 +00:00
log . Printf ( "Invalid file endpoint definition, skipping. [name: %v] [url: %v]" , endpoint . Name , endpoint . URL )
2017-02-06 05:29:34 +00:00
continue
}
sidx := endpointExists ( & fileEndpoints [ idx ] , storedEndpoints )
if sidx == - 1 {
2018-07-11 08:39:20 +00:00
log . Printf ( "File endpoint not found in database, adding to database. [name: %v] [url: %v]" , fileEndpoints [ idx ] . Name , fileEndpoints [ idx ] . URL )
2017-02-06 05:29:34 +00:00
endpointsToCreate = append ( endpointsToCreate , & fileEndpoints [ idx ] )
}
}
return & synchronization {
endpointsToCreate : endpointsToCreate ,
endpointsToUpdate : endpointsToUpdate ,
endpointsToDelete : endpointsToDelete ,
}
}