e15b908983
* 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>
243 lines
5.2 KiB
Go
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
|
|
}
|