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.
snapd-extra-utils/snapd-seed-glue/main.go

219 lines
6.9 KiB

// Copyright (C) 2024 Simon Quigley <tsimonq2@ubuntu.com>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 3
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
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...)
}
}