|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"flag"
|
|
|
|
"log"
|
|
|
|
"fmt"
|
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/snapcore/snapd/snap"
|
|
|
|
"github.com/snapcore/snapd/store"
|
|
|
|
)
|
|
|
|
|
|
|
|
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
|
|
|
|
seedYaml string
|
|
|
|
)
|
|
|
|
|
|
|
|
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()
|
|
|
|
|
|
|
|
// Load existing snaps from seed.yaml
|
|
|
|
existingSnapsInYaml := loadExistingSnaps()
|
|
|
|
|
|
|
|
// 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, 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)
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
|
|
|
versionID, err := getVersionID()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
defaultChannel := "latest/stable/ubuntu-" + versionID
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
fallbackChannel := "latest/stable"
|
|
|
|
for snapEntry := range requiredSnaps {
|
|
|
|
// Extract channel if specified, default to "stable"
|
|
|
|
parts := strings.SplitN(snapEntry, "=", 2)
|
|
|
|
channel := defaultChannel
|
|
|
|
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", snapDetails.InstanceName)
|
|
|
|
if len(snapDetails.Result.Deltas) > 0 {
|
|
|
|
for _, delta := range snapDetails.Result.Deltas {
|
|
|
|
verboseLog("Delta found for %s from %d to %d", snapDetails.InstanceName, delta.FromRevision, delta.ToRevision)
|
|
|
|
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...)
|
|
|
|
}
|
|
|
|
}
|