Improve updatemgr and updates module

This commit is contained in:
Daniel
2025-01-21 09:19:18 +01:00
parent 49e1fc8c31
commit aa1bd0679c
11 changed files with 589 additions and 139 deletions

View File

@@ -82,7 +82,7 @@ func (d *Downloader) getIndex(ctx context.Context, url string) (indexData []byte
}
// Verify and parse index.
bundle, err = ParseIndex(indexData, d.u.cfg.Verify)
bundle, err = ParseIndex(indexData, d.u.cfg.Platform, d.u.cfg.Verify)
if err != nil {
return nil, nil, fmt.Errorf("parse index: %w", err)
}
@@ -206,14 +206,14 @@ func (d *Downloader) getArtifact(ctx context.Context, artifact *Artifact, url st
// TODO: Normally we should do operations on "untrusted" data _after_ verification,
// but we really want the checksum to be for the unpacked data. Should we add another checksum, or is HTTPS enough?
if artifact.Unpack != "" {
artifactData, err = decompress(artifact.Unpack, artifactData)
artifactData, err = Decompress(artifact.Unpack, artifactData)
if err != nil {
return nil, fmt.Errorf("decompress: %w", err)
}
}
// Verify checksum.
if err := checkSHA256Sum(artifactData, artifact.SHA256); err != nil {
if err := CheckSHA256Sum(artifactData, artifact.SHA256); err != nil {
return nil, err
}
@@ -250,7 +250,8 @@ func (d *Downloader) downloadData(ctx context.Context, url string) ([]byte, erro
return content, nil
}
func decompress(cType string, fileBytes []byte) ([]byte, error) {
// Decompress decompresses the given data according to the specified type.
func Decompress(cType string, fileBytes []byte) ([]byte, error) {
switch cType {
case "zip":
return decompressZip(fileBytes)

View File

@@ -121,7 +121,8 @@ type Index struct {
}
// LoadIndex loads and parses an index from the given filename.
func LoadIndex(filename string, trustStore jess.TrustStore) (*Index, error) {
// Leave platform empty to use current platform.
func LoadIndex(filename string, platform string, trustStore jess.TrustStore) (*Index, error) {
// Read index file from disk.
content, err := os.ReadFile(filename)
if err != nil {
@@ -129,11 +130,12 @@ func LoadIndex(filename string, trustStore jess.TrustStore) (*Index, error) {
}
// Parse and return.
return ParseIndex(content, trustStore)
return ParseIndex(content, platform, trustStore)
}
// ParseIndex parses an index from a json string.
func ParseIndex(jsonContent []byte, trustStore jess.TrustStore) (*Index, error) {
// Leave platform empty to use current platform.
func ParseIndex(jsonContent []byte, platform string, trustStore jess.TrustStore) (*Index, error) {
// Verify signature.
if trustStore != nil {
if err := filesig.VerifyJSONSignature(jsonContent, trustStore); err != nil {
@@ -148,8 +150,13 @@ func ParseIndex(jsonContent []byte, trustStore jess.TrustStore) (*Index, error)
return nil, fmt.Errorf("parse index: %w", err)
}
// Check platform.
if platform == "" {
platform = currentPlatform
}
// Initialize data.
err = index.init()
err = index.init(platform)
if err != nil {
return nil, err
}
@@ -157,7 +164,7 @@ func ParseIndex(jsonContent []byte, trustStore jess.TrustStore) (*Index, error)
return index, nil
}
func (index *Index) init() error {
func (index *Index) init(platform string) error {
// Parse version number, if set.
if index.Version != "" {
versionNum, err := semver.NewVersion(index.Version)
@@ -167,10 +174,10 @@ func (index *Index) init() error {
index.versionNum = versionNum
}
// Filter artifacts by current platform.
// Filter artifacts by platform.
filtered := make([]*Artifact, 0)
for _, a := range index.Artifacts {
if a.Platform == "" || a.Platform == currentPlatform {
if a.Platform == "" || a.Platform == platform {
filtered = append(filtered, a)
}
}
@@ -248,7 +255,7 @@ func (index *Index) ShouldUpgradeTo(newIndex *Index) error {
// VerifyArtifacts checks if all artifacts are present in the given dir and have the correct hash.
func (index *Index) VerifyArtifacts(dir string) error {
for _, artifact := range index.Artifacts {
err := checkSHA256SumFile(filepath.Join(dir, artifact.Filename), artifact.SHA256)
err := CheckSHA256SumFile(filepath.Join(dir, artifact.Filename), artifact.SHA256)
if err != nil {
return fmt.Errorf("verify %s: %w", artifact.Filename, err)
}
@@ -283,7 +290,8 @@ func (index *Index) Export(signingKey *jess.Signet, trustStore jess.TrustStore)
return signedIndex, nil
}
func checkSHA256SumFile(filename string, sha256sum string) error {
// CheckSHA256SumFile checks the sha256sum of the given file.
func CheckSHA256SumFile(filename string, sha256sum string) error {
// Check expected hash.
expectedDigest, err := hex.DecodeString(sha256sum)
if err != nil {
@@ -312,7 +320,8 @@ func checkSHA256SumFile(filename string, sha256sum string) error {
return nil
}
func checkSHA256Sum(fileData []byte, sha256sum string) error {
// CheckSHA256Sum checks the sha256sum of the given data.
func CheckSHA256Sum(fileData []byte, sha256sum string) error {
// Check expected hash.
expectedDigest, err := hex.DecodeString(sha256sum)
if err != nil {

View File

@@ -253,7 +253,7 @@ func GenerateIndexFromDir(sourceDir string, cfg IndexScanConfig) (*Index, error)
export := make([]*Artifact, 0, len(artifacts))
for _, artifact := range artifacts {
// Compute hash.
hash, err := getSHA256(artifact.localFile, artifact.Unpack)
hash, err := GetSHA256(artifact.localFile, artifact.Unpack)
if err != nil {
return nil, fmt.Errorf("calculate hash of file: %s %w", artifact.localFile, err)
}
@@ -294,7 +294,8 @@ func GenerateIndexFromDir(sourceDir string, cfg IndexScanConfig) (*Index, error)
return index, nil
}
func getSHA256(path string, unpackType string) (string, error) {
// GetSHA256 gets the sha256sum of the given file and unpacks it if necessary.
func GetSHA256(path string, unpackType string) (string, error) {
content, err := os.ReadFile(path)
if err != nil {
return "", err
@@ -302,7 +303,7 @@ func getSHA256(path string, unpackType string) (string, error) {
// Decompress if compression was applied to the file.
if unpackType != "" {
content, err = decompress(unpackType, content)
content, err = Decompress(unpackType, content)
if err != nil {
return "", err
}

View File

@@ -68,6 +68,8 @@ type Config struct {
IndexFile string
// Verify enables and specifies the trust the index signatures will be checked against.
Verify jess.TrustStore
// Platform defines the platform to download artifacts for. Defaults to current platform.
Platform string
// AutoCheck defines that new indexes may be downloaded automatically without outside trigger.
AutoCheck bool
@@ -116,6 +118,11 @@ func (cfg *Config) Check() error {
}
}
// Check platform.
if cfg.Platform == "" {
cfg.Platform = currentPlatform
}
return nil
}
@@ -168,7 +175,7 @@ func New(instance instance, name string, cfg Config) (*Updater, error) {
module.upgradeWorkerMgr = m.NewWorkerMgr("upgrader", module.upgradeWorker, nil)
// Load index.
index, err := LoadIndex(filepath.Join(cfg.Directory, cfg.IndexFile), cfg.Verify)
index, err := LoadIndex(filepath.Join(cfg.Directory, cfg.IndexFile), cfg.Platform, cfg.Verify)
if err == nil {
// Verify artifacts.
if err := index.VerifyArtifacts(cfg.Directory); err != nil {
@@ -186,7 +193,7 @@ func New(instance instance, name string, cfg Config) (*Updater, error) {
module.corruptedInstallation = fmt.Errorf("invalid index: %w", err)
}
index, err = GenerateIndexFromDir(cfg.Directory, IndexScanConfig{Version: "0.0.0"})
if err == nil && index.init() == nil {
if err == nil && index.init(currentPlatform) == nil {
module.index = index
return module, nil
}
@@ -214,7 +221,7 @@ func (u *Updater) updateAndUpgrade(w *mgr.WorkerCtx, indexURLs []string, ignoreV
}
} else {
// Otherwise, load index from download dir.
downloader.index, err = LoadIndex(filepath.Join(u.cfg.DownloadDirectory, u.cfg.IndexFile), u.cfg.Verify)
downloader.index, err = LoadIndex(filepath.Join(u.cfg.DownloadDirectory, u.cfg.IndexFile), u.cfg.Platform, u.cfg.Verify)
if err != nil {
return fmt.Errorf("load previously downloaded index file: %w", err)
}
@@ -573,7 +580,7 @@ func (u *Updater) GetFiles() ([]*Artifact, error) {
export := make([]*Artifact, 0, len(u.index.Artifacts))
for _, artifact := range u.index.Artifacts {
switch {
case artifact.Platform != "" && artifact.Platform != currentPlatform:
case artifact.Platform != "" && artifact.Platform != u.cfg.Platform:
// Platform is defined and does not match.
// Platforms are usually pre-filtered, but just to be sure.
default:
@@ -604,7 +611,7 @@ func (u *Updater) GetFile(name string) (*Artifact, error) {
switch {
case artifact.Filename != name:
// Name does not match.
case artifact.Platform != "" && artifact.Platform != currentPlatform:
case artifact.Platform != "" && artifact.Platform != u.cfg.Platform:
// Platform is defined and does not match.
// Platforms are usually pre-filtered, but just to be sure.
default:

View File

@@ -84,7 +84,7 @@ func TestPerformUpdate(t *testing.T) {
}
if data, err := os.ReadFile(filepath.Join(updateDir, "index.json")); err == nil {
fmt.Println(string(data))
idx, err := ParseIndex(data, nil)
idx, err := ParseIndex(data, updater.cfg.Platform, nil)
if err == nil {
fmt.Println(idx.Version)
fmt.Println(idx.versionNum)
@@ -97,7 +97,7 @@ func TestPerformUpdate(t *testing.T) {
})
// Check if the current version is now the new.
newIndex, err := LoadIndex(filepath.Join(installedDir, "index.json"), nil)
newIndex, err := LoadIndex(filepath.Join(installedDir, "index.json"), updater.cfg.Platform, nil)
if err != nil {
t.Fatal(err)
}