Files
portainer/api/license/license.go
T
Dmitry Salakhov e15b908983 Feat(backup): add the ability to backup and restore portainer from file [EE-279] (#204)
* EE-319: backup endpoint (#193)

* feat(backup):
* add an orbiter to block writes while backup
* add backup handler
* add an ability to tar.gz a dir
* add aes encryption support

* EE-320: restore endpoint (#196)

* feat(backup):
* add restore handler
* re-init system state after restore

* feat(backup): Update server to respect readonly lock (#199)

* feat(backup): EE-322 Add backup and restore screen (#198)

Co-authored-by: Simon Meng <simon.meng@portainer.io>

* name archive as portainer-backup_yyyy-mm-dd_hh-mm-ss

* backup custom templates and edge jobs

* restart http and proxy servers after restore to re-init internal state

* feat(backup): EE-322 hide password field if password protect toggle is off

* feat(backup): EE-322 add tooltip for password field of restore backup

* feat(backup): EE-322 wait for backend restart after restoring

* Shutdown background go-routines

* changed restore err message when cannot extract

* fix: symlinks are ignored from backups

* replace single admin check with a restartable monitor (#238)

* clean log

Co-authored-by: Maxime Bajeux <max.bajeux@gmail.com>
Co-authored-by: cong meng <mcpacino@gmail.com>
Co-authored-by: Simon Meng <simon.meng@portainer.io>
2021-04-06 15:41:41 +12:00

243 lines
5.2 KiB
Go

package license
import (
"context"
"errors"
"log"
"time"
"github.com/portainer/liblicense"
"github.com/portainer/liblicense/master"
portainer "github.com/portainer/portainer/api"
)
// Service represents a service for managing portainer licenses
type Service struct {
repository portainer.LicenseRepository
info *portainer.LicenseInfo
shutdownCtx context.Context
}
// NewService creates a new instance of Service
func NewService(repository portainer.LicenseRepository, shutdownCtx context.Context) *Service {
return &Service{
repository: repository,
info: nil,
shutdownCtx: shutdownCtx,
}
}
// Start starts the service
func (service *Service) Start() error {
return service.startSyncLoop()
}
// Init initializes internal state
func (service *Service) Init() error {
licenses, err := service.Licenses()
if err != nil {
return err
}
service.info = aggregate(licenses)
return nil
}
// Info returns aggregation of the information about the existing licenses
func (service *Service) Info() *portainer.LicenseInfo {
return service.info
}
// Licenses returns the list of the existing licenses
func (service *Service) Licenses() ([]liblicense.PortainerLicense, error) {
return service.repository.Licenses()
}
// AddLicense adds the license to instance
func (service *Service) AddLicense(licenseKey string) (*liblicense.PortainerLicense, error) {
licenses, err := service.Licenses()
if err != nil {
return nil, err
}
license, err := master.ParseLicenseKey(licenseKey)
if err != nil {
return nil, err
}
if license.Type == liblicense.PortainerLicenseTrial && len(licenses) > 0 {
return nil, errors.New("Trial license can not be applied when there are existing licenses")
}
for _, existingLicense := range licenses {
if existingLicense.LicenseKey == licenseKey {
return nil, errors.New("License was already applied")
}
if existingLicense.Type == liblicense.PortainerLicenseTrial {
err = service.repository.DeleteLicense(existingLicense.LicenseKey)
if err != nil {
return nil, err
}
log.Printf("[DEBUG] [msg: removing the existing trial license]")
}
}
valid, err := master.ValidateLicense(license)
if err != nil {
return nil, err
}
if !valid {
return nil, errors.New("License is invalid")
}
err = service.repository.AddLicense(license.LicenseKey, license)
if err != nil {
return nil, err
}
licenses, err = service.Licenses()
if err != nil {
return nil, err
}
service.info = aggregate(licenses)
return license, nil
}
// DeleteLicense removes the license from instance
func (service *Service) DeleteLicense(licenseKey string) error {
license, err := service.repository.License(licenseKey)
if err != nil {
return err
}
valid := isLicenseValid(*license)
if valid {
licenses, err := service.Licenses()
if err != nil {
return err
}
hasMoreValidLicenses := false
for _, otherLicense := range licenses {
if licenseKey != otherLicense.LicenseKey && isLicenseValid(otherLicense) {
hasMoreValidLicenses = true
break
}
}
if !hasMoreValidLicenses {
return errors.New("At least one valid license is expected")
}
}
err = service.repository.DeleteLicense(licenseKey)
if err != nil {
return err
}
licenses, err := service.Licenses()
if err != nil {
return err
}
service.info = aggregate(licenses)
return nil
}
// revokeLicense revokes the license
func (service *Service) revokeLicense(licenseKey string) error {
license, err := service.repository.License(licenseKey)
if err != nil {
return err
}
license.Revoked = true
err = service.repository.UpdateLicense(licenseKey, license)
if err != nil {
return err
}
licenses, err := service.Licenses()
if err != nil {
return err
}
service.info = aggregate(licenses)
return nil
}
func aggregate(licenses []liblicense.PortainerLicense) *portainer.LicenseInfo {
nodes := 0
var expiresAt time.Time
company := ""
edition := liblicense.PortainerEE
licenseType := liblicense.PortainerLicenseSubscription
if len(licenses) == 0 {
return &portainer.LicenseInfo{Valid: false}
}
hasValidLicenses := false
for _, license := range licenses {
valid := isLicenseValid(license)
if !valid {
continue
}
hasValidLicenses = true
if license.Company != "" {
company = license.Company
}
nodes = nodes + license.Nodes
licenseExpiresAt := licenseExpiresAt(license)
if licenseExpiresAt.Before(expiresAt) || expiresAt.IsZero() {
expiresAt = licenseExpiresAt
}
if int(license.ProductEdition) < int(edition) {
edition = license.ProductEdition
}
if license.Type == liblicense.PortainerLicenseTrial {
licenseType = liblicense.PortainerLicenseTrial
}
}
expiresAtUnix := expiresAt.Unix()
if expiresAt.IsZero() {
expiresAtUnix = 0
}
return &portainer.LicenseInfo{
Company: company,
ProductEdition: edition,
Nodes: nodes,
ExpiresAt: expiresAtUnix,
Type: licenseType,
Valid: hasValidLicenses,
}
}
func licenseExpiresAt(license liblicense.PortainerLicense) time.Time {
return master.ExpiresAt(license.Created, license.ExpiresAfter)
}
func isLicenseValid(license liblicense.PortainerLicense) bool {
return !master.Expired(license.Created, license.ExpiresAfter) && !license.Revoked
}