Fix bad tabbing, default to the 25.04 branch

ubuntu/plucky
Simon Quigley 1 week ago
parent e57ad37808
commit 4d6176bc13

@ -1,131 +1,131 @@
package main package main
import ( import (
"encoding/base64" "encoding/base64"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"log" "log"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"time" "time"
"github.com/snapcore/snapd/asserts" "github.com/snapcore/snapd/asserts"
"github.com/snapcore/snapd/snap" "github.com/snapcore/snapd/snap"
"github.com/snapcore/snapd/store" "github.com/snapcore/snapd/store"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
// downloadAssertions dynamically fetches the necessary assertions and saves them to a file. // downloadAssertions dynamically fetches the necessary assertions and saves them to a file.
// It ensures that the account-key assertion is written first in the .assert file. // It ensures that the account-key assertion is written first in the .assert file.
func downloadAssertions(storeClient *store.Store, snapInfo *snap.Info, downloadDir string) error { func downloadAssertions(storeClient *store.Store, snapInfo *snap.Info, downloadDir string) error {
// Define the path for the assertions file // Define the path for the assertions file
assertionsPath := filepath.Join(downloadDir, fmt.Sprintf("%s_%d.assert", snapInfo.SuggestedName, snapInfo.Revision.N)) assertionsPath := filepath.Join(downloadDir, fmt.Sprintf("%s_%d.assert", snapInfo.SuggestedName, snapInfo.Revision.N))
// Extract necessary fields from snapInfo // Extract necessary fields from snapInfo
snapSHA := snapInfo.Sha3_384 snapSHA := snapInfo.Sha3_384
snapID := snapInfo.SnapID snapID := snapInfo.SnapID
publisherID := snapInfo.Publisher.ID publisherID := snapInfo.Publisher.ID
series := "16" // Consider making this dynamic if possible series := "16" // Consider making this dynamic if possible
// Define assertion types // Define assertion types
assertionTypes := map[string]*asserts.AssertionType{ assertionTypes := map[string]*asserts.AssertionType{
"snap-revision": asserts.SnapRevisionType, "snap-revision": asserts.SnapRevisionType,
"snap-declaration": asserts.SnapDeclarationType, "snap-declaration": asserts.SnapDeclarationType,
"account-key": asserts.AccountKeyType, "account-key": asserts.AccountKeyType,
"account": asserts.AccountType, "account": asserts.AccountType,
} }
// Open the assertions file for writing // Open the assertions file for writing
assertionsFile, err := os.Create(assertionsPath) assertionsFile, err := os.Create(assertionsPath)
if err != nil { if err != nil {
return fmt.Errorf("failed to create assertions file: %w", err) return fmt.Errorf("failed to create assertions file: %w", err)
} }
defer assertionsFile.Close() defer assertionsFile.Close()
// Step 1: Fetch snap-declaration assertion // Step 1: Fetch snap-declaration assertion
snapDecl, err := storeClient.Assertion(assertionTypes["snap-declaration"], []string{series, snapID}, nil) snapDecl, err := storeClient.Assertion(assertionTypes["snap-declaration"], []string{series, snapID}, nil)
if err != nil { if err != nil {
return fmt.Errorf("failed to fetch snap-declaration assertion for snap %s: %w", snapInfo.SuggestedName, err) return fmt.Errorf("failed to fetch snap-declaration assertion for snap %s: %w", snapInfo.SuggestedName, err)
} }
// Step 2: Extract sign-key-sha3-384 from snap-declaration // Step 2: Extract sign-key-sha3-384 from snap-declaration
signKey, ok := snapDecl.Header("sign-key-sha3-384").(string) signKey, ok := snapDecl.Header("sign-key-sha3-384").(string)
if !ok || signKey == "" { if !ok || signKey == "" {
return fmt.Errorf("snap-declaration assertion missing 'sign-key-sha3-384' header for snap %s", snapInfo.SuggestedName) return fmt.Errorf("snap-declaration assertion missing 'sign-key-sha3-384' header for snap %s", snapInfo.SuggestedName)
} }
// Step 3: Fetch account-key assertion using sign-key-sha3-384 (no decoding) // Step 3: Fetch account-key assertion using sign-key-sha3-384 (no decoding)
accountKeyAssertion, err := storeClient.Assertion(assertionTypes["account-key"], []string{signKey}, nil) accountKeyAssertion, err := storeClient.Assertion(assertionTypes["account-key"], []string{signKey}, nil)
if err != nil { if err != nil {
return fmt.Errorf("failed to fetch account-key assertion for snap %s: %w", snapInfo.SuggestedName, err) return fmt.Errorf("failed to fetch account-key assertion for snap %s: %w", snapInfo.SuggestedName, err)
} }
// Step 4: Fetch account assertion using publisher-id // Step 4: Fetch account assertion using publisher-id
accountAssertion, err := storeClient.Assertion(assertionTypes["account"], []string{publisherID}, nil) accountAssertion, err := storeClient.Assertion(assertionTypes["account"], []string{publisherID}, nil)
if err != nil { if err != nil {
return fmt.Errorf("failed to fetch account assertion for snap %s: %w", snapInfo.SuggestedName, err) return fmt.Errorf("failed to fetch account assertion for snap %s: %w", snapInfo.SuggestedName, err)
} }
// Step 5: Fetch snap-revision assertion // Step 5: Fetch snap-revision assertion
snapSHA384Bytes, err := hex.DecodeString(snapSHA) snapSHA384Bytes, err := hex.DecodeString(snapSHA)
if err != nil { if err != nil {
return fmt.Errorf("error decoding SHA3-384 hex string for snap %s: %w", snapInfo.SuggestedName, err) return fmt.Errorf("error decoding SHA3-384 hex string for snap %s: %w", snapInfo.SuggestedName, err)
} }
snapSHA384Base64 := base64.RawURLEncoding.EncodeToString(snapSHA384Bytes) snapSHA384Base64 := base64.RawURLEncoding.EncodeToString(snapSHA384Bytes)
//revisionKey := fmt.Sprintf("%s/global-upload", snapSHA384Base64) //revisionKey := fmt.Sprintf("%s/global-upload", snapSHA384Base64)
revisionKey := fmt.Sprintf("%s/", snapSHA384Base64) revisionKey := fmt.Sprintf("%s/", snapSHA384Base64)
snapRevisionAssertion, err := storeClient.Assertion(assertionTypes["snap-revision"], []string{revisionKey}, nil) snapRevisionAssertion, err := storeClient.Assertion(assertionTypes["snap-revision"], []string{revisionKey}, nil)
if err != nil { if err != nil {
verboseLog("Failed to fetch snap-revision assertion for snap %s: %v", snapInfo.SuggestedName, err) verboseLog("Failed to fetch snap-revision assertion for snap %s: %v", snapInfo.SuggestedName, err)
// Proceeding without snap-revision might be acceptable based on your use-case // Proceeding without snap-revision might be acceptable based on your use-case
} }
// Step 6: Write assertions in the desired order // Step 6: Write assertions in the desired order
// 1. account-key // 1. account-key
writeAssertion("account-key", accountKeyAssertion, assertionsFile) writeAssertion("account-key", accountKeyAssertion, assertionsFile)
// 2. account // 2. account
writeAssertion("account", accountAssertion, assertionsFile) writeAssertion("account", accountAssertion, assertionsFile)
// 3. snap-declaration // 3. snap-declaration
writeAssertion("snap-declaration", snapDecl, assertionsFile) writeAssertion("snap-declaration", snapDecl, assertionsFile)
// 4. snap-revision (if fetched successfully) // 4. snap-revision (if fetched successfully)
if snapRevisionAssertion != nil { if snapRevisionAssertion != nil {
writeAssertion("snap-revision", snapRevisionAssertion, assertionsFile) writeAssertion("snap-revision", snapRevisionAssertion, assertionsFile)
} }
verboseLog("Assertions downloaded and saved to: %s", assertionsPath) verboseLog("Assertions downloaded and saved to: %s", assertionsPath)
return nil return nil
} }
func writeAssertion(assertionType string, assertion asserts.Assertion, file *os.File) { func writeAssertion(assertionType string, assertion asserts.Assertion, file *os.File) {
fieldOrder := map[string][]string{ fieldOrder := map[string][]string{
"account-key": { "account-key": {
"type", "authority-id", "revision", "public-key-sha3-384", "type", "authority-id", "revision", "public-key-sha3-384",
"account-id", "name", "since", "body-length", "sign-key-sha3-384", "account-id", "name", "since", "body-length", "sign-key-sha3-384",
}, },
"account": { "account": {
"type", "authority-id", "revision", "account-id", "display-name", "type", "authority-id", "revision", "account-id", "display-name",
"timestamp", "username", "validation", "sign-key-sha3-384", "timestamp", "username", "validation", "sign-key-sha3-384",
}, },
"snap-declaration": { "snap-declaration": {
"type", "format", "authority-id", "revision", "series", "snap-id", "type", "format", "authority-id", "revision", "series", "snap-id",
"aliases", "auto-aliases", "plugs", "publisher-id", "slots", "aliases", "auto-aliases", "plugs", "publisher-id", "slots",
"snap-name", "timestamp", "sign-key-sha3-384", "snap-name", "timestamp", "sign-key-sha3-384",
}, },
"snap-revision": { "snap-revision": {
"type", "authority-id", "snap-sha3-384", "developer-id", "type", "authority-id", "snap-sha3-384", "developer-id",
"provenance", "snap-id", "snap-revision", "snap-size", "provenance", "snap-id", "snap-revision", "snap-size",
"timestamp", "sign-key-sha3-384", "timestamp", "sign-key-sha3-384",
}, },
} }
body := assertion.Body() body := assertion.Body()
bodyLength := len(body) bodyLength := len(body)
headers := assertion.Headers() headers := assertion.Headers()
// Only write the account assertion if it is not Canonical // Only write the account assertion if it is not Canonical
if assertionType == "account" { if assertionType == "account" {
@ -147,60 +147,60 @@ func writeAssertion(assertionType string, assertion asserts.Assertion, file *os.
} }
} }
// Write headers in the specified order // Write headers in the specified order
for _, key := range fieldOrder[assertionType] { for _, key := range fieldOrder[assertionType] {
value, exists := headers[key] value, exists := headers[key]
if !exists || value == "" { if !exists || value == "" {
continue continue
} }
if key == "type" { if key == "type" {
fmt.Fprintf(file, "%s: %s\n", key, assertionType) fmt.Fprintf(file, "%s: %s\n", key, assertionType)
} else if key == "body-length" && bodyLength > 0 { } else if key == "body-length" && bodyLength > 0 {
file.WriteString(fmt.Sprintf("body-length: %d\n", bodyLength)) file.WriteString(fmt.Sprintf("body-length: %d\n", bodyLength))
continue continue
} else if isComplexField(key) { } else if isComplexField(key) {
fmt.Fprintf(file, "%s:\n", key) fmt.Fprintf(file, "%s:\n", key)
serializeComplexField(value, file) serializeComplexField(value, file)
} else { } else {
fmt.Fprintf(file, "%s: %s\n", key, value) fmt.Fprintf(file, "%s: %s\n", key, value)
} }
} }
file.WriteString("\n") file.WriteString("\n")
// Write the body if it exists // Write the body if it exists
if bodyLength > 0 { if bodyLength > 0 {
file.Write(body) file.Write(body)
file.WriteString("\n\n") file.WriteString("\n\n")
} }
// Write the signature // Write the signature
_, signature := assertion.Signature() _, signature := assertion.Signature()
file.Write(signature) file.Write(signature)
if assertionType != "snap-revision" { if assertionType != "snap-revision" {
file.WriteString("\n") file.WriteString("\n")
} }
} }
func serializeComplexField(value interface{}, file *os.File) { func serializeComplexField(value interface{}, file *os.File) {
var buf strings.Builder var buf strings.Builder
encoder := yaml.NewEncoder(&buf) encoder := yaml.NewEncoder(&buf)
encoder.SetIndent(2) encoder.SetIndent(2)
defer encoder.Close() defer encoder.Close()
// Encode the value directly // Encode the value directly
if err := encoder.Encode(value); err != nil { if err := encoder.Encode(value); err != nil {
log.Fatalf("Error encoding YAML: %v", err) log.Fatalf("Error encoding YAML: %v", err)
} }
// Write the serialized YAML to the file with proper indentation // Write the serialized YAML to the file with proper indentation
lines := strings.Split(buf.String(), "\n") lines := strings.Split(buf.String(), "\n")
for _, line := range lines { for _, line := range lines {
if line == "" { if line == "" {
continue continue
} }
line = strings.ReplaceAll(line, `"true"`, "true") line = strings.ReplaceAll(line, `"true"`, "true")
line = strings.ReplaceAll(line, `"false"`, "false") line = strings.ReplaceAll(line, `"false"`, "false")
line = strings.ReplaceAll(line, `'*'`, "*") line = strings.ReplaceAll(line, `'*'`, "*")
// Check for dashes indicating list items // Check for dashes indicating list items
if strings.HasPrefix(strings.TrimSpace(line), "-") && strings.Contains(line, ":") { if strings.HasPrefix(strings.TrimSpace(line), "-") && strings.Contains(line, ":") {
before, after, found := strings.Cut(line, "- ") before, after, found := strings.Cut(line, "- ")
@ -216,10 +216,10 @@ func serializeComplexField(value interface{}, file *os.File) {
// For any other non-empty lines, indent them correctly // For any other non-empty lines, indent them correctly
file.WriteString(fmt.Sprintf(" %s\n", line)) file.WriteString(fmt.Sprintf(" %s\n", line))
} }
} }
} }
// isComplexField checks if a field is complex (nested) in YAML // isComplexField checks if a field is complex (nested) in YAML
func isComplexField(key string) bool { func isComplexField(key string) bool {
return key == "aliases" || key == "auto-aliases" || key == "plugs" || key == "slots" || key == "allow-installation" || key == "allow-connection" return key == "aliases" || key == "auto-aliases" || key == "plugs" || key == "slots" || key == "allow-installation" || key == "allow-connection"
} }

@ -1,152 +1,152 @@
package main package main
import ( import (
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/snapcore/snapd/store" "github.com/snapcore/snapd/store"
) )
// cleanUpFiles removes partial, old, and orphaned snap and assertion files from the download and assertions directories. // cleanUpFiles removes partial, old, and orphaned snap and assertion files from the download and assertions directories.
func cleanUpFiles(snapsDir, assertionsDir, seedYaml string) { func cleanUpFiles(snapsDir, assertionsDir, seedYaml string) {
verboseLog("Starting cleanup process...") verboseLog("Starting cleanup process...")
// Load the seed.yaml data // Load the seed.yaml data
seedData := loadSeedData(seedYaml) seedData := loadSeedData(seedYaml)
// Create a map of valid snap and assertion files based on seed.yaml // Create a map of valid snap and assertion files based on seed.yaml
validSnaps := make(map[string]bool) validSnaps := make(map[string]bool)
validAssertions := make(map[string]bool) validAssertions := make(map[string]bool)
// Populate valid snaps and assertions from the seed.yaml data // Populate valid snaps and assertions from the seed.yaml data
for _, snap := range seedData.Snaps { for _, snap := range seedData.Snaps {
// Ensure correct extraction of revision // Ensure correct extraction of revision
revision := extractRevisionFromFile(snap.File) revision := extractRevisionFromFile(snap.File)
if revision == "" { if revision == "" {
verboseLog("Failed to extract revision from file name: %s", snap.File) verboseLog("Failed to extract revision from file name: %s", snap.File)
continue continue
} }
snapFileName := fmt.Sprintf("%s_%s.snap", snap.Name, revision) snapFileName := fmt.Sprintf("%s_%s.snap", snap.Name, revision)
assertionFileName := fmt.Sprintf("%s_%s.assert", snap.Name, revision) assertionFileName := fmt.Sprintf("%s_%s.assert", snap.Name, revision)
validSnaps[snapFileName] = true validSnaps[snapFileName] = true
validAssertions[assertionFileName] = true validAssertions[assertionFileName] = true
} }
// Log valid snaps and assertions // Log valid snaps and assertions
verboseLog("Valid Snaps: %v", validSnaps) verboseLog("Valid Snaps: %v", validSnaps)
verboseLog("Valid Assertions: %v", validAssertions) verboseLog("Valid Assertions: %v", validAssertions)
// Remove outdated or partial snap files // Remove outdated or partial snap files
files, err := os.ReadDir(snapsDir) files, err := os.ReadDir(snapsDir)
if err != nil { if err != nil {
verboseLog("Error reading snaps directory for cleanup: %v", err) verboseLog("Error reading snaps directory for cleanup: %v", err)
} else { } else {
for _, file := range files { for _, file := range files {
filePath := filepath.Join(snapsDir, file.Name()) filePath := filepath.Join(snapsDir, file.Name())
if strings.HasSuffix(file.Name(), ".partial") || strings.HasSuffix(file.Name(), ".delta") { if strings.HasSuffix(file.Name(), ".partial") || strings.HasSuffix(file.Name(), ".delta") {
verboseLog("Removing partial/delta file: %s\n", filePath) verboseLog("Removing partial/delta file: %s\n", filePath)
if err := os.Remove(filePath); err != nil { if err := os.Remove(filePath); err != nil {
verboseLog("Failed to remove file %s: %v", filePath, err) verboseLog("Failed to remove file %s: %v", filePath, err)
} else if verbose { } else if verbose {
verboseLog("Removed partial/delta file: %s", filePath) verboseLog("Removed partial/delta file: %s", filePath)
} }
} else if strings.HasSuffix(file.Name(), ".snap") { } else if strings.HasSuffix(file.Name(), ".snap") {
if !validSnaps[file.Name()] { if !validSnaps[file.Name()] {
verboseLog("Removing outdated or orphaned snap file: %s\n", filePath) verboseLog("Removing outdated or orphaned snap file: %s\n", filePath)
if err := os.Remove(filePath); err != nil { if err := os.Remove(filePath); err != nil {
verboseLog("Failed to remove snap file %s: %v", filePath, err) verboseLog("Failed to remove snap file %s: %v", filePath, err)
} else if verbose { } else if verbose {
verboseLog("Removed snap file: %s", filePath) verboseLog("Removed snap file: %s", filePath)
} }
} else { } else {
verboseLog("Snap file %s is valid and retained.\n", file.Name()) verboseLog("Snap file %s is valid and retained.\n", file.Name())
} }
} }
} }
} }
// Remove orphaned assertion files // Remove orphaned assertion files
files, err = os.ReadDir(assertionsDir) files, err = os.ReadDir(assertionsDir)
if err != nil { if err != nil {
verboseLog("Error reading assertions directory for cleanup: %v", err) verboseLog("Error reading assertions directory for cleanup: %v", err)
} else { } else {
for _, file := range files { for _, file := range files {
filePath := filepath.Join(assertionsDir, file.Name()) filePath := filepath.Join(assertionsDir, file.Name())
if strings.HasSuffix(file.Name(), ".assert") { if strings.HasSuffix(file.Name(), ".assert") {
if !validAssertions[file.Name()] { if !validAssertions[file.Name()] {
verboseLog("Removing orphaned assertion file: %s\n", filePath) verboseLog("Removing orphaned assertion file: %s\n", filePath)
if err := os.Remove(filePath); err != nil { if err := os.Remove(filePath); err != nil {
verboseLog("Failed to remove assertion file %s: %v", filePath, err) verboseLog("Failed to remove assertion file %s: %v", filePath, err)
} else if verbose { } else if verbose {
verboseLog("Removed assertion file: %s", filePath) verboseLog("Removed assertion file: %s", filePath)
} }
} else { } else {
verboseLog("Assertion file %s is valid and retained.\n", file.Name()) verboseLog("Assertion file %s is valid and retained.\n", file.Name())
} }
} }
} }
} }
verboseLog("Cleanup process completed.") verboseLog("Cleanup process completed.")
} }
// removeOrphanedFiles deletes the assertion and snap file corresponding to the removed snap. // removeOrphanedFiles deletes the assertion and snap file corresponding to the removed snap.
func removeOrphanedFiles(snapName string, revision int, assertionsDir string, snapsDir string) { func removeOrphanedFiles(snapName string, revision int, assertionsDir string, snapsDir string) {
assertionFilePath := filepath.Join(assertionsDir, fmt.Sprintf("%s_%d.assert", snapName, revision)) assertionFilePath := filepath.Join(assertionsDir, fmt.Sprintf("%s_%d.assert", snapName, revision))
snapFilePath := filepath.Join(snapsDir, fmt.Sprintf("%s_%d.snap", snapName, revision)) snapFilePath := filepath.Join(snapsDir, fmt.Sprintf("%s_%d.snap", snapName, revision))
if fileExists(assertionFilePath) { if fileExists(assertionFilePath) {
err := os.Remove(assertionFilePath) err := os.Remove(assertionFilePath)
if err != nil { if err != nil {
verboseLog("Failed to remove assertion file %s: %v", assertionFilePath, err) verboseLog("Failed to remove assertion file %s: %v", assertionFilePath, err)
} else { } else {
verboseLog("Removed assertion file: %s", assertionFilePath) verboseLog("Removed assertion file: %s", assertionFilePath)
} }
} else { } else {
verboseLog("Assertion file %s does not exist. No action taken.", assertionFilePath) verboseLog("Assertion file %s does not exist. No action taken.", assertionFilePath)
} }
if fileExists(snapFilePath) { if fileExists(snapFilePath) {
err := os.Remove(snapFilePath) err := os.Remove(snapFilePath)
if err != nil { if err != nil {
verboseLog("Failed to remove snap file %s: %v", snapFilePath, err) verboseLog("Failed to remove snap file %s: %v", snapFilePath, err)
} else { } else {
verboseLog("Removed snap file: %s", snapFilePath) verboseLog("Removed snap file: %s", snapFilePath)
} }
} else { } else {
verboseLog("Snap file %s does not exist. No action taken.", snapFilePath) verboseLog("Snap file %s does not exist. No action taken.", snapFilePath)
} }
} }
// cleanUpCurrentSnaps removes snaps from currentSnaps that are not marked as required. // cleanUpCurrentSnaps removes snaps from currentSnaps that are not marked as required.
func cleanUpCurrentSnaps(assertionsDir string, snapsDir string) { func cleanUpCurrentSnaps(assertionsDir string, snapsDir string) {
var filteredSnaps []*store.CurrentSnap var filteredSnaps []*store.CurrentSnap
for _, snap := range currentSnaps { for _, snap := range currentSnaps {
if requiredSnaps[snap.InstanceName] { if requiredSnaps[snap.InstanceName] {
filteredSnaps = append(filteredSnaps, snap) filteredSnaps = append(filteredSnaps, snap)
} else { } else {
verboseLog("Removing unnecessary snap: %s\n", snap.InstanceName) verboseLog("Removing unnecessary snap: %s\n", snap.InstanceName)
removeOrphanedFiles(snap.InstanceName, snap.Revision.N, assertionsDir, snapsDir) removeOrphanedFiles(snap.InstanceName, snap.Revision.N, assertionsDir, snapsDir)
} }
} }
currentSnaps = filteredSnaps currentSnaps = filteredSnaps
// Log the updated currentSnaps // Log the updated currentSnaps
verboseLog("Filtered currentSnaps after cleanup:") verboseLog("Filtered currentSnaps after cleanup:")
for _, snap := range currentSnaps { for _, snap := range currentSnaps {
verboseLog("- %s_%d.snap", snap.InstanceName, snap.Revision.N) verboseLog("- %s_%d.snap", snap.InstanceName, snap.Revision.N)
} }
} }
// removeStateJson removes the state.json file if it exists // removeStateJson removes the state.json file if it exists
func removeStateJson(stateJsonPath string) { func removeStateJson(stateJsonPath string) {
if _, err := os.Stat(stateJsonPath); err == nil { if _, err := os.Stat(stateJsonPath); err == nil {
if err := os.Remove(stateJsonPath); err != nil { if err := os.Remove(stateJsonPath); err != nil {
verboseLog("Failed to remove state.json: %v", err) verboseLog("Failed to remove state.json: %v", err)
} else if verbose { } else if verbose {
verboseLog("Removed state.json at %s", stateJsonPath) verboseLog("Removed state.json at %s", stateJsonPath)
} }
} }
} }

@ -1,152 +1,152 @@
package main package main
import ( import (
"fmt" "fmt"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"time" "time"
"github.com/snapcore/snapd/snap" "github.com/snapcore/snapd/snap"
"github.com/snapcore/snapd/store" "github.com/snapcore/snapd/store"
) )
// downloadSnap downloads a snap file with retry logic // downloadSnap downloads a snap file with retry logic
func downloadSnap(storeClient *store.Store, snapInfo *snap.Info, downloadPath string) error { func downloadSnap(storeClient *store.Store, snapInfo *snap.Info, downloadPath string) error {
downloadInfo := &snap.DownloadInfo{ downloadInfo := &snap.DownloadInfo{
DownloadURL: snapInfo.DownloadURL, DownloadURL: snapInfo.DownloadURL,
} }
pbar := NewProgressMeter(snapInfo.SuggestedName, snapInfo.Version, false) pbar := NewProgressMeter(snapInfo.SuggestedName, snapInfo.Version, false)
progressTracker.UpdateStepProgress(0) progressTracker.UpdateStepProgress(0)
for attempts := 1; attempts <= 5; attempts++ { for attempts := 1; attempts <= 5; attempts++ {
verboseLog("Attempt %d to download snap: %s", attempts, downloadPath) verboseLog("Attempt %d to download snap: %s", attempts, downloadPath)
err := storeClient.Download(ctx, snapInfo.SnapID, downloadPath, downloadInfo, pbar, nil, nil) err := storeClient.Download(ctx, snapInfo.SnapID, downloadPath, downloadInfo, pbar, nil, nil)
if err == nil { if err == nil {
pbar.Finished() pbar.Finished()
return nil // Successful download return nil // Successful download
} }
if verbose { if verbose {
verboseLog("Attempt %d to download %s failed: %v", attempts, snapInfo.SuggestedName, err) verboseLog("Attempt %d to download %s failed: %v", attempts, snapInfo.SuggestedName, err)
} else if progressTracker != nil { } else if progressTracker != nil {
progressTracker.UpdateStepProgress(0) progressTracker.UpdateStepProgress(0)
} }
if attempts == 5 { if attempts == 5 {
return fmt.Errorf("snap download failed after 5 attempts: %v", err) return fmt.Errorf("snap download failed after 5 attempts: %v", err)
} }
} }
return fmt.Errorf("snap download failed after 5 attempts") return fmt.Errorf("snap download failed after 5 attempts")
} }
// downloadSnapDeltaWithRetries downloads the delta file with retry logic and exponential backoff. // downloadSnapDeltaWithRetries downloads the delta file with retry logic and exponential backoff.
func downloadSnapDeltaWithRetries(storeClient *store.Store, delta *snap.DeltaInfo, result *store.SnapActionResult, deltaPath string, maxRetries int, snapName string) error { func downloadSnapDeltaWithRetries(storeClient *store.Store, delta *snap.DeltaInfo, result *store.SnapActionResult, deltaPath string, maxRetries int, snapName string) error {
if !verbose { if !verbose {
verboseLog("Downloading delta for %s", snapName) verboseLog("Downloading delta for %s", snapName)
} }
var lastErr error var lastErr error
backoff := 1 * time.Second backoff := 1 * time.Second
for attempts := 1; attempts <= maxRetries; attempts++ { for attempts := 1; attempts <= maxRetries; attempts++ {
verboseLog("Attempt %d to download delta: %s", attempts, deltaPath) verboseLog("Attempt %d to download delta: %s", attempts, deltaPath)
err := downloadSnapDelta(storeClient, delta, result, deltaPath) err := downloadSnapDelta(storeClient, delta, result, deltaPath)
if err == nil { if err == nil {
return nil return nil
} }
lastErr = err lastErr = err
verboseLog("Attempt %d to download delta failed: %v", attempts, err) verboseLog("Attempt %d to download delta failed: %v", attempts, err)
time.Sleep(backoff) time.Sleep(backoff)
backoff *= 2 backoff *= 2
} }
return fmt.Errorf("delta download failed after %d attempts: %v", maxRetries, lastErr) return fmt.Errorf("delta download failed after %d attempts: %v", maxRetries, lastErr)
} }
// downloadSnapDelta downloads the delta file. // downloadSnapDelta downloads the delta file.
func downloadSnapDelta(storeClient *store.Store, delta *snap.DeltaInfo, result *store.SnapActionResult, deltaPath string) error { func downloadSnapDelta(storeClient *store.Store, delta *snap.DeltaInfo, result *store.SnapActionResult, deltaPath string) error {
verboseLog("Downloading delta from revision %d to %d from: %s", delta.FromRevision, delta.ToRevision, delta.DownloadURL) verboseLog("Downloading delta from revision %d to %d from: %s", delta.FromRevision, delta.ToRevision, delta.DownloadURL)
downloadInfo := &snap.DownloadInfo{ downloadInfo := &snap.DownloadInfo{
DownloadURL: delta.DownloadURL, DownloadURL: delta.DownloadURL,
Size: delta.Size, Size: delta.Size,
Sha3_384: delta.Sha3_384, Sha3_384: delta.Sha3_384,
} }
// Use the SnapID from the associated SnapActionResult's Info // Use the SnapID from the associated SnapActionResult's Info
snapID := result.Info.SnapID snapID := result.Info.SnapID
pbar := NewProgressMeter(result.Info.SuggestedName, result.Info.Version, true) pbar := NewProgressMeter(result.Info.SuggestedName, result.Info.Version, true)
progressTracker.UpdateStepProgress(0) progressTracker.UpdateStepProgress(0)
// Download the delta file // Download the delta file
if err := storeClient.Download(ctx, snapID, deltaPath, downloadInfo, pbar, nil, nil); err != nil { if err := storeClient.Download(ctx, snapID, deltaPath, downloadInfo, pbar, nil, nil); err != nil {
progressTracker.UpdateStepProgress(0) progressTracker.UpdateStepProgress(0)
return fmt.Errorf("delta download failed: %v", err) return fmt.Errorf("delta download failed: %v", err)
} }
verboseLog("Downloaded %s to %s", delta.DownloadURL, deltaPath) verboseLog("Downloaded %s to %s", delta.DownloadURL, deltaPath)
pbar.Finished() pbar.Finished()
return nil return nil
} }
// downloadAndApplySnap handles the downloading and delta application process. // downloadAndApplySnap handles the downloading and delta application process.
// It returns the snap information and an error if any. // It returns the snap information and an error if any.
func downloadAndApplySnap(storeClient *store.Store, result *store.SnapActionResult, snapsDir, assertionsDir string, currentSnap *store.CurrentSnap) (*snap.Info, error) { func downloadAndApplySnap(storeClient *store.Store, result *store.SnapActionResult, snapsDir, assertionsDir string, currentSnap *store.CurrentSnap) (*snap.Info, error) {
if result == nil || result.Info == nil { if result == nil || result.Info == nil {
verboseLog("No updates available for snap. Skipping download and assertions.") verboseLog("No updates available for snap. Skipping download and assertions.")
return nil, nil return nil, nil
} }
snapInfo := result.Info snapInfo := result.Info
downloadPath := filepath.Join(snapsDir, fmt.Sprintf("%s_%d.snap", snapInfo.SuggestedName, snapInfo.Revision.N)) downloadPath := filepath.Join(snapsDir, fmt.Sprintf("%s_%d.snap", snapInfo.SuggestedName, snapInfo.Revision.N))
// Check if a delta can be applied // Check if a delta can be applied
if currentSnap != nil && len(result.Deltas) > 0 { if currentSnap != nil && len(result.Deltas) > 0 {
for _, delta := range result.Deltas { for _, delta := range result.Deltas {
deltaPath := filepath.Join(snapsDir, fmt.Sprintf("%s_%d_to_%d.delta", snapInfo.SuggestedName, delta.FromRevision, delta.ToRevision)) deltaPath := filepath.Join(snapsDir, fmt.Sprintf("%s_%d_to_%d.delta", snapInfo.SuggestedName, delta.FromRevision, delta.ToRevision))
if err := downloadSnapDeltaWithRetries(storeClient, &delta, result, deltaPath, 5, snapInfo.SuggestedName); err == nil { if err := downloadSnapDeltaWithRetries(storeClient, &delta, result, deltaPath, 5, snapInfo.SuggestedName); err == nil {
oldSnapPath := filepath.Join(snapsDir, fmt.Sprintf("%s_%d.snap", snapInfo.SuggestedName, delta.FromRevision)) oldSnapPath := filepath.Join(snapsDir, fmt.Sprintf("%s_%d.snap", snapInfo.SuggestedName, delta.FromRevision))
if fileExists(oldSnapPath) { if fileExists(oldSnapPath) {
if err := applyDelta(oldSnapPath, deltaPath, downloadPath); err == nil { if err := applyDelta(oldSnapPath, deltaPath, downloadPath); err == nil {
verboseLog("Delta applied successfully for snap %s", snapInfo.SuggestedName) verboseLog("Delta applied successfully for snap %s", snapInfo.SuggestedName)
// Download assertions after successful snap download // Download assertions after successful snap download
if err := downloadAssertions(storeClient, snapInfo, assertionsDir); err != nil { if err := downloadAssertions(storeClient, snapInfo, assertionsDir); err != nil {
return nil, fmt.Errorf("failed to download assertions for snap %s: %w", snapInfo.SuggestedName, err) return nil, fmt.Errorf("failed to download assertions for snap %s: %w", snapInfo.SuggestedName, err)
} }
return snapInfo, nil // Successful delta application return snapInfo, nil // Successful delta application
} else { } else {
verboseLog("Failed to apply delta for snap %s: %v", snapInfo.SuggestedName, err) verboseLog("Failed to apply delta for snap %s: %v", snapInfo.SuggestedName, err)
} }
} else { } else {
verboseLog("Old snap file %s does not exist. Cannot apply delta.", oldSnapPath) verboseLog("Old snap file %s does not exist. Cannot apply delta.", oldSnapPath)
} }
} else { } else {
verboseLog("Attempt to download delta for snap %s failed: %v", snapInfo.SuggestedName, err) verboseLog("Attempt to download delta for snap %s failed: %v", snapInfo.SuggestedName, err)
} }
} }
} }
// If no delta was applied or no deltas are available, fallback to downloading the full snap // If no delta was applied or no deltas are available, fallback to downloading the full snap
if err := downloadSnap(storeClient, snapInfo, downloadPath); err != nil { if err := downloadSnap(storeClient, snapInfo, downloadPath); err != nil {
return nil, fmt.Errorf("failed to download snap %s: %w", snapInfo.SuggestedName, err) return nil, fmt.Errorf("failed to download snap %s: %w", snapInfo.SuggestedName, err)
} }
// Download assertions after successful snap download // Download assertions after successful snap download
if err := downloadAssertions(storeClient, snapInfo, assertionsDir); err != nil { if err := downloadAssertions(storeClient, snapInfo, assertionsDir); err != nil {
return nil, fmt.Errorf("failed to download assertions for snap %s: %w", snapInfo.SuggestedName, err) return nil, fmt.Errorf("failed to download assertions for snap %s: %w", snapInfo.SuggestedName, err)
} }
verboseLog("Downloaded and applied snap: %s, revision: %d", snapInfo.SuggestedName, snapInfo.Revision.N) verboseLog("Downloaded and applied snap: %s, revision: %d", snapInfo.SuggestedName, snapInfo.Revision.N)
return snapInfo, nil return snapInfo, nil
} }
// applyDelta applies the downloaded delta using xdelta3. // applyDelta applies the downloaded delta using xdelta3.
func applyDelta(oldSnapPath, deltaPath, newSnapPath string) error { func applyDelta(oldSnapPath, deltaPath, newSnapPath string) error {
verboseLog("Applying delta from %s to %s using %s", oldSnapPath, newSnapPath, deltaPath) verboseLog("Applying delta from %s to %s using %s", oldSnapPath, newSnapPath, deltaPath)
cmd := exec.Command("xdelta3", "-d", "-s", oldSnapPath, deltaPath, newSnapPath) cmd := exec.Command("xdelta3", "-d", "-s", oldSnapPath, deltaPath, newSnapPath)
output, err := cmd.CombinedOutput() output, err := cmd.CombinedOutput()
if err != nil { if err != nil {
verboseLog("xdelta3 output: %s", string(output)) verboseLog("xdelta3 output: %s", string(output))
return fmt.Errorf("failed to apply delta: %v - %s", err, string(output)) return fmt.Errorf("failed to apply delta: %v - %s", err, string(output))
} }
return nil return nil
} }

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

@ -1,220 +1,243 @@
package main package main
import ( import (
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings" "strings"
"github.com/snapcore/snapd/snap" "github.com/snapcore/snapd/snap"
"github.com/snapcore/snapd/store" "github.com/snapcore/snapd/store"
) )
// SnapDetails struct remains unchanged // SnapDetails struct remains unchanged
type SnapDetails struct { type SnapDetails struct {
InstanceName string InstanceName string
Channel string Channel string
CurrentSnap *store.CurrentSnap CurrentSnap *store.CurrentSnap
Result *store.SnapActionResult Result *store.SnapActionResult
} }
// collectSnapDependencies collects all dependencies for a given snap, marking them as requiredSnaps regardless of whether they need updates. // collectSnapDependencies collects all dependencies for a given snap, marking them as requiredSnaps regardless of whether they need updates.
func collectSnapDependencies(snapName, channel, snapsDir, assertionsDir string) ([]SnapDetails, error) { func collectSnapDependencies(snapName, channel, fallbackChannel, snapsDir, assertionsDir string) ([]SnapDetails, error) {
var snapDetailsList []SnapDetails var snapDetailsList []SnapDetails
if processedSnaps[snapName] { if processedSnaps[snapName] {
verboseLog("Snap %s has already been processed. Skipping.", snapName) verboseLog("Snap %s has already been processed. Skipping.", snapName)
return snapDetailsList, nil // Added 0 for int64 return snapDetailsList, nil // Added 0 for int64
} }
oldSnapPath, oldSnap := findPreviousSnap(snapsDir, assertionsDir, snapName) oldSnapPath, oldSnap := findPreviousSnap(snapsDir, assertionsDir, snapName)
var result *store.SnapActionResult var result *store.SnapActionResult
var err error var err error
// Fetch or refresh snap information // Fetch or refresh snap information
if oldSnap == nil || oldSnap.SnapID == "" || oldSnap.Revision.N == 0 { if oldSnap == nil || oldSnap.SnapID == "" || oldSnap.Revision.N == 0 {
result, err = fetchOrRefreshSnapInfo(snapName, nil, channel) result, err = fetchOrRefreshSnapInfo(snapName, nil, channel)
if err != nil { if err != nil {
return nil, err // Added 0 for int64 if strings.Contains(err.Error(), "no snap revision available as specified") {
} result, err = fetchOrRefreshSnapInfo(snapName, nil, fallbackChannel)
} else { if err != nil {
result, err = fetchOrRefreshSnapInfo(snapName, oldSnap, channel) return nil, err
if err != nil && strings.Contains(err.Error(), "snap has no updates available") { }
result, err = fetchOrRefreshSnapInfo(snapName, nil, channel) } else {
if err != nil { return nil, err
return nil, err // Added 0 for int64 }
} }
} } else {
} result, err = fetchOrRefreshSnapInfo(snapName, oldSnap, channel)
if err != nil {
if result == nil || result.Info == nil || result.Info.SnapID == "" || result.Info.Revision.N == 0 { if strings.Contains(err.Error(), "snap has no updates available") {
return nil, fmt.Errorf("invalid snap information returned for %s: SnapID or Revision is missing", snapName) // Added 0 for int64 result, err = fetchOrRefreshSnapInfo(snapName, nil, channel)
} if err != nil {
return nil, err
info := result.Info }
newSnap := &store.CurrentSnap{ } else if strings.Contains(err.Error(), "no snap revision available as specified") {
InstanceName: snapName, result, err = fetchOrRefreshSnapInfo(snapName, oldSnap, fallbackChannel)
SnapID: info.SnapID, if err != nil {
Revision: snap.Revision{N: info.Revision.N}, if strings.Contains(err.Error(), "snap has no updates available") {
} result, err = fetchOrRefreshSnapInfo(snapName, nil, fallbackChannel)
if err != nil {
return nil, err
}
} else {
return nil, err
}
}
} else {
return nil, err
}
}
}
if result == nil || result.Info == nil || result.Info.SnapID == "" || result.Info.Revision.N == 0 {
return nil, fmt.Errorf("invalid snap information returned for %s: SnapID or Revision is missing", snapName) // Added 0 for int64
}
info := result.Info
newSnap := &store.CurrentSnap{
InstanceName: snapName,
SnapID: info.SnapID,
Revision: snap.Revision{N: info.Revision.N},
}
snapInCurrentSnaps, oldRevision := isSnapInCurrentSnaps(snapName) snapInCurrentSnaps, oldRevision := isSnapInCurrentSnaps(snapName)
if snapInCurrentSnaps { if snapInCurrentSnaps {
removeSnapFromCurrentSnaps(snapName, oldRevision) removeSnapFromCurrentSnaps(snapName, oldRevision)
} }
currentSnaps = append(currentSnaps, newSnap) currentSnaps = append(currentSnaps, newSnap)
processedSnaps[snapName] = true processedSnaps[snapName] = true
needsUpdate := (oldSnapPath == "" || oldSnap.Revision.N < info.Revision.N) needsUpdate := (oldSnapPath == "" || oldSnap.Revision.N < info.Revision.N)
if needsUpdate { if needsUpdate {
snapDetailsList = append(snapDetailsList, SnapDetails{ snapDetailsList = append(snapDetailsList, SnapDetails{
InstanceName: snapName, InstanceName: snapName,
Channel: channel, Channel: channel,
CurrentSnap: newSnap, CurrentSnap: newSnap,
Result: result, Result: result,
}) })
} else { } else {
// Mark the snap as required even if no update is needed // Mark the snap as required even if no update is needed
requiredSnaps[snapName] = true requiredSnaps[snapName] = true
} }
// Safely handle dependencies // Safely handle dependencies
tracker := snap.SimplePrereqTracker{} tracker := snap.SimplePrereqTracker{}
missingPrereqs := tracker.MissingProviderContentTags(info, nil) missingPrereqs := tracker.MissingProviderContentTags(info, nil)
for prereq := range missingPrereqs { for prereq := range missingPrereqs {
if !processedSnaps[prereq] { if !processedSnaps[prereq] {
verboseLog("Collecting dependencies for prerequisite snap: %s for %s", prereq, snapName) verboseLog("Collecting dependencies for prerequisite snap: %s for %s", prereq, snapName)
prereqDetails, err := collectSnapDependencies(prereq, "stable", snapsDir, assertionsDir) prereqDetails, err := collectSnapDependencies(prereq, channel, fallbackChannel, snapsDir, assertionsDir)
if err != nil { if err != nil {
// Additional logging for dependency resolution issues // Additional logging for dependency resolution issues
verboseLog("Failed to collect dependencies for prerequisite %s for snap %s: %v", prereq, snapName, err) verboseLog("Failed to collect dependencies for prerequisite %s for snap %s: %v", prereq, snapName, err)
return nil, fmt.Errorf("failed to collect dependencies for prerequisite %s for snap %s: %v", prereq, snapName, err) // Added 0 for int64 return nil, fmt.Errorf("failed to collect dependencies for prerequisite %s for snap %s: %v", prereq, snapName, err) // Added 0 for int64
} }
snapDetailsList = append(snapDetailsList, prereqDetails...) snapDetailsList = append(snapDetailsList, prereqDetails...)
} }
} }
// Also handle base snaps safely // Also handle base snaps safely
if info.Base != "" && !processedSnaps[info.Base] { if info.Base != "" && !processedSnaps[info.Base] {
verboseLog("Collecting dependencies for base snap: %s for %s", info.Base, snapName) verboseLog("Collecting dependencies for base snap: %s for %s", info.Base, snapName)
baseDetails, err := collectSnapDependencies(info.Base, "stable", snapsDir, assertionsDir) baseDetails, err := collectSnapDependencies(info.Base, channel, fallbackChannel, snapsDir, assertionsDir)
if err != nil { if err != nil {
verboseLog("Failed to collect dependencies for base snap %s for snap %s: %v", info.Base, snapName, err) verboseLog("Failed to collect dependencies for base snap %s for snap %s: %v", info.Base, snapName, err)
return nil, fmt.Errorf("failed to collect dependencies for base snap %s for snap %s: %v", info.Base, snapName, err) // Added 0 for int64 return nil, fmt.Errorf("failed to collect dependencies for base snap %s for snap %s: %v", info.Base, snapName, err) // Added 0 for int64
} }
snapDetailsList = append(snapDetailsList, baseDetails...) snapDetailsList = append(snapDetailsList, baseDetails...)
} }
return snapDetailsList, nil return snapDetailsList, nil
} }
// processSnap handles the downloading and applying of a snap if updates are available. // processSnap handles the downloading and applying of a snap if updates are available.
// It gracefully handles the "no updates available" scenario and ensures dependencies are marked as required. // It gracefully handles the "no updates available" scenario and ensures dependencies are marked as required.
func processSnap(snapDetails SnapDetails, snapsDir, assertionsDir string) error { func processSnap(snapDetails SnapDetails, snapsDir, assertionsDir string) error {
verboseLog("Processing snap: %s on channel: %s", snapDetails.InstanceName, snapDetails.Channel) verboseLog("Processing snap: %s on channel: %s", snapDetails.InstanceName, snapDetails.Channel)
// Proceed with downloading the snap (either full or delta) using downloadAndApplySnap // Proceed with downloading the snap (either full or delta) using downloadAndApplySnap
snapInfo, err := downloadAndApplySnap(storeClient, snapDetails.Result, snapsDir, assertionsDir, snapDetails.CurrentSnap) snapInfo, err := downloadAndApplySnap(storeClient, snapDetails.Result, snapsDir, assertionsDir, snapDetails.CurrentSnap)
if err != nil { if err != nil {
return fmt.Errorf("failed to download snap %s: %w", snapDetails.InstanceName, err) return fmt.Errorf("failed to download snap %s: %w", snapDetails.InstanceName, err)
} }
// Mark the snap as required after successful download and application // Mark the snap as required after successful download and application
requiredSnaps[snapDetails.InstanceName] = true requiredSnaps[snapDetails.InstanceName] = true
verboseLog("Downloaded and applied snap: %s, revision: %d", snapInfo.SuggestedName, snapInfo.Revision.N) verboseLog("Downloaded and applied snap: %s, revision: %d", snapInfo.SuggestedName, snapInfo.Revision.N)
return nil return nil
} }
// fetchOrRefreshSnapInfo retrieves snap information and returns the SnapActionResult including deltas. // fetchOrRefreshSnapInfo retrieves snap information and returns the SnapActionResult including deltas.
func fetchOrRefreshSnapInfo(snapName string, currentSnap *store.CurrentSnap, channel string) (*store.SnapActionResult, error) { func fetchOrRefreshSnapInfo(snapName string, currentSnap *store.CurrentSnap, channel string) (*store.SnapActionResult, error) {
var actions []*store.SnapAction var actions []*store.SnapAction
var includeSnap []*store.CurrentSnap var includeSnap []*store.CurrentSnap
if currentSnap != nil { if currentSnap != nil {
verboseLog("Crafting refresh SnapAction for %s", snapName) verboseLog("Crafting refresh SnapAction for %s", snapName)
actions = append(actions, &store.SnapAction{ actions = append(actions, &store.SnapAction{
Action: "refresh", Action: "refresh",
SnapID: currentSnap.SnapID, SnapID: currentSnap.SnapID,
InstanceName: snapName, InstanceName: snapName,
Channel: channel, Channel: channel,
}) })
includeSnap = []*store.CurrentSnap{currentSnap} includeSnap = []*store.CurrentSnap{currentSnap}
} else { } else {
verboseLog("Crafting install SnapAction for %s", snapName) verboseLog("Crafting install SnapAction for %s", snapName)
actions = append(actions, &store.SnapAction{ actions = append(actions, &store.SnapAction{
Action: "install", Action: "install",
InstanceName: snapName, InstanceName: snapName,
Channel: channel, Channel: channel,
}) })
includeSnap = []*store.CurrentSnap{} includeSnap = []*store.CurrentSnap{}
} }
results, _, err := storeClient.SnapAction(ctx, includeSnap, actions, nil, nil, nil) results, _, err := storeClient.SnapAction(ctx, includeSnap, actions, nil, nil, nil)
if err != nil { if err != nil {
verboseLog("SnapAction error for %s: %v", snapName, err) verboseLog("SnapAction error for %s: %v", snapName, err)
if strings.Contains(err.Error(), "snap has no updates available") && currentSnap != nil { if strings.Contains(err.Error(), "snap has no updates available") && currentSnap != nil {
return nil, err return nil, err
} }
return nil, fmt.Errorf("snap action failed for %s: %w", snapName, err) return nil, fmt.Errorf("snap action failed for %s: %w", snapName, err)
} }
if len(results) == 0 || results[0].Info == nil { if len(results) == 0 || results[0].Info == nil {
return nil, fmt.Errorf("no snap info returned for snap %s", snapName) return nil, fmt.Errorf("no snap info returned for snap %s", snapName)
} }
result := &results[0] result := &results[0]
info := result.Info info := result.Info
// Validate necessary fields in the snap information // Validate necessary fields in the snap information
if info.SnapID == "" || info.Revision.N == 0 { if info.SnapID == "" || info.Revision.N == 0 {
return nil, fmt.Errorf("invalid snap information for %s: SnapID or Revision is missing", snapName) return nil, fmt.Errorf("invalid snap information for %s: SnapID or Revision is missing", snapName)
} }
verboseLog("Fetched latest snap info for %s: SnapID: %s, Revision: %d", snapName, info.SnapID, info.Revision.N) verboseLog("Fetched latest snap info for %s: SnapID: %s, Revision: %d", snapName, info.SnapID, info.Revision.N)
return result, nil return result, nil
} }
// findPreviousSnap locates the previous snap revision in the downloads directory. // findPreviousSnap locates the previous snap revision in the downloads directory.
func findPreviousSnap(downloadDir, assertionsDir, snapName string) (string, *store.CurrentSnap) { func findPreviousSnap(downloadDir, assertionsDir, snapName string) (string, *store.CurrentSnap) {
var currentSnap store.CurrentSnap var currentSnap store.CurrentSnap
files, err := os.ReadDir(downloadDir) files, err := os.ReadDir(downloadDir)
if err != nil { if err != nil {
verboseLog("Error reading directory: %v", err) verboseLog("Error reading directory: %v", err)
return "", nil return "", nil
} }
var latestRevision int var latestRevision int
var latestSnapPath string var latestSnapPath string
for _, file := range files { for _, file := range files {
if strings.HasPrefix(file.Name(), snapName+"_") && strings.HasSuffix(file.Name(), ".snap") { if strings.HasPrefix(file.Name(), snapName+"_") && strings.HasSuffix(file.Name(), ".snap") {
revisionStr := extractRevisionFromFile(file.Name()) revisionStr := extractRevisionFromFile(file.Name())
if revisionStr == "" { if revisionStr == "" {
verboseLog("Failed to extract revision from file name: %s", file.Name()) verboseLog("Failed to extract revision from file name: %s", file.Name())
continue continue
} }
revision, err := strconv.Atoi(revisionStr) revision, err := strconv.Atoi(revisionStr)
if err != nil { if err != nil {
verboseLog("Failed to parse revision number for file %s: %v", file.Name(), err) verboseLog("Failed to parse revision number for file %s: %v", file.Name(), err)
continue continue
} }
verboseLog("Found %s with revision %d", file.Name(), revision) verboseLog("Found %s with revision %d", file.Name(), revision)
if revision > latestRevision { if revision > latestRevision {
latestRevision = revision latestRevision = revision
latestSnapPath = filepath.Join(downloadDir, file.Name()) latestSnapPath = filepath.Join(downloadDir, file.Name())
// Parse the corresponding assertion file // Parse the corresponding assertion file
assertFilePath := filepath.Join(assertionsDir, strings.Replace(file.Name(), ".snap", ".assert", 1)) assertFilePath := filepath.Join(assertionsDir, strings.Replace(file.Name(), ".snap", ".assert", 1))
currentSnap = parseSnapInfo(assertFilePath, snapName) currentSnap = parseSnapInfo(assertFilePath, snapName)
currentSnap.Revision.N = revision currentSnap.Revision.N = revision
} }
} }
} }
if latestSnapPath != "" { if latestSnapPath != "" {
return latestSnapPath, &currentSnap return latestSnapPath, &currentSnap
} }
return "", nil return "", nil
} }

@ -1,114 +1,114 @@
package main package main
import ( import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log" "log"
"os" "os"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"github.com/snapcore/snapd/store" "github.com/snapcore/snapd/store"
) )
// initializeSeedYaml ensures that seed.yaml exists; if not, creates it. // initializeSeedYaml ensures that seed.yaml exists; if not, creates it.
func initializeSeedYaml(seedYaml string) { func initializeSeedYaml(seedYaml string) {
if _, err := os.Stat(seedYaml); os.IsNotExist(err) { if _, err := os.Stat(seedYaml); os.IsNotExist(err) {
file, err := os.Create(seedYaml) file, err := os.Create(seedYaml)
if err != nil { if err != nil {
log.Fatalf("Failed to create seed.yaml: %v", err) log.Fatalf("Failed to create seed.yaml: %v", err)
} }
defer file.Close() defer file.Close()
file.WriteString("snaps:\n") file.WriteString("snaps:\n")
} }
} }
// loadSeedData loads seed data from seed.yaml // loadSeedData loads seed data from seed.yaml
func loadSeedData(seedYaml string) seed { func loadSeedData(seedYaml string) seed {
file, err := ioutil.ReadFile(seedYaml) file, err := ioutil.ReadFile(seedYaml)
if err != nil { if err != nil {
log.Fatalf("Failed to read seed.yaml: %v", err) log.Fatalf("Failed to read seed.yaml: %v", err)
} }
var seedData seed var seedData seed
if err := yaml.Unmarshal(file, &seedData); err != nil { if err := yaml.Unmarshal(file, &seedData); err != nil {
log.Fatalf("Failed to parse seed.yaml: %v", err) log.Fatalf("Failed to parse seed.yaml: %v", err)
} }
return seedData return seedData
} }
// loadExistingSnaps loads snaps from seed.yaml into a map // loadExistingSnaps loads snaps from seed.yaml into a map
func loadExistingSnaps(seedYaml string) map[string]bool { func loadExistingSnaps(seedYaml string) map[string]bool {
file, err := ioutil.ReadFile(seedYaml) file, err := ioutil.ReadFile(seedYaml)
if err != nil { if err != nil {
log.Fatalf("Failed to read seed.yaml: %v", err) log.Fatalf("Failed to read seed.yaml: %v", err)
} }
var seedData seed var seedData seed
if err := yaml.Unmarshal(file, &seedData); err != nil { if err := yaml.Unmarshal(file, &seedData); err != nil {
log.Fatalf("Failed to parse seed.yaml: %v", err) log.Fatalf("Failed to parse seed.yaml: %v", err)
} }
existing := make(map[string]bool) existing := make(map[string]bool)
for _, snap := range seedData.Snaps { for _, snap := range seedData.Snaps {
existing[snap.Name] = true existing[snap.Name] = true
verboseLog("Found %s in seed.yaml\n", snap.Name) verboseLog("Found %s in seed.yaml\n", snap.Name)
} }
return existing return existing
} }
// updateSeedYaml updates the seed.yaml file with the current required snaps // updateSeedYaml updates the seed.yaml file with the current required snaps
func updateSeedYaml(snapsDir, seedYaml string, currentSnaps []*store.CurrentSnap) error { func updateSeedYaml(snapsDir, seedYaml string, currentSnaps []*store.CurrentSnap) error {
// Log the snaps to be written // Log the snaps to be written
verboseLog("CurrentSnaps to be written to seed.yaml:") verboseLog("CurrentSnaps to be written to seed.yaml:")
for _, snapInfo := range currentSnaps { for _, snapInfo := range currentSnaps {
verboseLog("- %s_%d.snap", snapInfo.InstanceName, snapInfo.Revision.N) verboseLog("- %s_%d.snap", snapInfo.InstanceName, snapInfo.Revision.N)
} }
// Load existing seed data // Load existing seed data
file, err := ioutil.ReadFile(seedYaml) file, err := ioutil.ReadFile(seedYaml)
if err != nil { if err != nil {
return fmt.Errorf("failed to read seed.yaml: %w", err) return fmt.Errorf("failed to read seed.yaml: %w", err)
} }
var seedData seed var seedData seed
if err := yaml.Unmarshal(file, &seedData); err != nil { if err := yaml.Unmarshal(file, &seedData); err != nil {
return fmt.Errorf("failed to parse seed.yaml: %w", err) return fmt.Errorf("failed to parse seed.yaml: %w", err)
} }
// Clear existing snaps // Clear existing snaps
seedData.Snaps = []struct { seedData.Snaps = []struct {
Name string `yaml:"name"` Name string `yaml:"name"`
Channel string `yaml:"channel"` Channel string `yaml:"channel"`
File string `yaml:"file"` File string `yaml:"file"`
}{} }{}
// Populate seedData with currentSnaps // Populate seedData with currentSnaps
for _, snapInfo := range currentSnaps { for _, snapInfo := range currentSnaps {
snapFileName := fmt.Sprintf("%s_%d.snap", snapInfo.InstanceName, snapInfo.Revision.N) snapFileName := fmt.Sprintf("%s_%d.snap", snapInfo.InstanceName, snapInfo.Revision.N)
snapData := struct { snapData := struct {
Name string `yaml:"name"` Name string `yaml:"name"`
Channel string `yaml:"channel"` Channel string `yaml:"channel"`
File string `yaml:"file"` File string `yaml:"file"`
}{ }{
Name: snapInfo.InstanceName, Name: snapInfo.InstanceName,
Channel: "stable", // Assuming 'stable' channel; modify as needed Channel: "stable", // Assuming 'stable' channel; modify as needed
File: snapFileName, File: snapFileName,
} }
seedData.Snaps = append(seedData.Snaps, snapData) seedData.Snaps = append(seedData.Snaps, snapData)
} }
// Marshal the updated seedData back to YAML // Marshal the updated seedData back to YAML
updatedYAML, err := yaml.Marshal(&seedData) updatedYAML, err := yaml.Marshal(&seedData)
if err != nil { if err != nil {
return fmt.Errorf("failed to marshal updated seed data: %w", err) return fmt.Errorf("failed to marshal updated seed data: %w", err)
} }
// Write the updated YAML back to seed.yaml // Write the updated YAML back to seed.yaml
if err := ioutil.WriteFile(seedYaml, updatedYAML, 0644); err != nil { if err := ioutil.WriteFile(seedYaml, updatedYAML, 0644); err != nil {
return fmt.Errorf("failed to write updated seed.yaml: %w", err) return fmt.Errorf("failed to write updated seed.yaml: %w", err)
} }
verboseLog("Updated seed.yaml with current snaps.") verboseLog("Updated seed.yaml with current snaps.")
return nil return nil
} }

@ -1,106 +1,106 @@
package main package main
import ( import (
"bufio" "bufio"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"io" "io"
"log" "log"
"os" "os"
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings" "strings"
"github.com/snapcore/snapd/snap" "github.com/snapcore/snapd/snap"
"github.com/snapcore/snapd/store" "github.com/snapcore/snapd/store"
"golang.org/x/crypto/sha3" "golang.org/x/crypto/sha3"
) )
// verifyChecksum calculates the SHA3-384 checksum of a file and compares it with the expected checksum. // verifyChecksum calculates the SHA3-384 checksum of a file and compares it with the expected checksum.
func verifyChecksum(filePath, expectedChecksum string) (bool, error) { func verifyChecksum(filePath, expectedChecksum string) (bool, error) {
file, err := os.Open(filePath) file, err := os.Open(filePath)
if err != nil { if err != nil {
return false, fmt.Errorf("failed to open file for checksum verification: %w", err) return false, fmt.Errorf("failed to open file for checksum verification: %w", err)
} }
defer file.Close() defer file.Close()
hash := sha3.New384() hash := sha3.New384()
if _, err := io.Copy(hash, file); err != nil { if _, err := io.Copy(hash, file); err != nil {
return false, fmt.Errorf("failed to calculate checksum: %w", err) return false, fmt.Errorf("failed to calculate checksum: %w", err)
} }
calculatedChecksum := hex.EncodeToString(hash.Sum(nil)) calculatedChecksum := hex.EncodeToString(hash.Sum(nil))
return strings.EqualFold(calculatedChecksum, expectedChecksum), nil return strings.EqualFold(calculatedChecksum, expectedChecksum), nil
} }
// extractRevisionFromFile extracts the revision number from a file name by splitting at the last underscore. // extractRevisionFromFile extracts the revision number from a file name by splitting at the last underscore.
func extractRevisionFromFile(fileName string) string { func extractRevisionFromFile(fileName string) string {
lastUnderscore := strings.LastIndex(fileName, "_") lastUnderscore := strings.LastIndex(fileName, "_")
if lastUnderscore == -1 { if lastUnderscore == -1 {
return "" return ""
} }
revisionWithSuffix := fileName[lastUnderscore+1:] revisionWithSuffix := fileName[lastUnderscore+1:]
// Handle both .snap and .assert suffixes // Handle both .snap and .assert suffixes
if strings.HasSuffix(revisionWithSuffix, ".snap") { if strings.HasSuffix(revisionWithSuffix, ".snap") {
return strings.TrimSuffix(revisionWithSuffix, ".snap") return strings.TrimSuffix(revisionWithSuffix, ".snap")
} else if strings.HasSuffix(revisionWithSuffix, ".assert") { } else if strings.HasSuffix(revisionWithSuffix, ".assert") {
return strings.TrimSuffix(revisionWithSuffix, ".assert") return strings.TrimSuffix(revisionWithSuffix, ".assert")
} }
return revisionWithSuffix return revisionWithSuffix
} }
// fileExists checks if a file exists and is not a directory before we use it // fileExists checks if a file exists and is not a directory before we use it
func fileExists(filename string) bool { func fileExists(filename string) bool {
info, err := os.Stat(filename) info, err := os.Stat(filename)
if os.IsNotExist(err) { if os.IsNotExist(err) {
return false return false
} }
return !info.IsDir() return !info.IsDir()
} }
// parseSnapInfo extracts snap details from the assertion file and returns the snap's current revision. // parseSnapInfo extracts snap details from the assertion file and returns the snap's current revision.
func parseSnapInfo(assertFilePath, snapName string) store.CurrentSnap { func parseSnapInfo(assertFilePath, snapName string) store.CurrentSnap {
currentSnap := store.CurrentSnap{InstanceName: snapName} currentSnap := store.CurrentSnap{InstanceName: snapName}
file, err := os.Open(assertFilePath) file, err := os.Open(assertFilePath)
if err != nil { if err != nil {
verboseLog("Failed to read assertion file: %v\n", err) verboseLog("Failed to read assertion file: %v\n", err)
return currentSnap return currentSnap
} }
defer file.Close() defer file.Close()
scanner := bufio.NewScanner(file) scanner := bufio.NewScanner(file)
for scanner.Scan() { for scanner.Scan() {
line := strings.TrimSpace(scanner.Text()) line := strings.TrimSpace(scanner.Text())
if strings.HasPrefix(line, "snap-id:") { if strings.HasPrefix(line, "snap-id:") {
currentSnap.SnapID = strings.TrimSpace(strings.SplitN(line, ":", 2)[1]) currentSnap.SnapID = strings.TrimSpace(strings.SplitN(line, ":", 2)[1])
} }
if strings.HasPrefix(line, "snap-revision:") { if strings.HasPrefix(line, "snap-revision:") {
revisionStr := strings.TrimSpace(strings.SplitN(line, ":", 2)[1]) revisionStr := strings.TrimSpace(strings.SplitN(line, ":", 2)[1])
revision, err := strconv.Atoi(revisionStr) revision, err := strconv.Atoi(revisionStr)
if err != nil { if err != nil {
verboseLog("Failed to parse snap-revision for snap %s: %v", snapName, err) verboseLog("Failed to parse snap-revision for snap %s: %v", snapName, err)
continue continue
} }
currentSnap.Revision.N = revision currentSnap.Revision.N = revision
} }
} }
// Validate that required fields are present // Validate that required fields are present
if currentSnap.SnapID == "" || currentSnap.Revision.N == 0 { if currentSnap.SnapID == "" || currentSnap.Revision.N == 0 {
verboseLog("Incomplete snap info in assertion file: %s", assertFilePath) verboseLog("Incomplete snap info in assertion file: %s", assertFilePath)
} }
return currentSnap return currentSnap
} }
// isSnapInCurrentSnaps checks if a snap is already in currentSnaps // isSnapInCurrentSnaps checks if a snap is already in currentSnaps
func isSnapInCurrentSnaps(snapName string) (bool, snap.Revision) { func isSnapInCurrentSnaps(snapName string) (bool, snap.Revision) {
for _, snap := range currentSnaps { for _, snap := range currentSnaps {
if snap.InstanceName == snapName { if snap.InstanceName == snapName {
return true, snap.Revision return true, snap.Revision
} }
} }
return false, snap.Revision{} return false, snap.Revision{}
} }
func removeSnapFromCurrentSnaps(snapName string, revision snap.Revision) { func removeSnapFromCurrentSnaps(snapName string, revision snap.Revision) {
@ -115,36 +115,36 @@ func removeSnapFromCurrentSnaps(snapName string, revision snap.Revision) {
// initializeDirectories ensures that the snaps and assertions directories exist // initializeDirectories ensures that the snaps and assertions directories exist
func initializeDirectories(snapsDir, assertionsDir string) { func initializeDirectories(snapsDir, assertionsDir string) {
if err := os.MkdirAll(snapsDir, 0755); err != nil { if err := os.MkdirAll(snapsDir, 0755); err != nil {
log.Fatalf("Failed to create snaps directory: %v", err) log.Fatalf("Failed to create snaps directory: %v", err)
} }
if err := os.MkdirAll(assertionsDir, 0755); err != nil { if err := os.MkdirAll(assertionsDir, 0755); err != nil {
log.Fatalf("Failed to create assertions directory: %v", err) log.Fatalf("Failed to create assertions directory: %v", err)
} }
} }
// getCurrentSnapInfo retrieves current snap information from assertions // getCurrentSnapInfo retrieves current snap information from assertions
func getCurrentSnapInfo(assertionsDir, snapName string) (*store.CurrentSnap, error) { func getCurrentSnapInfo(assertionsDir, snapName string) (*store.CurrentSnap, error) {
assertionFiles, err := filepath.Glob(filepath.Join(assertionsDir, fmt.Sprintf("%s_*.assert", snapName))) assertionFiles, err := filepath.Glob(filepath.Join(assertionsDir, fmt.Sprintf("%s_*.assert", snapName)))
if err != nil || len(assertionFiles) == 0 { if err != nil || len(assertionFiles) == 0 {
return nil, fmt.Errorf("no assertion file found for snap: %s", snapName) return nil, fmt.Errorf("no assertion file found for snap: %s", snapName)
} }
assertionFile := assertionFiles[0] assertionFile := assertionFiles[0]
currentSnap := parseSnapInfo(assertionFile, snapName) currentSnap := parseSnapInfo(assertionFile, snapName)
if currentSnap.SnapID == "" || currentSnap.Revision.N == 0 { if currentSnap.SnapID == "" || currentSnap.Revision.N == 0 {
return nil, fmt.Errorf("incomplete snap info in assertion file for snap: %s", snapName) return nil, fmt.Errorf("incomplete snap info in assertion file for snap: %s", snapName)
} }
verboseLog("Found snap info for %s: SnapID: %s, Revision: %d", snapName, currentSnap.SnapID, currentSnap.Revision.N) verboseLog("Found snap info for %s: SnapID: %s, Revision: %d", snapName, currentSnap.SnapID, currentSnap.Revision.N)
return &currentSnap, nil return &currentSnap, nil
} }
// verifySnapIntegrity verifies the integrity of a snap file // verifySnapIntegrity verifies the integrity of a snap file
func verifySnapIntegrity(filePath, expectedChecksum string) bool { func verifySnapIntegrity(filePath, expectedChecksum string) bool {
checksumMatches, err := verifyChecksum(filePath, expectedChecksum) checksumMatches, err := verifyChecksum(filePath, expectedChecksum)
if err != nil { if err != nil {
verboseLog("Checksum verification failed for %s: %v", filePath, err) verboseLog("Checksum verification failed for %s: %v", filePath, err)
return false return false
} }
return checksumMatches return checksumMatches
} }

@ -1,108 +1,108 @@
package main package main
import ( import (
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log" "log"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"strings" "strings"
) )
// validateSeed validates the seed using snap debug // validateSeed validates the seed using snap debug
func validateSeed(seedYaml string) error { func validateSeed(seedYaml string) error {
cmd := exec.Command("snap", "debug", "validate-seed", seedYaml) cmd := exec.Command("snap", "debug", "validate-seed", seedYaml)
output, err := cmd.CombinedOutput() output, err := cmd.CombinedOutput()
if err != nil { if err != nil {
return fmt.Errorf("validation failed with output: %s, error: %v", string(output), err) return fmt.Errorf("validation failed with output: %s, error: %v", string(output), err)
} }
verboseLog("Seed validation successful: %s", string(output)) verboseLog("Seed validation successful: %s", string(output))
return nil return nil
} }
// ensureAssertions ensures that essential assertions are present // ensureAssertions ensures that essential assertions are present
func ensureAssertions(assertionsDir string) { func ensureAssertions(assertionsDir string) {
model := "generic-classic" model := "generic-classic"
brand := "generic" brand := "generic"
series := "16" // Hardcoded series as snap.Info does not have a Series field series := "16" // Hardcoded series as snap.Info does not have a Series field
modelAssertionPath := filepath.Join(assertionsDir, "model") modelAssertionPath := filepath.Join(assertionsDir, "model")
accountKeyAssertionPath := filepath.Join(assertionsDir, "account-key") accountKeyAssertionPath := filepath.Join(assertionsDir, "account-key")
accountAssertionPath := filepath.Join(assertionsDir, "account") accountAssertionPath := filepath.Join(assertionsDir, "account")
// Check and generate model assertion // Check and generate model assertion
if _, err := os.Stat(modelAssertionPath); os.IsNotExist(err) { if _, err := os.Stat(modelAssertionPath); os.IsNotExist(err) {
output, err := exec.Command("snap", "known", "--remote", "model", "series="+series, "model="+model, "brand-id="+brand).CombinedOutput() output, err := exec.Command("snap", "known", "--remote", "model", "series="+series, "model="+model, "brand-id="+brand).CombinedOutput()
if err != nil { if err != nil {
log.Fatalf("Failed to fetch model assertion: %v, Output: %s", err, string(output)) log.Fatalf("Failed to fetch model assertion: %v, Output: %s", err, string(output))
} }
if err := ioutil.WriteFile(modelAssertionPath, output, 0644); err != nil { if err := ioutil.WriteFile(modelAssertionPath, output, 0644); err != nil {
log.Fatalf("Failed to write model assertion: %v", err) log.Fatalf("Failed to write model assertion: %v", err)
} }
verboseLog("Fetched and saved model assertion to %s", modelAssertionPath) verboseLog("Fetched and saved model assertion to %s", modelAssertionPath)
} }
// Generate account-key assertion if not exists // Generate account-key assertion if not exists
if _, err := os.Stat(accountKeyAssertionPath); os.IsNotExist(err) { if _, err := os.Stat(accountKeyAssertionPath); os.IsNotExist(err) {
signKeySha3 := grepPattern(modelAssertionPath, "sign-key-sha3-384: ") signKeySha3 := grepPattern(modelAssertionPath, "sign-key-sha3-384: ")
if signKeySha3 == "" { if signKeySha3 == "" {
log.Fatalf("Failed to extract sign-key-sha3-384 from model assertion.") log.Fatalf("Failed to extract sign-key-sha3-384 from model assertion.")
} }
decodedSignKey, err := base64.StdEncoding.DecodeString(signKeySha3) decodedSignKey, err := base64.StdEncoding.DecodeString(signKeySha3)
if err != nil { if err != nil {
log.Fatalf("Failed to decode sign-key-sha3-384: %v", err) log.Fatalf("Failed to decode sign-key-sha3-384: %v", err)
} }
encodedSignKey := base64.StdEncoding.EncodeToString(decodedSignKey) encodedSignKey := base64.StdEncoding.EncodeToString(decodedSignKey)
output, err := exec.Command("snap", "known", "--remote", "account-key", "public-key-sha3-384="+encodedSignKey).CombinedOutput() output, err := exec.Command("snap", "known", "--remote", "account-key", "public-key-sha3-384="+encodedSignKey).CombinedOutput()
if err != nil { if err != nil {
log.Fatalf("Failed to fetch account-key assertion: %v, Output: %s", err, string(output)) log.Fatalf("Failed to fetch account-key assertion: %v, Output: %s", err, string(output))
} }
if err := ioutil.WriteFile(accountKeyAssertionPath, output, 0644); err != nil { if err := ioutil.WriteFile(accountKeyAssertionPath, output, 0644); err != nil {
log.Fatalf("Failed to write account-key assertion: %v", err) log.Fatalf("Failed to write account-key assertion: %v", err)
} }
verboseLog("Fetched and saved account-key assertion to %s", accountKeyAssertionPath) verboseLog("Fetched and saved account-key assertion to %s", accountKeyAssertionPath)
} }
// Generate account assertion if not exists // Generate account assertion if not exists
if _, err := os.Stat(accountAssertionPath); os.IsNotExist(err) { if _, err := os.Stat(accountAssertionPath); os.IsNotExist(err) {
accountId := grepPattern(accountKeyAssertionPath, "account-id: ") accountId := grepPattern(accountKeyAssertionPath, "account-id: ")
if accountId == "" { if accountId == "" {
log.Fatalf("Failed to extract account-id from account-key assertion.") log.Fatalf("Failed to extract account-id from account-key assertion.")
} }
output, err := exec.Command("snap", "known", "--remote", "account", "account-id="+accountId).CombinedOutput() output, err := exec.Command("snap", "known", "--remote", "account", "account-id="+accountId).CombinedOutput()
if err != nil { if err != nil {
log.Fatalf("Failed to fetch account assertion: %v, Output: %s", err, string(output)) log.Fatalf("Failed to fetch account assertion: %v, Output: %s", err, string(output))
} }
if err := ioutil.WriteFile(accountAssertionPath, output, 0644); err != nil { if err := ioutil.WriteFile(accountAssertionPath, output, 0644); err != nil {
log.Fatalf("Failed to write account assertion: %v", err) log.Fatalf("Failed to write account assertion: %v", err)
} }
verboseLog("Fetched and saved account assertion to %s", accountAssertionPath) verboseLog("Fetched and saved account assertion to %s", accountAssertionPath)
} }
} }
// grepPattern extracts a specific pattern from a file // grepPattern extracts a specific pattern from a file
func grepPattern(filePath, pattern string) string { func grepPattern(filePath, pattern string) string {
content, err := ioutil.ReadFile(filePath) content, err := ioutil.ReadFile(filePath)
if err != nil { if err != nil {
log.Fatalf("Failed to read from file %s: %v", filePath, err) log.Fatalf("Failed to read from file %s: %v", filePath, err)
} }
lines := strings.Split(string(content), "\n") lines := strings.Split(string(content), "\n")
for _, line := range lines { for _, line := range lines {
if strings.Contains(line, pattern) { if strings.Contains(line, pattern) {
parts := strings.SplitN(line, ":", 2) parts := strings.SplitN(line, ":", 2)
if len(parts) == 2 { if len(parts) == 2 {
encodedValue := strings.TrimSpace(parts[1]) encodedValue := strings.TrimSpace(parts[1])
// Check if the value is base64 encoded // Check if the value is base64 encoded
if decodedBytes, err := base64.StdEncoding.DecodeString(encodedValue); err == nil { if decodedBytes, err := base64.StdEncoding.DecodeString(encodedValue); err == nil {
return string(decodedBytes) return string(decodedBytes)
} }
return encodedValue return encodedValue
} }
} }
} }
log.Fatalf("Pattern %s not found in file %s", pattern, filePath) log.Fatalf("Pattern %s not found in file %s", pattern, filePath)
return "" return ""
} }

Loading…
Cancel
Save