You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

204 lines
6.3 KiB

package main
import (
"context"
"flag"
"log"
"fmt"
"path/filepath"
"strings"
"github.com/snapcore/snapd/snap"
"github.com/snapcore/snapd/store"
)
// Seed structure for seed.yaml
type seed struct {
Snaps []struct {
Name string `yaml:"name"`
Channel string `yaml:"channel"`
File string `yaml:"file"`
} `yaml:"snaps"`
}
var (
ctx = context.Background()
storeClient *store.Store
verbose bool
currentSnaps []*store.CurrentSnap
requiredSnaps map[string]bool
processedSnaps = make(map[string]bool)
snapSizeMap = make(map[string]float64)
totalSnapSize float64
)
type SnapInfo struct {
InstanceName string
SnapID string
Revision snap.Revision
}
func main() {
// Override the default plug slot sanitizer
snap.SanitizePlugsSlots = sanitizePlugsSlots
// Initialize progress reporting
InitProgress()
totalSnapSize = 0
// Initialize the store client
storeClient = store.New(nil, nil)
// Parse command-line flags
var seedDirectory string
flag.StringVar(&seedDirectory, "seed", "/var/lib/snapd/seed", "Specify the seed directory")
flag.BoolVar(&verbose, "verbose", false, "Enable verbose output")
flag.Parse()
if !verbose {
fmt.Printf("2\tLoading existing snaps...\n")
}
// Define directories based on the seed directory
snapsDir := filepath.Join(seedDirectory, "snaps")
assertionsDir := filepath.Join(seedDirectory, "assertions")
seedYaml := filepath.Join(seedDirectory, "seed.yaml")
// Setup directories and seed.yaml
initializeDirectories(snapsDir, assertionsDir)
initializeSeedYaml(seedYaml)
// Load existing snaps from seed.yaml
existingSnapsInYaml := loadExistingSnaps(seedYaml)
// Populate currentSnaps based on existing snaps
for snapName := range existingSnapsInYaml {
snapInfo, err := getCurrentSnapInfo(assertionsDir, snapName)
if err != nil {
verboseLog("Failed to get info for existing snap %s: %v", snapName, err)
continue
}
currentSnaps = append(currentSnaps, snapInfo)
}
// Process essential snaps
requiredSnaps = map[string]bool{"snapd": true, "bare": true}
for _, arg := range flag.Args() {
requiredSnaps[arg] = true
}
if !verbose {
fmt.Printf("4\tFetching information from the Snap Store...\n")
}
// Collect snaps to process
snapsToProcess, err := collectSnapsToProcess(snapsDir, assertionsDir)
if err != nil {
log.Fatalf("Failed to collect snaps to process: %v", err)
}
progressTracker.Finish("Finished collecting snap info")
// Calculate the number of snaps to download
totalSnaps := len(snapsToProcess)
if totalSnaps == 0 {
verboseLog("No snaps to process.")
} else {
verboseLog("Total snaps to download: %d", totalSnaps)
}
// Initialize variables to track download progress
completedSnaps := 0
// Update "Downloading snaps" step to 0%
progressTracker.UpdateStepProgress(0)
// Process all the snaps that need updates
for _, snapDetails := range snapsToProcess {
if err := processSnap(snapDetails, snapsDir, assertionsDir); err != nil {
log.Fatalf("Failed to process snap %s: %v", snapDetails.InstanceName, err)
}
completedSnaps++
progressTracker.UpdateStepProgress(-1)
}
// Mark "Downloading snaps" as complete
if totalSnaps > 0 {
progressTracker.Finish("Downloading snaps completed")
} else {
// If no snaps to download, skip to finalizing
progressTracker.NextStep()
}
// Remove unnecessary snaps after processing dependencies
cleanUpCurrentSnaps(assertionsDir, snapsDir)
// Update seed.yaml with the current required snaps
if err := updateSeedYaml(snapsDir, seedYaml, currentSnaps); err != nil {
log.Fatalf("Failed to update seed.yaml: %v", err)
}
// Perform cleanup and validation tasks
removeStateJson(filepath.Join(seedDirectory, "..", "state.json"))
ensureAssertions(assertionsDir)
if err := validateSeed(seedYaml); err != nil {
log.Fatalf("Seed validation failed: %v", err)
}
cleanUpFiles(snapsDir, assertionsDir, seedYaml)
// Mark "Finalizing" as complete
if progressTracker != nil {
progressTracker.Finish("Cleanup and validation completed")
}
}
// collectSnapsToProcess collects all snaps and their dependencies, returning only those that need updates
func collectSnapsToProcess(snapsDir, assertionsDir string) ([]SnapDetails, error) {
var snapsToProcess []SnapDetails
fallbackChannel := "latest/stable"
for snapEntry := range requiredSnaps {
// Extract channel if specified, default to "stable"
parts := strings.SplitN(snapEntry, "=", 2)
channel := "latest/stable/ubuntu-25.04"
if len(parts) == 2 {
channel = parts[1]
}
snapName := parts[0]
// Collect snap dependencies and their statuses
snapList, err := collectSnapDependencies(snapName, channel, fallbackChannel, snapsDir, assertionsDir)
if err != nil {
return nil, err
}
// Append only those snaps that need updates
for _, snapDetails := range snapList {
verboseLog("Processing snap: %s with result: %v", snapDetails.InstanceName, snapDetails.Result)
if len(snapDetails.Result.Deltas) > 0 {
for _, delta := range snapDetails.Result.Deltas {
snapSize := float64(delta.Size)
snapSizeMap[snapDetails.Result.Info.SuggestedName] = snapSize
totalSnapSize += snapSize
}
} else {
snapSize := float64(snapDetails.Result.Info.Size)
snapSizeMap[snapDetails.Result.Info.SuggestedName] = snapSize
totalSnapSize += snapSize
}
snapsToProcess = append(snapsToProcess, snapDetails)
}
}
return snapsToProcess, nil
}
// sanitizePlugsSlots is a placeholder function to sanitize plug slots in snap.Info
func sanitizePlugsSlots(info *snap.Info) {}
// verboseLog logs messages only when verbose mode is enabled
func verboseLog(format string, v ...interface{}) {
if verbose {
log.Printf(format, v...)
}
}