From 8b0eb71d6924df6b738fd9fcc708a0c8f56851eb Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Wed, 20 Nov 2019 18:02:07 +1300 Subject: [PATCH] feat(api): automatically update extensions at startup (#3349) * feat(api): automatically update extensions at startup * feat(api): review updateAndStartExtensions --- api/cmd/portainer/main.go | 17 +----------- api/exec/extension.go | 57 +++++++++++++++++++++++++++++++++++++++ api/portainer.go | 1 + 3 files changed, 59 insertions(+), 16 deletions(-) diff --git a/api/cmd/portainer/main.go b/api/cmd/portainer/main.go index 88b0a4e7c..3bc1eb67d 100644 --- a/api/cmd/portainer/main.go +++ b/api/cmd/portainer/main.go @@ -489,26 +489,11 @@ func initJobService(dockerClientFactory *docker.ClientFactory) portainer.JobServ func initExtensionManager(fileService portainer.FileService, extensionService portainer.ExtensionService) (portainer.ExtensionManager, error) { extensionManager := exec.NewExtensionManager(fileService, extensionService) - extensions, err := extensionService.Extensions() + err := extensionManager.StartExtensions() if err != nil { return nil, err } - for _, extension := range extensions { - err := extensionManager.EnableExtension(&extension, extension.License.LicenseKey) - if err != nil { - log.Printf("Unable to enable extension: %s [extension: %s]", err.Error(), extension.Name) - extension.Enabled = false - extension.License.Valid = false - } - - err = extensionService.Persist(&extension) - if err != nil { - return nil, err - } - - } - return extensionManager, nil } diff --git a/api/exec/extension.go b/api/exec/extension.go index ba52dd114..46cf902d3 100644 --- a/api/exec/extension.go +++ b/api/exec/extension.go @@ -13,6 +13,8 @@ import ( "strings" "time" + "github.com/coreos/go-semver/semver" + "github.com/orcaman/concurrent-map" "github.com/portainer/portainer/api" "github.com/portainer/portainer/api/http/client" @@ -146,6 +148,61 @@ func (manager *ExtensionManager) DisableExtension(extension *portainer.Extension return manager.fileService.RemoveDirectory(extensionBinaryPath) } +// StartExtensions will retrieve the extensions definitions from the Internet and check if a new version of each +// extension is available. If so, it will automatically install the new version of the extension. If no update is +// available it will simply start the extension. +// The purpose of this function is to be ran at startup, as such most of the error handling won't block the program execution +// and will log warning messages instead. +func (manager *ExtensionManager) StartExtensions() error { + extensions, err := manager.extensionService.Extensions() + if err != nil { + return err + } + + definitions, err := manager.FetchExtensionDefinitions() + if err != nil { + log.Printf("[WARN] [exec,extensions] [message: unable to retrieve extension information from Internet. Skipping extensions update check.] [err: %s]", err) + return nil + } + + return manager.updateAndStartExtensions(extensions, definitions) +} + +func (manager *ExtensionManager) updateAndStartExtensions(extensions []portainer.Extension, definitions []portainer.Extension) error { + for _, definition := range definitions { + for _, extension := range extensions { + if extension.ID == definition.ID { + definitionVersion := semver.New(definition.Version) + extensionVersion := semver.New(extension.Version) + + if extensionVersion.LessThan(*definitionVersion) { + log.Printf("[INFO] [exec,extensions] [message: new version detected, updating extension] [extension: %s] [current_version: %s] [available_version: %s]", extension.Name, extension.Version, definition.Version) + err := manager.UpdateExtension(&extension, definition.Version) + if err != nil { + log.Printf("[WARN] [exec,extensions] [message: unable to update extension automatically] [extension: %s] [current_version: %s] [available_version: %s] [err: %s]", extension.Name, extension.Version, definition.Version, err) + } + } else { + err := manager.EnableExtension(&extension, extension.License.LicenseKey) + if err != nil { + log.Printf("[WARN] [exec,extensions] [message: unable to start extension] [extension: %s] [err: %s]", extension.Name, err) + extension.Enabled = false + extension.License.Valid = false + } + } + + err := manager.extensionService.Persist(&extension) + if err != nil { + return err + } + + break + } + } + } + + return nil +} + // UpdateExtension will download the new extension binary from the official Portainer assets // server, disable the previous extension via DisableExtension, trigger a license check // and then start the extension process and add it to the processes map diff --git a/api/portainer.go b/api/portainer.go index 138aca7ed..64591b998 100644 --- a/api/portainer.go +++ b/api/portainer.go @@ -894,6 +894,7 @@ type ( EnableExtension(extension *Extension, licenseKey string) error DisableExtension(extension *Extension) error UpdateExtension(extension *Extension, version string) error + StartExtensions() error } // ReverseTunnelService represensts a service used to manage reverse tunnel connections.