mirror of
https://github.com/safing/portbase
synced 2025-09-01 01:59:48 +00:00
Add support for new index format
This commit is contained in:
parent
0e5eb4b6de
commit
f6fc67ad46
5 changed files with 165 additions and 14 deletions
|
@ -1,13 +1,97 @@
|
|||
package updater
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Index describes an index file pulled by the updater.
|
||||
type Index struct {
|
||||
// Path is the path to the index file
|
||||
// on the update server.
|
||||
Path string
|
||||
|
||||
// Channel holds the release channel name of the index.
|
||||
// It must match the filename without extension.
|
||||
Channel string
|
||||
|
||||
// PreRelease signifies that all versions of this index should be marked as
|
||||
// pre-releases, no matter if the versions actually have a pre-release tag or
|
||||
// not.
|
||||
PreRelease bool
|
||||
|
||||
// LastRelease holds the time of the last seen release of this index.
|
||||
LastRelease time.Time
|
||||
}
|
||||
|
||||
// IndexFile represents an index file.
|
||||
type IndexFile struct {
|
||||
Channel string
|
||||
Published time.Time
|
||||
|
||||
Releases map[string]string
|
||||
}
|
||||
|
||||
var (
|
||||
// ErrIndexFromFuture is returned when a signed index is parsed with a
|
||||
// Published timestamp that lies in the future.
|
||||
ErrIndexFromFuture = errors.New("index is from the future")
|
||||
|
||||
// ErrIndexIsOlder is returned when a signed index is parsed with an older
|
||||
// Published timestamp than the current Published timestamp.
|
||||
ErrIndexIsOlder = errors.New("index is older than the current one")
|
||||
|
||||
// ErrIndexChannelMismatch is returned when a signed index is parsed with a
|
||||
// different channel that the expected one.
|
||||
ErrIndexChannelMismatch = errors.New("index does not match the expected channel")
|
||||
)
|
||||
|
||||
// ParseIndexFile parses an index file and checks if it is valid.
|
||||
func ParseIndexFile(indexData []byte, channel string, currentPublished time.Time) (*IndexFile, error) {
|
||||
// Load into struct.
|
||||
indexFile := &IndexFile{}
|
||||
err := json.Unmarshal(indexData, indexFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse signed index data: %w", err)
|
||||
}
|
||||
|
||||
// Fallback to old format if there are no releases and no channel is defined.
|
||||
// TODO: Remove in v0.10
|
||||
if len(indexFile.Releases) == 0 && indexFile.Channel == "" {
|
||||
return loadOldIndexFormat(indexData, channel)
|
||||
}
|
||||
|
||||
// Check the index metadata.
|
||||
switch {
|
||||
case !indexFile.Published.IsZero() && time.Now().Before(indexFile.Published):
|
||||
return indexFile, ErrIndexFromFuture
|
||||
|
||||
case !indexFile.Published.IsZero() &&
|
||||
!currentPublished.IsZero() &&
|
||||
currentPublished.After(indexFile.Published):
|
||||
return indexFile, ErrIndexIsOlder
|
||||
|
||||
case channel != "" &&
|
||||
indexFile.Channel != "" &&
|
||||
channel != indexFile.Channel:
|
||||
return indexFile, ErrIndexChannelMismatch
|
||||
}
|
||||
|
||||
return indexFile, nil
|
||||
}
|
||||
|
||||
func loadOldIndexFormat(indexData []byte, channel string) (*IndexFile, error) {
|
||||
releases := make(map[string]string)
|
||||
err := json.Unmarshal(indexData, &releases)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &IndexFile{
|
||||
Channel: channel,
|
||||
Published: time.Now(),
|
||||
Releases: releases,
|
||||
}, nil
|
||||
}
|
||||
|
|
55
updater/indexes_test.go
Normal file
55
updater/indexes_test.go
Normal file
|
@ -0,0 +1,55 @@
|
|||
package updater
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var (
|
||||
oldFormat = `{
|
||||
"all/ui/modules/assets.zip": "0.3.0",
|
||||
"all/ui/modules/portmaster.zip": "0.2.4",
|
||||
"linux_amd64/core/portmaster-core": "0.8.13"
|
||||
}`
|
||||
|
||||
newFormat = `{
|
||||
"Channel": "stable",
|
||||
"Published": "2022-01-02T00:00:00Z",
|
||||
"Releases": {
|
||||
"all/ui/modules/assets.zip": "0.3.0",
|
||||
"all/ui/modules/portmaster.zip": "0.2.4",
|
||||
"linux_amd64/core/portmaster-core": "0.8.13"
|
||||
}
|
||||
}`
|
||||
|
||||
formatTestChannel = "stable"
|
||||
formatTestReleases = map[string]string{
|
||||
"all/ui/modules/assets.zip": "0.3.0",
|
||||
"all/ui/modules/portmaster.zip": "0.2.4",
|
||||
"linux_amd64/core/portmaster-core": "0.8.13",
|
||||
}
|
||||
)
|
||||
|
||||
func TestIndexParsing(t *testing.T) {
|
||||
lastRelease, err := time.Parse(time.RFC3339, "2022-01-01T00:00:00Z")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
oldIndexFile, err := ParseIndexFile([]byte(oldFormat), formatTestChannel, lastRelease)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
newIndexFile, err := ParseIndexFile([]byte(newFormat), formatTestChannel, lastRelease)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, formatTestChannel, oldIndexFile.Channel, "channel should be the same")
|
||||
assert.Equal(t, formatTestChannel, newIndexFile.Channel, "channel should be the same")
|
||||
assert.Equal(t, formatTestReleases, oldIndexFile.Releases, "releases should be the same")
|
||||
assert.Equal(t, formatTestReleases, newIndexFile.Releases, "releases should be the same")
|
||||
}
|
|
@ -3,7 +3,9 @@ package updater
|
|||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/safing/portbase/log"
|
||||
|
@ -50,6 +52,11 @@ func (reg *ResourceRegistry) AddIndex(idx Index) {
|
|||
reg.Lock()
|
||||
defer reg.Unlock()
|
||||
|
||||
// Get channel name from path.
|
||||
idx.Channel = strings.TrimSuffix(
|
||||
filepath.Base(idx.Path), filepath.Ext(idx.Path),
|
||||
)
|
||||
|
||||
reg.indexes = append(reg.indexes, idx)
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@ package updater
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
|
@ -131,18 +130,26 @@ func (reg *ResourceRegistry) loadIndexFile(idx Index) error {
|
|||
return err
|
||||
}
|
||||
|
||||
releases := make(map[string]string)
|
||||
err = json.Unmarshal(data, &releases)
|
||||
// Parse the index file.
|
||||
indexFile, err := ParseIndexFile(data, idx.Channel, idx.LastRelease)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(releases) == 0 {
|
||||
log.Debugf("%s: index %s is empty", reg.Name, idx.Path)
|
||||
// Update last seen release.
|
||||
// TODO: We are working with a copy here, so this has no effect.
|
||||
// This does not break to current implementation, but make the
|
||||
// protection ineffective.
|
||||
idx.LastRelease = indexFile.Published
|
||||
|
||||
// Warn if there aren't any releases in the index.
|
||||
if len(indexFile.Releases) == 0 {
|
||||
log.Debugf("%s: index %s has no releases", reg.Name, idx.Path)
|
||||
return nil
|
||||
}
|
||||
|
||||
err = reg.AddResources(releases, false, true, idx.PreRelease)
|
||||
// Add index releases to available resources.
|
||||
err = reg.AddResources(indexFile.Releases, false, true, idx.PreRelease)
|
||||
if err != nil {
|
||||
log.Warningf("%s: failed to add resource: %s", reg.Name, err)
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ package updater
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
@ -52,23 +51,22 @@ func (reg *ResourceRegistry) downloadIndex(ctx context.Context, client *http.Cli
|
|||
return fmt.Errorf("failed to download index %s: %w", idx.Path, err)
|
||||
}
|
||||
|
||||
// parse
|
||||
newIndexData := make(map[string]string)
|
||||
err = json.Unmarshal(data, &newIndexData)
|
||||
// Parse the index file.
|
||||
indexFile, err := ParseIndexFile(data, idx.Channel, idx.LastRelease)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse index %s: %w", idx.Path, err)
|
||||
}
|
||||
|
||||
// Add index data to registry.
|
||||
if len(newIndexData) > 0 {
|
||||
if len(indexFile.Releases) > 0 {
|
||||
// Check if all resources are within the indexes' authority.
|
||||
authoritativePath := path.Dir(idx.Path) + "/"
|
||||
if authoritativePath == "./" {
|
||||
// Fix path for indexes at the storage root.
|
||||
authoritativePath = ""
|
||||
}
|
||||
cleanedData := make(map[string]string, len(newIndexData))
|
||||
for key, version := range newIndexData {
|
||||
cleanedData := make(map[string]string, len(indexFile.Releases))
|
||||
for key, version := range indexFile.Releases {
|
||||
if strings.HasPrefix(key, authoritativePath) {
|
||||
cleanedData[key] = version
|
||||
} else {
|
||||
|
@ -100,7 +98,7 @@ func (reg *ResourceRegistry) downloadIndex(ctx context.Context, client *http.Cli
|
|||
log.Warningf("%s: failed to save updated index %s: %s", reg.Name, idx.Path, err)
|
||||
}
|
||||
|
||||
log.Infof("%s: updated index %s with %d entries", reg.Name, idx.Path, len(newIndexData))
|
||||
log.Infof("%s: updated index %s with %d entries", reg.Name, idx.Path, len(indexFile.Releases))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue