Files
cthulhu/internal/app/tentacle_preparation.go
2026-11-01 22:01:32 -05:00

109 lines
3.6 KiB
Go

// Copyright (c) 2026 Storm Dragon
// Authors: Storm Dragon, Angus Kola
// SPDX-License-Identifier: GPL-3.0-or-later
package app
import (
"context"
"errors"
"fmt"
"slices"
"time"
"git.stormux.org/storm/cthulhu/internal/tentacle"
)
// Preparation runs on the caller's worker. Its busy reservation remains owned
// by Manage through the lifecycle reply, including rollback after cancellation.
func (runtime *tentacleRuntime) beginRepositoryWork() bool {
runtime.mutex.Lock()
defer runtime.mutex.Unlock()
if runtime.repositoryBusy {
return false
}
runtime.repositoryBusy = true
return true
}
func (runtime *tentacleRuntime) endRepositoryWork() {
runtime.mutex.Lock()
runtime.repositoryBusy = false
runtime.mutex.Unlock()
}
func repositoryForInstallation(snapshot tentacle.Snapshot, request tentacle.ManagementRequest) (tentacle.Repository, error) {
if request.Entry != nil {
return tentacle.Repository{}, errors.New("unexpected tentacle entry in installation request")
}
if snapshot.Suppressed || snapshot.Message != "" {
return tentacle.Repository{}, tentacle.ErrUnavailable
}
index := slices.IndexFunc(snapshot.Repositories, func(value tentacle.Repository) bool { return value.URL == request.URL })
if index < 0 {
return tentacle.Repository{}, errors.New("repository is not configured")
}
repository := snapshot.Repositories[index]
id := tentacle.PackageID(repository, request.ID)
installed := slices.ContainsFunc(snapshot.Entries, func(value tentacle.EntryStatus) bool { return value.Entry.ID == id })
if request.Action == "upgrade" && !installed {
return tentacle.Repository{}, errors.New("tentacle is not installed")
}
if request.Action != "upgrade" && installed {
return tentacle.Repository{}, errors.New("tentacle is already installed")
}
if request.Action == "upgrade" {
for _, status := range snapshot.Entries {
if status.Entry.ID != id {
continue
}
for _, manifest := range repository.Tentacles {
if manifest.ID != request.ID {
continue
}
if manifest.Version == status.Entry.Version {
return tentacle.Repository{}, errors.New("tentacle is already at the repository version")
}
newer, err := tentacle.IsNewerVersion(manifest.Version, status.Entry.Version)
if err != nil {
return tentacle.Repository{}, fmt.Errorf("cannot compare tentacle versions: %w", err)
}
if !newer {
return tentacle.Repository{}, errors.New("repository tentacle version is not newer than installed version")
}
break
}
break
}
}
return repository, nil
}
func (runtime *tentacleRuntime) prepareManagedPackage(ctx context.Context, repository tentacle.Repository, id string) (*tentacle.PreparedPackage, error) {
prepare := runtime.preparePackage
if prepare == nil {
prepare = tentacle.PreparePackage
}
prepareCtx, cancel := context.WithTimeout(ctx, 45*time.Second)
stop := context.AfterFunc(runtime.lifecycleContext, cancel)
prepared, err := prepare(prepareCtx, tentacle.PackageRoot(runtime.registry.Path), repository, id)
stop()
cancel()
return prepared, err
}
func (runtime *tentacleRuntime) fetchManagedRepository(ctx context.Context, origin string) (tentacle.Repository, error) {
fetch := runtime.fetchRepository
if fetch == nil {
fetch = tentacle.FetchRepository
}
// Fetch on the Preferences worker, leaving lifecycle and native input
// free to disable, revoke, and process failures while the network waits.
fetchCtx, cancel := context.WithTimeout(ctx, 45*time.Second)
stop := context.AfterFunc(runtime.lifecycleContext, cancel)
repository, err := fetch(fetchCtx, origin)
stop()
cancel()
return repository, err
}