github.com/safing/portbase@v0.19.5/updater/registry.go (about)

     1  package updater
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"os"
     7  	"path/filepath"
     8  	"runtime"
     9  	"strings"
    10  	"sync"
    11  
    12  	"github.com/safing/portbase/log"
    13  	"github.com/safing/portbase/utils"
    14  )
    15  
    16  const (
    17  	onWindows = runtime.GOOS == "windows"
    18  )
    19  
    20  // ResourceRegistry is a registry for managing update resources.
    21  type ResourceRegistry struct {
    22  	sync.RWMutex
    23  
    24  	Name       string
    25  	storageDir *utils.DirStructure
    26  	tmpDir     *utils.DirStructure
    27  	indexes    []*Index
    28  	state      *RegistryState
    29  
    30  	resources        map[string]*Resource
    31  	UpdateURLs       []string
    32  	UserAgent        string
    33  	MandatoryUpdates []string
    34  	AutoUnpack       []string
    35  
    36  	// Verification holds a map of VerificationOptions assigned to their
    37  	// applicable identifier path prefix.
    38  	// Use an empty string to denote the default.
    39  	// Use empty options to disable verification for a path prefix.
    40  	Verification map[string]*VerificationOptions
    41  
    42  	// UsePreReleases signifies that pre-releases should be used when selecting a
    43  	// version. Even if false, a pre-release version will still be used if it is
    44  	// defined as the current version by an index.
    45  	UsePreReleases bool
    46  
    47  	// DevMode specifies if a local 0.0.0 version should be always chosen, when available.
    48  	DevMode bool
    49  
    50  	// Online specifies if resources may be downloaded if not available locally.
    51  	Online bool
    52  
    53  	// StateNotifyFunc may be set to receive any changes to the registry state.
    54  	// The specified function may lock the state, but may not block or take a
    55  	// lot of time.
    56  	StateNotifyFunc func(*RegistryState)
    57  }
    58  
    59  // AddIndex adds a new index to the resource registry.
    60  // The order is important, as indexes added later will override the current
    61  // release from earlier indexes.
    62  func (reg *ResourceRegistry) AddIndex(idx Index) {
    63  	reg.Lock()
    64  	defer reg.Unlock()
    65  
    66  	// Get channel name from path.
    67  	idx.Channel = strings.TrimSuffix(
    68  		filepath.Base(idx.Path), filepath.Ext(idx.Path),
    69  	)
    70  
    71  	reg.indexes = append(reg.indexes, &idx)
    72  }
    73  
    74  // PreInitUpdateState sets the initial update state of the registry before initialization.
    75  func (reg *ResourceRegistry) PreInitUpdateState(s UpdateState) error {
    76  	if reg.state != nil {
    77  		return errors.New("registry already initialized")
    78  	}
    79  
    80  	reg.state = &RegistryState{
    81  		Updates: s,
    82  	}
    83  	return nil
    84  }
    85  
    86  // Initialize initializes a raw registry struct and makes it ready for usage.
    87  func (reg *ResourceRegistry) Initialize(storageDir *utils.DirStructure) error {
    88  	// check if storage dir is available
    89  	err := storageDir.Ensure()
    90  	if err != nil {
    91  		return err
    92  	}
    93  
    94  	// set default name
    95  	if reg.Name == "" {
    96  		reg.Name = "updater"
    97  	}
    98  
    99  	// initialize private attributes
   100  	reg.storageDir = storageDir
   101  	reg.tmpDir = storageDir.ChildDir("tmp", 0o0700)
   102  	reg.resources = make(map[string]*Resource)
   103  	if reg.state == nil {
   104  		reg.state = &RegistryState{}
   105  	}
   106  	reg.state.ID = StateReady
   107  	reg.state.reg = reg
   108  
   109  	// remove tmp dir to delete old entries
   110  	err = reg.Cleanup()
   111  	if err != nil {
   112  		log.Warningf("%s: failed to remove tmp dir: %s", reg.Name, err)
   113  	}
   114  
   115  	// (re-)create tmp dir
   116  	err = reg.tmpDir.Ensure()
   117  	if err != nil {
   118  		log.Warningf("%s: failed to create tmp dir: %s", reg.Name, err)
   119  	}
   120  
   121  	// Check verification options.
   122  	if reg.Verification != nil {
   123  		for prefix, opts := range reg.Verification {
   124  			// Check if verification is disable for this prefix.
   125  			if opts == nil {
   126  				continue
   127  			}
   128  
   129  			// If enabled, a trust store is required.
   130  			if opts.TrustStore == nil {
   131  				return fmt.Errorf("verification enabled for prefix %q, but no trust store configured", prefix)
   132  			}
   133  
   134  			// DownloadPolicy must be equal or stricter than DiskLoadPolicy.
   135  			if opts.DiskLoadPolicy < opts.DownloadPolicy {
   136  				return errors.New("verification download policy must be equal or stricter than the disk load policy")
   137  			}
   138  
   139  			// Warn if all policies are disabled.
   140  			if opts.DownloadPolicy == SignaturePolicyDisable &&
   141  				opts.DiskLoadPolicy == SignaturePolicyDisable {
   142  				log.Warningf("%s: verification enabled for prefix %q, but all policies set to disable", reg.Name, prefix)
   143  			}
   144  		}
   145  	}
   146  
   147  	return nil
   148  }
   149  
   150  // StorageDir returns the main storage dir of the resource registry.
   151  func (reg *ResourceRegistry) StorageDir() *utils.DirStructure {
   152  	return reg.storageDir
   153  }
   154  
   155  // TmpDir returns the temporary working dir of the resource registry.
   156  func (reg *ResourceRegistry) TmpDir() *utils.DirStructure {
   157  	return reg.tmpDir
   158  }
   159  
   160  // SetDevMode sets the development mode flag.
   161  func (reg *ResourceRegistry) SetDevMode(on bool) {
   162  	reg.Lock()
   163  	defer reg.Unlock()
   164  
   165  	reg.DevMode = on
   166  }
   167  
   168  // SetUsePreReleases sets the UsePreReleases flag.
   169  func (reg *ResourceRegistry) SetUsePreReleases(yes bool) {
   170  	reg.Lock()
   171  	defer reg.Unlock()
   172  
   173  	reg.UsePreReleases = yes
   174  }
   175  
   176  // AddResource adds a resource to the registry. Does _not_ select new version.
   177  func (reg *ResourceRegistry) AddResource(identifier, version string, index *Index, available, currentRelease, preRelease bool) error {
   178  	reg.Lock()
   179  	defer reg.Unlock()
   180  
   181  	err := reg.addResource(identifier, version, index, available, currentRelease, preRelease)
   182  	return err
   183  }
   184  
   185  func (reg *ResourceRegistry) addResource(identifier, version string, index *Index, available, currentRelease, preRelease bool) error {
   186  	res, ok := reg.resources[identifier]
   187  	if !ok {
   188  		res = reg.newResource(identifier)
   189  		reg.resources[identifier] = res
   190  	}
   191  	res.Index = index
   192  
   193  	return res.AddVersion(version, available, currentRelease, preRelease)
   194  }
   195  
   196  // AddResources adds resources to the registry. Errors are logged, the last one is returned. Despite errors, non-failing resources are still added. Does _not_ select new versions.
   197  func (reg *ResourceRegistry) AddResources(versions map[string]string, index *Index, available, currentRelease, preRelease bool) error {
   198  	reg.Lock()
   199  	defer reg.Unlock()
   200  
   201  	// add versions and their flags to registry
   202  	var lastError error
   203  	for identifier, version := range versions {
   204  		lastError = reg.addResource(identifier, version, index, available, currentRelease, preRelease)
   205  		if lastError != nil {
   206  			log.Warningf("%s: failed to add resource %s: %s", reg.Name, identifier, lastError)
   207  		}
   208  	}
   209  
   210  	return lastError
   211  }
   212  
   213  // SelectVersions selects new resource versions depending on the current registry state.
   214  func (reg *ResourceRegistry) SelectVersions() {
   215  	reg.RLock()
   216  	defer reg.RUnlock()
   217  
   218  	for _, res := range reg.resources {
   219  		res.Lock()
   220  		res.selectVersion()
   221  		res.Unlock()
   222  	}
   223  }
   224  
   225  // GetSelectedVersions returns a list of the currently selected versions.
   226  func (reg *ResourceRegistry) GetSelectedVersions() (versions map[string]string) {
   227  	reg.RLock()
   228  	defer reg.RUnlock()
   229  
   230  	for _, res := range reg.resources {
   231  		res.Lock()
   232  		versions[res.Identifier] = res.SelectedVersion.VersionNumber
   233  		res.Unlock()
   234  	}
   235  
   236  	return
   237  }
   238  
   239  // Purge deletes old updates, retaining a certain amount, specified by the keep
   240  // parameter. Will at least keep 2 updates per resource.
   241  func (reg *ResourceRegistry) Purge(keep int) {
   242  	reg.RLock()
   243  	defer reg.RUnlock()
   244  
   245  	for _, res := range reg.resources {
   246  		res.Purge(keep)
   247  	}
   248  }
   249  
   250  // ResetResources removes all resources from the registry.
   251  func (reg *ResourceRegistry) ResetResources() {
   252  	reg.Lock()
   253  	defer reg.Unlock()
   254  
   255  	reg.resources = make(map[string]*Resource)
   256  }
   257  
   258  // ResetIndexes removes all indexes from the registry.
   259  func (reg *ResourceRegistry) ResetIndexes() {
   260  	reg.Lock()
   261  	defer reg.Unlock()
   262  
   263  	reg.indexes = make([]*Index, 0, len(reg.indexes))
   264  }
   265  
   266  // Cleanup removes temporary files.
   267  func (reg *ResourceRegistry) Cleanup() error {
   268  	// delete download tmp dir
   269  	return os.RemoveAll(reg.tmpDir.Path)
   270  }