Andrey Smirnov cde9b3954c
fix: update Talos version listing
Now Image Factory filters out pre-release versions for all releases but
the last one.

In the UI, now pre-release versions are shown.

Return proper 404 not found when someone requests something for
an unsupported version.

Signed-off-by: Andrey Smirnov <andrey.smirnov@siderolabs.com>
2023-12-21 15:02:36 +04:00

172 lines
3.9 KiB
Go

// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package artifacts
import (
"archive/tar"
"bufio"
"context"
"errors"
"fmt"
"io"
"slices"
"strings"
"time"
"github.com/blang/semver/v4"
"github.com/google/go-containerregistry/pkg/name"
"github.com/siderolabs/gen/xslices"
"go.uber.org/zap"
)
func (m *Manager) fetchTalosVersions() (any, error) {
m.logger.Info("fetching available Talos versions")
ctx, cancel := context.WithTimeout(context.Background(), FetchTimeout)
defer cancel()
repository := m.imageRegistry.Repo(ImagerImage)
candidates, err := m.pullers[ArchAmd64].List(ctx, repository)
if err != nil {
return nil, fmt.Errorf("failed to list Talos versions: %w", err)
}
var versions []semver.Version //nolint:prealloc
for _, candidate := range candidates {
version, err := semver.ParseTolerant(candidate)
if err != nil {
continue // ignore invalid versions
}
versions = append(versions, version)
}
// find "current" maximum version
maxVersion := slices.MaxFunc(versions, semver.Version.Compare)
// allow non-prerelease versions, and allow pre-release for the "latest" release (maxVersion)
versions = xslices.Filter(versions, func(version semver.Version) bool {
if version.LT(m.options.MinVersion) {
return false // ignore versions below minimum
}
if len(version.Pre) > 0 {
if !(version.Major == maxVersion.Major && version.Minor == maxVersion.Minor) {
return false // ignore pre-releases for older versions
}
if len(version.Pre) != 2 {
return false
}
if !(version.Pre[0].VersionStr == "alpha" || version.Pre[0].VersionStr == "beta") {
return false
}
if !version.Pre[1].IsNumeric() {
return false
}
}
return true
})
slices.SortFunc(versions, semver.Version.Compare)
m.talosVersionsMu.Lock()
m.talosVersions, m.talosVersionsTimestamp = versions, time.Now()
m.talosVersionsMu.Unlock()
return nil, nil //nolint:nilnil
}
// ExtensionRef is a ref to the extension for some Talos version.
type ExtensionRef struct {
TaggedReference name.Tag
Digest string
}
func (m *Manager) fetchOfficialExtensions(tag string) error {
var extensions []ExtensionRef
if err := m.fetchImageByTag(ExtensionManifestImage, tag, ArchAmd64, imageExportHandler(func(logger *zap.Logger, r io.Reader) error {
var extractErr error
extensions, extractErr = extractExtensionList(r)
if extractErr == nil {
m.logger.Info("extracted the image digests", zap.Int("count", len(extensions)))
}
return extractErr
})); err != nil {
return err
}
m.officialExtensionsMu.Lock()
if m.officialExtensions == nil {
m.officialExtensions = make(map[string][]ExtensionRef)
}
m.officialExtensions[tag] = extensions
m.officialExtensionsMu.Unlock()
return nil
}
func extractExtensionList(r io.Reader) ([]ExtensionRef, error) {
var extensions []ExtensionRef
tr := tar.NewReader(r)
for {
hdr, err := tr.Next()
if err != nil {
if errors.Is(err, io.EOF) {
break
}
return nil, fmt.Errorf("error reading tar header: %w", err)
}
if hdr.Name == "image-digests" {
scanner := bufio.NewScanner(tr)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
tagged, digest, ok := strings.Cut(line, "@")
if !ok {
continue
}
taggedRef, err := name.NewTag(tagged)
if err != nil {
return nil, fmt.Errorf("failed to parse tagged reference %s: %w", tagged, err)
}
extensions = append(extensions, ExtensionRef{
TaggedReference: taggedRef,
Digest: digest,
})
}
if scanner.Err() != nil {
return nil, fmt.Errorf("error reading image-digests: %w", scanner.Err())
}
}
}
if extensions != nil {
return extensions, nil
}
return nil, errors.New("failed to find image-digests file")
}