parent
e57ad37808
commit
4d6176bc13
@ -1,152 +1,152 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"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.
|
||||
func cleanUpFiles(snapsDir, assertionsDir, seedYaml string) {
|
||||
verboseLog("Starting cleanup process...")
|
||||
|
||||
// Load the seed.yaml data
|
||||
seedData := loadSeedData(seedYaml)
|
||||
|
||||
// Create a map of valid snap and assertion files based on seed.yaml
|
||||
validSnaps := make(map[string]bool)
|
||||
validAssertions := make(map[string]bool)
|
||||
|
||||
// Populate valid snaps and assertions from the seed.yaml data
|
||||
for _, snap := range seedData.Snaps {
|
||||
// Ensure correct extraction of revision
|
||||
revision := extractRevisionFromFile(snap.File)
|
||||
if revision == "" {
|
||||
verboseLog("Failed to extract revision from file name: %s", snap.File)
|
||||
continue
|
||||
}
|
||||
snapFileName := fmt.Sprintf("%s_%s.snap", snap.Name, revision)
|
||||
assertionFileName := fmt.Sprintf("%s_%s.assert", snap.Name, revision)
|
||||
|
||||
validSnaps[snapFileName] = true
|
||||
validAssertions[assertionFileName] = true
|
||||
}
|
||||
|
||||
// Log valid snaps and assertions
|
||||
verboseLog("Valid Snaps: %v", validSnaps)
|
||||
verboseLog("Valid Assertions: %v", validAssertions)
|
||||
|
||||
// Remove outdated or partial snap files
|
||||
files, err := os.ReadDir(snapsDir)
|
||||
if err != nil {
|
||||
verboseLog("Error reading snaps directory for cleanup: %v", err)
|
||||
} else {
|
||||
for _, file := range files {
|
||||
filePath := filepath.Join(snapsDir, file.Name())
|
||||
if strings.HasSuffix(file.Name(), ".partial") || strings.HasSuffix(file.Name(), ".delta") {
|
||||
verboseLog("Removing partial/delta file: %s\n", filePath)
|
||||
if err := os.Remove(filePath); err != nil {
|
||||
verboseLog("Failed to remove file %s: %v", filePath, err)
|
||||
} else if verbose {
|
||||
verboseLog("Removed partial/delta file: %s", filePath)
|
||||
}
|
||||
} else if strings.HasSuffix(file.Name(), ".snap") {
|
||||
if !validSnaps[file.Name()] {
|
||||
verboseLog("Removing outdated or orphaned snap file: %s\n", filePath)
|
||||
if err := os.Remove(filePath); err != nil {
|
||||
verboseLog("Failed to remove snap file %s: %v", filePath, err)
|
||||
} else if verbose {
|
||||
verboseLog("Removed snap file: %s", filePath)
|
||||
}
|
||||
} else {
|
||||
verboseLog("Snap file %s is valid and retained.\n", file.Name())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove orphaned assertion files
|
||||
files, err = os.ReadDir(assertionsDir)
|
||||
if err != nil {
|
||||
verboseLog("Error reading assertions directory for cleanup: %v", err)
|
||||
} else {
|
||||
for _, file := range files {
|
||||
filePath := filepath.Join(assertionsDir, file.Name())
|
||||
if strings.HasSuffix(file.Name(), ".assert") {
|
||||
if !validAssertions[file.Name()] {
|
||||
verboseLog("Removing orphaned assertion file: %s\n", filePath)
|
||||
if err := os.Remove(filePath); err != nil {
|
||||
verboseLog("Failed to remove assertion file %s: %v", filePath, err)
|
||||
} else if verbose {
|
||||
verboseLog("Removed assertion file: %s", filePath)
|
||||
}
|
||||
} else {
|
||||
verboseLog("Assertion file %s is valid and retained.\n", file.Name())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
verboseLog("Cleanup process completed.")
|
||||
verboseLog("Starting cleanup process...")
|
||||
|
||||
// Load the seed.yaml data
|
||||
seedData := loadSeedData(seedYaml)
|
||||
|
||||
// Create a map of valid snap and assertion files based on seed.yaml
|
||||
validSnaps := make(map[string]bool)
|
||||
validAssertions := make(map[string]bool)
|
||||
|
||||
// Populate valid snaps and assertions from the seed.yaml data
|
||||
for _, snap := range seedData.Snaps {
|
||||
// Ensure correct extraction of revision
|
||||
revision := extractRevisionFromFile(snap.File)
|
||||
if revision == "" {
|
||||
verboseLog("Failed to extract revision from file name: %s", snap.File)
|
||||
continue
|
||||
}
|
||||
snapFileName := fmt.Sprintf("%s_%s.snap", snap.Name, revision)
|
||||
assertionFileName := fmt.Sprintf("%s_%s.assert", snap.Name, revision)
|
||||
|
||||
validSnaps[snapFileName] = true
|
||||
validAssertions[assertionFileName] = true
|
||||
}
|
||||
|
||||
// Log valid snaps and assertions
|
||||
verboseLog("Valid Snaps: %v", validSnaps)
|
||||
verboseLog("Valid Assertions: %v", validAssertions)
|
||||
|
||||
// Remove outdated or partial snap files
|
||||
files, err := os.ReadDir(snapsDir)
|
||||
if err != nil {
|
||||
verboseLog("Error reading snaps directory for cleanup: %v", err)
|
||||
} else {
|
||||
for _, file := range files {
|
||||
filePath := filepath.Join(snapsDir, file.Name())
|
||||
if strings.HasSuffix(file.Name(), ".partial") || strings.HasSuffix(file.Name(), ".delta") {
|
||||
verboseLog("Removing partial/delta file: %s\n", filePath)
|
||||
if err := os.Remove(filePath); err != nil {
|
||||
verboseLog("Failed to remove file %s: %v", filePath, err)
|
||||
} else if verbose {
|
||||
verboseLog("Removed partial/delta file: %s", filePath)
|
||||
}
|
||||
} else if strings.HasSuffix(file.Name(), ".snap") {
|
||||
if !validSnaps[file.Name()] {
|
||||
verboseLog("Removing outdated or orphaned snap file: %s\n", filePath)
|
||||
if err := os.Remove(filePath); err != nil {
|
||||
verboseLog("Failed to remove snap file %s: %v", filePath, err)
|
||||
} else if verbose {
|
||||
verboseLog("Removed snap file: %s", filePath)
|
||||
}
|
||||
} else {
|
||||
verboseLog("Snap file %s is valid and retained.\n", file.Name())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove orphaned assertion files
|
||||
files, err = os.ReadDir(assertionsDir)
|
||||
if err != nil {
|
||||
verboseLog("Error reading assertions directory for cleanup: %v", err)
|
||||
} else {
|
||||
for _, file := range files {
|
||||
filePath := filepath.Join(assertionsDir, file.Name())
|
||||
if strings.HasSuffix(file.Name(), ".assert") {
|
||||
if !validAssertions[file.Name()] {
|
||||
verboseLog("Removing orphaned assertion file: %s\n", filePath)
|
||||
if err := os.Remove(filePath); err != nil {
|
||||
verboseLog("Failed to remove assertion file %s: %v", filePath, err)
|
||||
} else if verbose {
|
||||
verboseLog("Removed assertion file: %s", filePath)
|
||||
}
|
||||
} else {
|
||||
verboseLog("Assertion file %s is valid and retained.\n", file.Name())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
verboseLog("Cleanup process completed.")
|
||||
}
|
||||
|
||||
// removeOrphanedFiles deletes the assertion and snap file corresponding to the removed snap.
|
||||
func removeOrphanedFiles(snapName string, revision int, assertionsDir string, snapsDir string) {
|
||||
assertionFilePath := filepath.Join(assertionsDir, fmt.Sprintf("%s_%d.assert", snapName, revision))
|
||||
snapFilePath := filepath.Join(snapsDir, fmt.Sprintf("%s_%d.snap", snapName, revision))
|
||||
if fileExists(assertionFilePath) {
|
||||
err := os.Remove(assertionFilePath)
|
||||
if err != nil {
|
||||
verboseLog("Failed to remove assertion file %s: %v", assertionFilePath, err)
|
||||
} else {
|
||||
verboseLog("Removed assertion file: %s", assertionFilePath)
|
||||
}
|
||||
} else {
|
||||
verboseLog("Assertion file %s does not exist. No action taken.", assertionFilePath)
|
||||
}
|
||||
if fileExists(snapFilePath) {
|
||||
err := os.Remove(snapFilePath)
|
||||
if err != nil {
|
||||
verboseLog("Failed to remove snap file %s: %v", snapFilePath, err)
|
||||
} else {
|
||||
verboseLog("Removed snap file: %s", snapFilePath)
|
||||
}
|
||||
} else {
|
||||
verboseLog("Snap file %s does not exist. No action taken.", snapFilePath)
|
||||
}
|
||||
assertionFilePath := filepath.Join(assertionsDir, fmt.Sprintf("%s_%d.assert", snapName, revision))
|
||||
snapFilePath := filepath.Join(snapsDir, fmt.Sprintf("%s_%d.snap", snapName, revision))
|
||||
if fileExists(assertionFilePath) {
|
||||
err := os.Remove(assertionFilePath)
|
||||
if err != nil {
|
||||
verboseLog("Failed to remove assertion file %s: %v", assertionFilePath, err)
|
||||
} else {
|
||||
verboseLog("Removed assertion file: %s", assertionFilePath)
|
||||
}
|
||||
} else {
|
||||
verboseLog("Assertion file %s does not exist. No action taken.", assertionFilePath)
|
||||
}
|
||||
if fileExists(snapFilePath) {
|
||||
err := os.Remove(snapFilePath)
|
||||
if err != nil {
|
||||
verboseLog("Failed to remove snap file %s: %v", snapFilePath, err)
|
||||
} else {
|
||||
verboseLog("Removed snap file: %s", snapFilePath)
|
||||
}
|
||||
} else {
|
||||
verboseLog("Snap file %s does not exist. No action taken.", snapFilePath)
|
||||
}
|
||||
}
|
||||
|
||||
// cleanUpCurrentSnaps removes snaps from currentSnaps that are not marked as required.
|
||||
func cleanUpCurrentSnaps(assertionsDir string, snapsDir string) {
|
||||
var filteredSnaps []*store.CurrentSnap
|
||||
|
||||
for _, snap := range currentSnaps {
|
||||
if requiredSnaps[snap.InstanceName] {
|
||||
filteredSnaps = append(filteredSnaps, snap)
|
||||
} else {
|
||||
verboseLog("Removing unnecessary snap: %s\n", snap.InstanceName)
|
||||
removeOrphanedFiles(snap.InstanceName, snap.Revision.N, assertionsDir, snapsDir)
|
||||
}
|
||||
}
|
||||
currentSnaps = filteredSnaps
|
||||
|
||||
// Log the updated currentSnaps
|
||||
verboseLog("Filtered currentSnaps after cleanup:")
|
||||
for _, snap := range currentSnaps {
|
||||
verboseLog("- %s_%d.snap", snap.InstanceName, snap.Revision.N)
|
||||
}
|
||||
var filteredSnaps []*store.CurrentSnap
|
||||
|
||||
for _, snap := range currentSnaps {
|
||||
if requiredSnaps[snap.InstanceName] {
|
||||
filteredSnaps = append(filteredSnaps, snap)
|
||||
} else {
|
||||
verboseLog("Removing unnecessary snap: %s\n", snap.InstanceName)
|
||||
removeOrphanedFiles(snap.InstanceName, snap.Revision.N, assertionsDir, snapsDir)
|
||||
}
|
||||
}
|
||||
currentSnaps = filteredSnaps
|
||||
|
||||
// Log the updated currentSnaps
|
||||
verboseLog("Filtered currentSnaps after cleanup:")
|
||||
for _, snap := range currentSnaps {
|
||||
verboseLog("- %s_%d.snap", snap.InstanceName, snap.Revision.N)
|
||||
}
|
||||
}
|
||||
|
||||
// removeStateJson removes the state.json file if it exists
|
||||
func removeStateJson(stateJsonPath string) {
|
||||
if _, err := os.Stat(stateJsonPath); err == nil {
|
||||
if err := os.Remove(stateJsonPath); err != nil {
|
||||
verboseLog("Failed to remove state.json: %v", err)
|
||||
} else if verbose {
|
||||
verboseLog("Removed state.json at %s", stateJsonPath)
|
||||
}
|
||||
}
|
||||
if _, err := os.Stat(stateJsonPath); err == nil {
|
||||
if err := os.Remove(stateJsonPath); err != nil {
|
||||
verboseLog("Failed to remove state.json: %v", err)
|
||||
} else if verbose {
|
||||
verboseLog("Removed state.json at %s", stateJsonPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,152 +1,152 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/snapcore/snapd/snap"
|
||||
"github.com/snapcore/snapd/store"
|
||||
"github.com/snapcore/snapd/snap"
|
||||
"github.com/snapcore/snapd/store"
|
||||
)
|
||||
|
||||
// downloadSnap downloads a snap file with retry logic
|
||||
func downloadSnap(storeClient *store.Store, snapInfo *snap.Info, downloadPath string) error {
|
||||
downloadInfo := &snap.DownloadInfo{
|
||||
DownloadURL: snapInfo.DownloadURL,
|
||||
}
|
||||
downloadInfo := &snap.DownloadInfo{
|
||||
DownloadURL: snapInfo.DownloadURL,
|
||||
}
|
||||
|
||||
pbar := NewProgressMeter(snapInfo.SuggestedName, snapInfo.Version, false)
|
||||
progressTracker.UpdateStepProgress(0)
|
||||
|
||||
for attempts := 1; attempts <= 5; attempts++ {
|
||||
verboseLog("Attempt %d to download snap: %s", attempts, downloadPath)
|
||||
err := storeClient.Download(ctx, snapInfo.SnapID, downloadPath, downloadInfo, pbar, nil, nil)
|
||||
if err == nil {
|
||||
for attempts := 1; attempts <= 5; attempts++ {
|
||||
verboseLog("Attempt %d to download snap: %s", attempts, downloadPath)
|
||||
err := storeClient.Download(ctx, snapInfo.SnapID, downloadPath, downloadInfo, pbar, nil, nil)
|
||||
if err == nil {
|
||||
pbar.Finished()
|
||||
return nil // Successful download
|
||||
}
|
||||
if verbose {
|
||||
verboseLog("Attempt %d to download %s failed: %v", attempts, snapInfo.SuggestedName, err)
|
||||
} else if progressTracker != nil {
|
||||
return nil // Successful download
|
||||
}
|
||||
if verbose {
|
||||
verboseLog("Attempt %d to download %s failed: %v", attempts, snapInfo.SuggestedName, err)
|
||||
} else if progressTracker != nil {
|
||||
progressTracker.UpdateStepProgress(0)
|
||||
}
|
||||
if attempts == 5 {
|
||||
return fmt.Errorf("snap download failed after 5 attempts: %v", err)
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("snap download failed after 5 attempts")
|
||||
}
|
||||
if attempts == 5 {
|
||||
return fmt.Errorf("snap download failed after 5 attempts: %v", err)
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("snap download failed after 5 attempts")
|
||||
}
|
||||
|
||||
// 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 {
|
||||
if !verbose {
|
||||
verboseLog("Downloading delta for %s", snapName)
|
||||
}
|
||||
var lastErr error
|
||||
backoff := 1 * time.Second
|
||||
|
||||
for attempts := 1; attempts <= maxRetries; attempts++ {
|
||||
verboseLog("Attempt %d to download delta: %s", attempts, deltaPath)
|
||||
err := downloadSnapDelta(storeClient, delta, result, deltaPath)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
lastErr = err
|
||||
verboseLog("Attempt %d to download delta failed: %v", attempts, err)
|
||||
time.Sleep(backoff)
|
||||
backoff *= 2
|
||||
}
|
||||
return fmt.Errorf("delta download failed after %d attempts: %v", maxRetries, lastErr)
|
||||
if !verbose {
|
||||
verboseLog("Downloading delta for %s", snapName)
|
||||
}
|
||||
var lastErr error
|
||||
backoff := 1 * time.Second
|
||||
|
||||
for attempts := 1; attempts <= maxRetries; attempts++ {
|
||||
verboseLog("Attempt %d to download delta: %s", attempts, deltaPath)
|
||||
err := downloadSnapDelta(storeClient, delta, result, deltaPath)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
lastErr = err
|
||||
verboseLog("Attempt %d to download delta failed: %v", attempts, err)
|
||||
time.Sleep(backoff)
|
||||
backoff *= 2
|
||||
}
|
||||
return fmt.Errorf("delta download failed after %d attempts: %v", maxRetries, lastErr)
|
||||
}
|
||||
|
||||
// downloadSnapDelta downloads the delta file.
|
||||
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{
|
||||
DownloadURL: delta.DownloadURL,
|
||||
Size: delta.Size,
|
||||
Sha3_384: delta.Sha3_384,
|
||||
}
|
||||
downloadInfo := &snap.DownloadInfo{
|
||||
DownloadURL: delta.DownloadURL,
|
||||
Size: delta.Size,
|
||||
Sha3_384: delta.Sha3_384,
|
||||
}
|
||||
|
||||
// Use the SnapID from the associated SnapActionResult's Info
|
||||
snapID := result.Info.SnapID
|
||||
// Use the SnapID from the associated SnapActionResult's Info
|
||||
snapID := result.Info.SnapID
|
||||
|
||||
pbar := NewProgressMeter(result.Info.SuggestedName, result.Info.Version, true)
|
||||
progressTracker.UpdateStepProgress(0)
|
||||
|
||||
// Download the delta file
|
||||
if err := storeClient.Download(ctx, snapID, deltaPath, downloadInfo, pbar, nil, nil); err != nil {
|
||||
// Download the delta file
|
||||
if err := storeClient.Download(ctx, snapID, deltaPath, downloadInfo, pbar, nil, nil); err != nil {
|
||||
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)
|
||||
pbar.Finished()
|
||||
|
||||
return nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// downloadAndApplySnap handles the downloading and delta application process.
|
||||
// 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) {
|
||||
if result == nil || result.Info == nil {
|
||||
verboseLog("No updates available for snap. Skipping download and assertions.")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
snapInfo := result.Info
|
||||
downloadPath := filepath.Join(snapsDir, fmt.Sprintf("%s_%d.snap", snapInfo.SuggestedName, snapInfo.Revision.N))
|
||||
|
||||
// Check if a delta can be applied
|
||||
if currentSnap != nil && len(result.Deltas) > 0 {
|
||||
for _, delta := range result.Deltas {
|
||||
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 {
|
||||
oldSnapPath := filepath.Join(snapsDir, fmt.Sprintf("%s_%d.snap", snapInfo.SuggestedName, delta.FromRevision))
|
||||
if fileExists(oldSnapPath) {
|
||||
if err := applyDelta(oldSnapPath, deltaPath, downloadPath); err == nil {
|
||||
verboseLog("Delta applied successfully for snap %s", snapInfo.SuggestedName)
|
||||
if result == nil || result.Info == nil {
|
||||
verboseLog("No updates available for snap. Skipping download and assertions.")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
snapInfo := result.Info
|
||||
downloadPath := filepath.Join(snapsDir, fmt.Sprintf("%s_%d.snap", snapInfo.SuggestedName, snapInfo.Revision.N))
|
||||
|
||||
// Check if a delta can be applied
|
||||
if currentSnap != nil && len(result.Deltas) > 0 {
|
||||
for _, delta := range result.Deltas {
|
||||
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 {
|
||||
oldSnapPath := filepath.Join(snapsDir, fmt.Sprintf("%s_%d.snap", snapInfo.SuggestedName, delta.FromRevision))
|
||||
if fileExists(oldSnapPath) {
|
||||
if err := applyDelta(oldSnapPath, deltaPath, downloadPath); err == nil {
|
||||
verboseLog("Delta applied successfully for snap %s", snapInfo.SuggestedName)
|
||||
// Download assertions after successful snap download
|
||||
if err := downloadAssertions(storeClient, snapInfo, assertionsDir); err != nil {
|
||||
return nil, fmt.Errorf("failed to download assertions for snap %s: %w", snapInfo.SuggestedName, err)
|
||||
}
|
||||
return snapInfo, nil // Successful delta application
|
||||
} else {
|
||||
verboseLog("Failed to apply delta for snap %s: %v", snapInfo.SuggestedName, err)
|
||||
}
|
||||
} else {
|
||||
verboseLog("Old snap file %s does not exist. Cannot apply delta.", oldSnapPath)
|
||||
}
|
||||
} else {
|
||||
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 err := downloadSnap(storeClient, snapInfo, downloadPath); err != nil {
|
||||
return nil, fmt.Errorf("failed to download snap %s: %w", snapInfo.SuggestedName, err)
|
||||
}
|
||||
|
||||
// Download assertions after successful snap download
|
||||
if err := downloadAssertions(storeClient, snapInfo, assertionsDir); err != nil {
|
||||
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)
|
||||
return snapInfo, nil
|
||||
return snapInfo, nil // Successful delta application
|
||||
} else {
|
||||
verboseLog("Failed to apply delta for snap %s: %v", snapInfo.SuggestedName, err)
|
||||
}
|
||||
} else {
|
||||
verboseLog("Old snap file %s does not exist. Cannot apply delta.", oldSnapPath)
|
||||
}
|
||||
} else {
|
||||
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 err := downloadSnap(storeClient, snapInfo, downloadPath); err != nil {
|
||||
return nil, fmt.Errorf("failed to download snap %s: %w", snapInfo.SuggestedName, err)
|
||||
}
|
||||
|
||||
// Download assertions after successful snap download
|
||||
if err := downloadAssertions(storeClient, snapInfo, assertionsDir); err != nil {
|
||||
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)
|
||||
return snapInfo, nil
|
||||
}
|
||||
|
||||
// applyDelta applies the downloaded delta using xdelta3.
|
||||
func applyDelta(oldSnapPath, deltaPath, newSnapPath string) error {
|
||||
verboseLog("Applying delta from %s to %s using %s", oldSnapPath, newSnapPath, deltaPath)
|
||||
|
||||
cmd := exec.Command("xdelta3", "-d", "-s", oldSnapPath, deltaPath, newSnapPath)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
verboseLog("xdelta3 output: %s", string(output))
|
||||
return fmt.Errorf("failed to apply delta: %v - %s", err, string(output))
|
||||
}
|
||||
return nil
|
||||
verboseLog("Applying delta from %s to %s using %s", oldSnapPath, newSnapPath, deltaPath)
|
||||
|
||||
cmd := exec.Command("xdelta3", "-d", "-s", oldSnapPath, deltaPath, newSnapPath)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
verboseLog("xdelta3 output: %s", string(output))
|
||||
return fmt.Errorf("failed to apply delta: %v - %s", err, string(output))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -1,220 +1,243 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/snapcore/snapd/snap"
|
||||
"github.com/snapcore/snapd/store"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/snapcore/snapd/snap"
|
||||
"github.com/snapcore/snapd/store"
|
||||
)
|
||||
|
||||
// SnapDetails struct remains unchanged
|
||||
type SnapDetails struct {
|
||||
InstanceName string
|
||||
Channel string
|
||||
CurrentSnap *store.CurrentSnap
|
||||
InstanceName string
|
||||
Channel string
|
||||
CurrentSnap *store.CurrentSnap
|
||||
Result *store.SnapActionResult
|
||||
}
|
||||
|
||||
// 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) {
|
||||
var snapDetailsList []SnapDetails
|
||||
|
||||
if processedSnaps[snapName] {
|
||||
verboseLog("Snap %s has already been processed. Skipping.", snapName)
|
||||
return snapDetailsList, nil // Added 0 for int64
|
||||
}
|
||||
|
||||
oldSnapPath, oldSnap := findPreviousSnap(snapsDir, assertionsDir, snapName)
|
||||
|
||||
var result *store.SnapActionResult
|
||||
var err error
|
||||
|
||||
// Fetch or refresh snap information
|
||||
if oldSnap == nil || oldSnap.SnapID == "" || oldSnap.Revision.N == 0 {
|
||||
result, err = fetchOrRefreshSnapInfo(snapName, nil, channel)
|
||||
if err != nil {
|
||||
return nil, err // Added 0 for int64
|
||||
}
|
||||
} else {
|
||||
result, err = fetchOrRefreshSnapInfo(snapName, oldSnap, channel)
|
||||
if err != nil && strings.Contains(err.Error(), "snap has no updates available") {
|
||||
result, err = fetchOrRefreshSnapInfo(snapName, nil, channel)
|
||||
if err != nil {
|
||||
return nil, err // Added 0 for int64
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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},
|
||||
}
|
||||
func collectSnapDependencies(snapName, channel, fallbackChannel, snapsDir, assertionsDir string) ([]SnapDetails, error) {
|
||||
var snapDetailsList []SnapDetails
|
||||
|
||||
if processedSnaps[snapName] {
|
||||
verboseLog("Snap %s has already been processed. Skipping.", snapName)
|
||||
return snapDetailsList, nil // Added 0 for int64
|
||||
}
|
||||
|
||||
oldSnapPath, oldSnap := findPreviousSnap(snapsDir, assertionsDir, snapName)
|
||||
|
||||
var result *store.SnapActionResult
|
||||
var err error
|
||||
|
||||
// Fetch or refresh snap information
|
||||
if oldSnap == nil || oldSnap.SnapID == "" || oldSnap.Revision.N == 0 {
|
||||
result, err = fetchOrRefreshSnapInfo(snapName, nil, channel)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "no snap revision available as specified") {
|
||||
result, err = fetchOrRefreshSnapInfo(snapName, nil, fallbackChannel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
result, err = fetchOrRefreshSnapInfo(snapName, oldSnap, channel)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "snap has no updates available") {
|
||||
result, err = fetchOrRefreshSnapInfo(snapName, nil, channel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if strings.Contains(err.Error(), "no snap revision available as specified") {
|
||||
result, err = fetchOrRefreshSnapInfo(snapName, oldSnap, fallbackChannel)
|
||||
if err != nil {
|
||||
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)
|
||||
if snapInCurrentSnaps {
|
||||
removeSnapFromCurrentSnaps(snapName, oldRevision)
|
||||
}
|
||||
currentSnaps = append(currentSnaps, newSnap)
|
||||
processedSnaps[snapName] = true
|
||||
|
||||
needsUpdate := (oldSnapPath == "" || oldSnap.Revision.N < info.Revision.N)
|
||||
|
||||
if needsUpdate {
|
||||
snapDetailsList = append(snapDetailsList, SnapDetails{
|
||||
InstanceName: snapName,
|
||||
Channel: channel,
|
||||
CurrentSnap: newSnap,
|
||||
Result: result,
|
||||
})
|
||||
} else {
|
||||
// Mark the snap as required even if no update is needed
|
||||
requiredSnaps[snapName] = true
|
||||
}
|
||||
|
||||
// Safely handle dependencies
|
||||
tracker := snap.SimplePrereqTracker{}
|
||||
missingPrereqs := tracker.MissingProviderContentTags(info, nil)
|
||||
for prereq := range missingPrereqs {
|
||||
if !processedSnaps[prereq] {
|
||||
verboseLog("Collecting dependencies for prerequisite snap: %s for %s", prereq, snapName)
|
||||
prereqDetails, err := collectSnapDependencies(prereq, "stable", snapsDir, assertionsDir)
|
||||
if err != nil {
|
||||
// Additional logging for dependency resolution issues
|
||||
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
|
||||
}
|
||||
snapDetailsList = append(snapDetailsList, prereqDetails...)
|
||||
}
|
||||
}
|
||||
|
||||
// Also handle base snaps safely
|
||||
if info.Base != "" && !processedSnaps[info.Base] {
|
||||
verboseLog("Collecting dependencies for base snap: %s for %s", info.Base, snapName)
|
||||
baseDetails, err := collectSnapDependencies(info.Base, "stable", snapsDir, assertionsDir)
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
snapDetailsList = append(snapDetailsList, baseDetails...)
|
||||
}
|
||||
|
||||
return snapDetailsList, nil
|
||||
currentSnaps = append(currentSnaps, newSnap)
|
||||
processedSnaps[snapName] = true
|
||||
|
||||
needsUpdate := (oldSnapPath == "" || oldSnap.Revision.N < info.Revision.N)
|
||||
|
||||
if needsUpdate {
|
||||
snapDetailsList = append(snapDetailsList, SnapDetails{
|
||||
InstanceName: snapName,
|
||||
Channel: channel,
|
||||
CurrentSnap: newSnap,
|
||||
Result: result,
|
||||
})
|
||||
} else {
|
||||
// Mark the snap as required even if no update is needed
|
||||
requiredSnaps[snapName] = true
|
||||
}
|
||||
|
||||
// Safely handle dependencies
|
||||
tracker := snap.SimplePrereqTracker{}
|
||||
missingPrereqs := tracker.MissingProviderContentTags(info, nil)
|
||||
for prereq := range missingPrereqs {
|
||||
if !processedSnaps[prereq] {
|
||||
verboseLog("Collecting dependencies for prerequisite snap: %s for %s", prereq, snapName)
|
||||
prereqDetails, err := collectSnapDependencies(prereq, channel, fallbackChannel, snapsDir, assertionsDir)
|
||||
if err != nil {
|
||||
// Additional logging for dependency resolution issues
|
||||
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
|
||||
}
|
||||
snapDetailsList = append(snapDetailsList, prereqDetails...)
|
||||
}
|
||||
}
|
||||
|
||||
// Also handle base snaps safely
|
||||
if info.Base != "" && !processedSnaps[info.Base] {
|
||||
verboseLog("Collecting dependencies for base snap: %s for %s", info.Base, snapName)
|
||||
baseDetails, err := collectSnapDependencies(info.Base, channel, fallbackChannel, snapsDir, assertionsDir)
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
snapDetailsList = append(snapDetailsList, baseDetails...)
|
||||
}
|
||||
|
||||
return snapDetailsList, nil
|
||||
}
|
||||
|
||||
// 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.
|
||||
func processSnap(snapDetails SnapDetails, snapsDir, assertionsDir string) error {
|
||||
verboseLog("Processing snap: %s on channel: %s", snapDetails.InstanceName, snapDetails.Channel)
|
||||
|
||||
// Proceed with downloading the snap (either full or delta) using downloadAndApplySnap
|
||||
snapInfo, err := downloadAndApplySnap(storeClient, snapDetails.Result, snapsDir, assertionsDir, snapDetails.CurrentSnap)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to download snap %s: %w", snapDetails.InstanceName, err)
|
||||
}
|
||||
|
||||
// Mark the snap as required after successful download and application
|
||||
requiredSnaps[snapDetails.InstanceName] = true
|
||||
verboseLog("Downloaded and applied snap: %s, revision: %d", snapInfo.SuggestedName, snapInfo.Revision.N)
|
||||
return nil
|
||||
verboseLog("Processing snap: %s on channel: %s", snapDetails.InstanceName, snapDetails.Channel)
|
||||
|
||||
// Proceed with downloading the snap (either full or delta) using downloadAndApplySnap
|
||||
snapInfo, err := downloadAndApplySnap(storeClient, snapDetails.Result, snapsDir, assertionsDir, snapDetails.CurrentSnap)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to download snap %s: %w", snapDetails.InstanceName, err)
|
||||
}
|
||||
|
||||
// Mark the snap as required after successful download and application
|
||||
requiredSnaps[snapDetails.InstanceName] = true
|
||||
verboseLog("Downloaded and applied snap: %s, revision: %d", snapInfo.SuggestedName, snapInfo.Revision.N)
|
||||
return nil
|
||||
}
|
||||
|
||||
// fetchOrRefreshSnapInfo retrieves snap information and returns the SnapActionResult including deltas.
|
||||
func fetchOrRefreshSnapInfo(snapName string, currentSnap *store.CurrentSnap, channel string) (*store.SnapActionResult, error) {
|
||||
var actions []*store.SnapAction
|
||||
var actions []*store.SnapAction
|
||||
var includeSnap []*store.CurrentSnap
|
||||
if currentSnap != nil {
|
||||
verboseLog("Crafting refresh SnapAction for %s", snapName)
|
||||
actions = append(actions, &store.SnapAction{
|
||||
Action: "refresh",
|
||||
SnapID: currentSnap.SnapID,
|
||||
InstanceName: snapName,
|
||||
Channel: channel,
|
||||
})
|
||||
if currentSnap != nil {
|
||||
verboseLog("Crafting refresh SnapAction for %s", snapName)
|
||||
actions = append(actions, &store.SnapAction{
|
||||
Action: "refresh",
|
||||
SnapID: currentSnap.SnapID,
|
||||
InstanceName: snapName,
|
||||
Channel: channel,
|
||||
})
|
||||
includeSnap = []*store.CurrentSnap{currentSnap}
|
||||
} else {
|
||||
verboseLog("Crafting install SnapAction for %s", snapName)
|
||||
actions = append(actions, &store.SnapAction{
|
||||
Action: "install",
|
||||
InstanceName: snapName,
|
||||
Channel: channel,
|
||||
})
|
||||
} else {
|
||||
verboseLog("Crafting install SnapAction for %s", snapName)
|
||||
actions = append(actions, &store.SnapAction{
|
||||
Action: "install",
|
||||
InstanceName: snapName,
|
||||
Channel: channel,
|
||||
})
|
||||
includeSnap = []*store.CurrentSnap{}
|
||||
}
|
||||
}
|
||||
|
||||
results, _, err := storeClient.SnapAction(ctx, includeSnap, actions, nil, nil, nil)
|
||||
if err != nil {
|
||||
verboseLog("SnapAction error for %s: %v", snapName, err)
|
||||
if strings.Contains(err.Error(), "snap has no updates available") && currentSnap != nil {
|
||||
results, _, err := storeClient.SnapAction(ctx, includeSnap, actions, nil, nil, nil)
|
||||
if err != nil {
|
||||
verboseLog("SnapAction error for %s: %v", snapName, err)
|
||||
if strings.Contains(err.Error(), "snap has no updates available") && currentSnap != nil {
|
||||
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 {
|
||||
return nil, fmt.Errorf("no snap info returned for snap %s", snapName)
|
||||
}
|
||||
if len(results) == 0 || results[0].Info == nil {
|
||||
return nil, fmt.Errorf("no snap info returned for snap %s", snapName)
|
||||
}
|
||||
|
||||
result := &results[0]
|
||||
info := result.Info
|
||||
result := &results[0]
|
||||
info := result.Info
|
||||
|
||||
// Validate necessary fields in the snap information
|
||||
if info.SnapID == "" || info.Revision.N == 0 {
|
||||
return nil, fmt.Errorf("invalid snap information for %s: SnapID or Revision is missing", snapName)
|
||||
}
|
||||
// Validate necessary fields in the snap information
|
||||
if info.SnapID == "" || info.Revision.N == 0 {
|
||||
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)
|
||||
return result, nil
|
||||
verboseLog("Fetched latest snap info for %s: SnapID: %s, Revision: %d", snapName, info.SnapID, info.Revision.N)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// findPreviousSnap locates the previous snap revision in the downloads directory.
|
||||
func findPreviousSnap(downloadDir, assertionsDir, snapName string) (string, *store.CurrentSnap) {
|
||||
var currentSnap store.CurrentSnap
|
||||
files, err := os.ReadDir(downloadDir)
|
||||
if err != nil {
|
||||
verboseLog("Error reading directory: %v", err)
|
||||
return "", nil
|
||||
}
|
||||
|
||||
var latestRevision int
|
||||
var latestSnapPath string
|
||||
|
||||
for _, file := range files {
|
||||
if strings.HasPrefix(file.Name(), snapName+"_") && strings.HasSuffix(file.Name(), ".snap") {
|
||||
revisionStr := extractRevisionFromFile(file.Name())
|
||||
if revisionStr == "" {
|
||||
verboseLog("Failed to extract revision from file name: %s", file.Name())
|
||||
continue
|
||||
}
|
||||
revision, err := strconv.Atoi(revisionStr)
|
||||
if err != nil {
|
||||
verboseLog("Failed to parse revision number for file %s: %v", file.Name(), err)
|
||||
continue
|
||||
}
|
||||
verboseLog("Found %s with revision %d", file.Name(), revision)
|
||||
|
||||
if revision > latestRevision {
|
||||
latestRevision = revision
|
||||
latestSnapPath = filepath.Join(downloadDir, file.Name())
|
||||
|
||||
// Parse the corresponding assertion file
|
||||
assertFilePath := filepath.Join(assertionsDir, strings.Replace(file.Name(), ".snap", ".assert", 1))
|
||||
currentSnap = parseSnapInfo(assertFilePath, snapName)
|
||||
currentSnap.Revision.N = revision
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if latestSnapPath != "" {
|
||||
return latestSnapPath, ¤tSnap
|
||||
}
|
||||
return "", nil
|
||||
var currentSnap store.CurrentSnap
|
||||
files, err := os.ReadDir(downloadDir)
|
||||
if err != nil {
|
||||
verboseLog("Error reading directory: %v", err)
|
||||
return "", nil
|
||||
}
|
||||
|
||||
var latestRevision int
|
||||
var latestSnapPath string
|
||||
|
||||
for _, file := range files {
|
||||
if strings.HasPrefix(file.Name(), snapName+"_") && strings.HasSuffix(file.Name(), ".snap") {
|
||||
revisionStr := extractRevisionFromFile(file.Name())
|
||||
if revisionStr == "" {
|
||||
verboseLog("Failed to extract revision from file name: %s", file.Name())
|
||||
continue
|
||||
}
|
||||
revision, err := strconv.Atoi(revisionStr)
|
||||
if err != nil {
|
||||
verboseLog("Failed to parse revision number for file %s: %v", file.Name(), err)
|
||||
continue
|
||||
}
|
||||
verboseLog("Found %s with revision %d", file.Name(), revision)
|
||||
|
||||
if revision > latestRevision {
|
||||
latestRevision = revision
|
||||
latestSnapPath = filepath.Join(downloadDir, file.Name())
|
||||
|
||||
// Parse the corresponding assertion file
|
||||
assertFilePath := filepath.Join(assertionsDir, strings.Replace(file.Name(), ".snap", ".assert", 1))
|
||||
currentSnap = parseSnapInfo(assertFilePath, snapName)
|
||||
currentSnap.Revision.N = revision
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if latestSnapPath != "" {
|
||||
return latestSnapPath, ¤tSnap
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
@ -1,114 +1,114 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
"github.com/snapcore/snapd/store"
|
||||
"gopkg.in/yaml.v3"
|
||||
"github.com/snapcore/snapd/store"
|
||||
)
|
||||
|
||||
// initializeSeedYaml ensures that seed.yaml exists; if not, creates it.
|
||||
func initializeSeedYaml(seedYaml string) {
|
||||
if _, err := os.Stat(seedYaml); os.IsNotExist(err) {
|
||||
file, err := os.Create(seedYaml)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create seed.yaml: %v", err)
|
||||
}
|
||||
defer file.Close()
|
||||
file.WriteString("snaps:\n")
|
||||
}
|
||||
if _, err := os.Stat(seedYaml); os.IsNotExist(err) {
|
||||
file, err := os.Create(seedYaml)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create seed.yaml: %v", err)
|
||||
}
|
||||
defer file.Close()
|
||||
file.WriteString("snaps:\n")
|
||||
}
|
||||
}
|
||||
|
||||
// loadSeedData loads seed data from seed.yaml
|
||||
func loadSeedData(seedYaml string) seed {
|
||||
file, err := ioutil.ReadFile(seedYaml)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to read seed.yaml: %v", err)
|
||||
}
|
||||
file, err := ioutil.ReadFile(seedYaml)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to read seed.yaml: %v", err)
|
||||
}
|
||||
|
||||
var seedData seed
|
||||
if err := yaml.Unmarshal(file, &seedData); err != nil {
|
||||
log.Fatalf("Failed to parse seed.yaml: %v", err)
|
||||
}
|
||||
var seedData seed
|
||||
if err := yaml.Unmarshal(file, &seedData); err != nil {
|
||||
log.Fatalf("Failed to parse seed.yaml: %v", err)
|
||||
}
|
||||
|
||||
return seedData
|
||||
return seedData
|
||||
}
|
||||
|
||||
// loadExistingSnaps loads snaps from seed.yaml into a map
|
||||
func loadExistingSnaps(seedYaml string) map[string]bool {
|
||||
file, err := ioutil.ReadFile(seedYaml)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to read seed.yaml: %v", err)
|
||||
}
|
||||
|
||||
var seedData seed
|
||||
if err := yaml.Unmarshal(file, &seedData); err != nil {
|
||||
log.Fatalf("Failed to parse seed.yaml: %v", err)
|
||||
}
|
||||
|
||||
existing := make(map[string]bool)
|
||||
for _, snap := range seedData.Snaps {
|
||||
existing[snap.Name] = true
|
||||
verboseLog("Found %s in seed.yaml\n", snap.Name)
|
||||
}
|
||||
return existing
|
||||
file, err := ioutil.ReadFile(seedYaml)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to read seed.yaml: %v", err)
|
||||
}
|
||||
|
||||
var seedData seed
|
||||
if err := yaml.Unmarshal(file, &seedData); err != nil {
|
||||
log.Fatalf("Failed to parse seed.yaml: %v", err)
|
||||
}
|
||||
|
||||
existing := make(map[string]bool)
|
||||
for _, snap := range seedData.Snaps {
|
||||
existing[snap.Name] = true
|
||||
verboseLog("Found %s in seed.yaml\n", snap.Name)
|
||||
}
|
||||
return existing
|
||||
}
|
||||
|
||||
// updateSeedYaml updates the seed.yaml file with the current required snaps
|
||||
func updateSeedYaml(snapsDir, seedYaml string, currentSnaps []*store.CurrentSnap) error {
|
||||
// Log the snaps to be written
|
||||
verboseLog("CurrentSnaps to be written to seed.yaml:")
|
||||
for _, snapInfo := range currentSnaps {
|
||||
verboseLog("- %s_%d.snap", snapInfo.InstanceName, snapInfo.Revision.N)
|
||||
}
|
||||
|
||||
// Load existing seed data
|
||||
file, err := ioutil.ReadFile(seedYaml)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read seed.yaml: %w", err)
|
||||
}
|
||||
|
||||
var seedData seed
|
||||
if err := yaml.Unmarshal(file, &seedData); err != nil {
|
||||
return fmt.Errorf("failed to parse seed.yaml: %w", err)
|
||||
}
|
||||
|
||||
// Clear existing snaps
|
||||
seedData.Snaps = []struct {
|
||||
Name string `yaml:"name"`
|
||||
Channel string `yaml:"channel"`
|
||||
File string `yaml:"file"`
|
||||
}{}
|
||||
|
||||
// Populate seedData with currentSnaps
|
||||
for _, snapInfo := range currentSnaps {
|
||||
snapFileName := fmt.Sprintf("%s_%d.snap", snapInfo.InstanceName, snapInfo.Revision.N)
|
||||
snapData := struct {
|
||||
Name string `yaml:"name"`
|
||||
Channel string `yaml:"channel"`
|
||||
File string `yaml:"file"`
|
||||
}{
|
||||
Name: snapInfo.InstanceName,
|
||||
Channel: "stable", // Assuming 'stable' channel; modify as needed
|
||||
File: snapFileName,
|
||||
}
|
||||
seedData.Snaps = append(seedData.Snaps, snapData)
|
||||
}
|
||||
|
||||
// Marshal the updated seedData back to YAML
|
||||
updatedYAML, err := yaml.Marshal(&seedData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal updated seed data: %w", err)
|
||||
}
|
||||
|
||||
// Write the updated YAML back to seed.yaml
|
||||
if err := ioutil.WriteFile(seedYaml, updatedYAML, 0644); err != nil {
|
||||
return fmt.Errorf("failed to write updated seed.yaml: %w", err)
|
||||
}
|
||||
|
||||
verboseLog("Updated seed.yaml with current snaps.")
|
||||
return nil
|
||||
// Log the snaps to be written
|
||||
verboseLog("CurrentSnaps to be written to seed.yaml:")
|
||||
for _, snapInfo := range currentSnaps {
|
||||
verboseLog("- %s_%d.snap", snapInfo.InstanceName, snapInfo.Revision.N)
|
||||
}
|
||||
|
||||
// Load existing seed data
|
||||
file, err := ioutil.ReadFile(seedYaml)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read seed.yaml: %w", err)
|
||||
}
|
||||
|
||||
var seedData seed
|
||||
if err := yaml.Unmarshal(file, &seedData); err != nil {
|
||||
return fmt.Errorf("failed to parse seed.yaml: %w", err)
|
||||
}
|
||||
|
||||
// Clear existing snaps
|
||||
seedData.Snaps = []struct {
|
||||
Name string `yaml:"name"`
|
||||
Channel string `yaml:"channel"`
|
||||
File string `yaml:"file"`
|
||||
}{}
|
||||
|
||||
// Populate seedData with currentSnaps
|
||||
for _, snapInfo := range currentSnaps {
|
||||
snapFileName := fmt.Sprintf("%s_%d.snap", snapInfo.InstanceName, snapInfo.Revision.N)
|
||||
snapData := struct {
|
||||
Name string `yaml:"name"`
|
||||
Channel string `yaml:"channel"`
|
||||
File string `yaml:"file"`
|
||||
}{
|
||||
Name: snapInfo.InstanceName,
|
||||
Channel: "stable", // Assuming 'stable' channel; modify as needed
|
||||
File: snapFileName,
|
||||
}
|
||||
seedData.Snaps = append(seedData.Snaps, snapData)
|
||||
}
|
||||
|
||||
// Marshal the updated seedData back to YAML
|
||||
updatedYAML, err := yaml.Marshal(&seedData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal updated seed data: %w", err)
|
||||
}
|
||||
|
||||
// Write the updated YAML back to seed.yaml
|
||||
if err := ioutil.WriteFile(seedYaml, updatedYAML, 0644); err != nil {
|
||||
return fmt.Errorf("failed to write updated seed.yaml: %w", err)
|
||||
}
|
||||
|
||||
verboseLog("Updated seed.yaml with current snaps.")
|
||||
return nil
|
||||
}
|
||||
|
@ -1,108 +1,108 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// validateSeed validates the seed using snap debug
|
||||
func validateSeed(seedYaml string) error {
|
||||
cmd := exec.Command("snap", "debug", "validate-seed", seedYaml)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("validation failed with output: %s, error: %v", string(output), err)
|
||||
}
|
||||
verboseLog("Seed validation successful: %s", string(output))
|
||||
return nil
|
||||
cmd := exec.Command("snap", "debug", "validate-seed", seedYaml)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("validation failed with output: %s, error: %v", string(output), err)
|
||||
}
|
||||
verboseLog("Seed validation successful: %s", string(output))
|
||||
return nil
|
||||
}
|
||||
|
||||
// ensureAssertions ensures that essential assertions are present
|
||||
func ensureAssertions(assertionsDir string) {
|
||||
model := "generic-classic"
|
||||
brand := "generic"
|
||||
series := "16" // Hardcoded series as snap.Info does not have a Series field
|
||||
model := "generic-classic"
|
||||
brand := "generic"
|
||||
series := "16" // Hardcoded series as snap.Info does not have a Series field
|
||||
|
||||
modelAssertionPath := filepath.Join(assertionsDir, "model")
|
||||
accountKeyAssertionPath := filepath.Join(assertionsDir, "account-key")
|
||||
accountAssertionPath := filepath.Join(assertionsDir, "account")
|
||||
modelAssertionPath := filepath.Join(assertionsDir, "model")
|
||||
accountKeyAssertionPath := filepath.Join(assertionsDir, "account-key")
|
||||
accountAssertionPath := filepath.Join(assertionsDir, "account")
|
||||
|
||||
// Check and generate model assertion
|
||||
if _, err := os.Stat(modelAssertionPath); os.IsNotExist(err) {
|
||||
output, err := exec.Command("snap", "known", "--remote", "model", "series="+series, "model="+model, "brand-id="+brand).CombinedOutput()
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to fetch model assertion: %v, Output: %s", err, string(output))
|
||||
}
|
||||
if err := ioutil.WriteFile(modelAssertionPath, output, 0644); err != nil {
|
||||
log.Fatalf("Failed to write model assertion: %v", err)
|
||||
}
|
||||
verboseLog("Fetched and saved model assertion to %s", modelAssertionPath)
|
||||
}
|
||||
// Check and generate model assertion
|
||||
if _, err := os.Stat(modelAssertionPath); os.IsNotExist(err) {
|
||||
output, err := exec.Command("snap", "known", "--remote", "model", "series="+series, "model="+model, "brand-id="+brand).CombinedOutput()
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to fetch model assertion: %v, Output: %s", err, string(output))
|
||||
}
|
||||
if err := ioutil.WriteFile(modelAssertionPath, output, 0644); err != nil {
|
||||
log.Fatalf("Failed to write model assertion: %v", err)
|
||||
}
|
||||
verboseLog("Fetched and saved model assertion to %s", modelAssertionPath)
|
||||
}
|
||||
|
||||
// Generate account-key assertion if not exists
|
||||
if _, err := os.Stat(accountKeyAssertionPath); os.IsNotExist(err) {
|
||||
signKeySha3 := grepPattern(modelAssertionPath, "sign-key-sha3-384: ")
|
||||
if signKeySha3 == "" {
|
||||
log.Fatalf("Failed to extract sign-key-sha3-384 from model assertion.")
|
||||
}
|
||||
decodedSignKey, err := base64.StdEncoding.DecodeString(signKeySha3)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to decode sign-key-sha3-384: %v", err)
|
||||
}
|
||||
encodedSignKey := base64.StdEncoding.EncodeToString(decodedSignKey)
|
||||
output, err := exec.Command("snap", "known", "--remote", "account-key", "public-key-sha3-384="+encodedSignKey).CombinedOutput()
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to fetch account-key assertion: %v, Output: %s", err, string(output))
|
||||
}
|
||||
if err := ioutil.WriteFile(accountKeyAssertionPath, output, 0644); err != nil {
|
||||
log.Fatalf("Failed to write account-key assertion: %v", err)
|
||||
}
|
||||
verboseLog("Fetched and saved account-key assertion to %s", accountKeyAssertionPath)
|
||||
}
|
||||
// Generate account-key assertion if not exists
|
||||
if _, err := os.Stat(accountKeyAssertionPath); os.IsNotExist(err) {
|
||||
signKeySha3 := grepPattern(modelAssertionPath, "sign-key-sha3-384: ")
|
||||
if signKeySha3 == "" {
|
||||
log.Fatalf("Failed to extract sign-key-sha3-384 from model assertion.")
|
||||
}
|
||||
decodedSignKey, err := base64.StdEncoding.DecodeString(signKeySha3)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to decode sign-key-sha3-384: %v", err)
|
||||
}
|
||||
encodedSignKey := base64.StdEncoding.EncodeToString(decodedSignKey)
|
||||
output, err := exec.Command("snap", "known", "--remote", "account-key", "public-key-sha3-384="+encodedSignKey).CombinedOutput()
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to fetch account-key assertion: %v, Output: %s", err, string(output))
|
||||
}
|
||||
if err := ioutil.WriteFile(accountKeyAssertionPath, output, 0644); err != nil {
|
||||
log.Fatalf("Failed to write account-key assertion: %v", err)
|
||||
}
|
||||
verboseLog("Fetched and saved account-key assertion to %s", accountKeyAssertionPath)
|
||||
}
|
||||
|
||||
// Generate account assertion if not exists
|
||||
if _, err := os.Stat(accountAssertionPath); os.IsNotExist(err) {
|
||||
accountId := grepPattern(accountKeyAssertionPath, "account-id: ")
|
||||
if accountId == "" {
|
||||
log.Fatalf("Failed to extract account-id from account-key assertion.")
|
||||
}
|
||||
output, err := exec.Command("snap", "known", "--remote", "account", "account-id="+accountId).CombinedOutput()
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to fetch account assertion: %v, Output: %s", err, string(output))
|
||||
}
|
||||
if err := ioutil.WriteFile(accountAssertionPath, output, 0644); err != nil {
|
||||
log.Fatalf("Failed to write account assertion: %v", err)
|
||||
}
|
||||
verboseLog("Fetched and saved account assertion to %s", accountAssertionPath)
|
||||
}
|
||||
// Generate account assertion if not exists
|
||||
if _, err := os.Stat(accountAssertionPath); os.IsNotExist(err) {
|
||||
accountId := grepPattern(accountKeyAssertionPath, "account-id: ")
|
||||
if accountId == "" {
|
||||
log.Fatalf("Failed to extract account-id from account-key assertion.")
|
||||
}
|
||||
output, err := exec.Command("snap", "known", "--remote", "account", "account-id="+accountId).CombinedOutput()
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to fetch account assertion: %v, Output: %s", err, string(output))
|
||||
}
|
||||
if err := ioutil.WriteFile(accountAssertionPath, output, 0644); err != nil {
|
||||
log.Fatalf("Failed to write account assertion: %v", err)
|
||||
}
|
||||
verboseLog("Fetched and saved account assertion to %s", accountAssertionPath)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// grepPattern extracts a specific pattern from a file
|
||||
func grepPattern(filePath, pattern string) string {
|
||||
content, err := ioutil.ReadFile(filePath)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to read from file %s: %v", filePath, err)
|
||||
}
|
||||
lines := strings.Split(string(content), "\n")
|
||||
for _, line := range lines {
|
||||
if strings.Contains(line, pattern) {
|
||||
parts := strings.SplitN(line, ":", 2)
|
||||
if len(parts) == 2 {
|
||||
encodedValue := strings.TrimSpace(parts[1])
|
||||
// Check if the value is base64 encoded
|
||||
if decodedBytes, err := base64.StdEncoding.DecodeString(encodedValue); err == nil {
|
||||
return string(decodedBytes)
|
||||
}
|
||||
return encodedValue
|
||||
}
|
||||
}
|
||||
}
|
||||
log.Fatalf("Pattern %s not found in file %s", pattern, filePath)
|
||||
return ""
|
||||
content, err := ioutil.ReadFile(filePath)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to read from file %s: %v", filePath, err)
|
||||
}
|
||||
lines := strings.Split(string(content), "\n")
|
||||
for _, line := range lines {
|
||||
if strings.Contains(line, pattern) {
|
||||
parts := strings.SplitN(line, ":", 2)
|
||||
if len(parts) == 2 {
|
||||
encodedValue := strings.TrimSpace(parts[1])
|
||||
// Check if the value is base64 encoded
|
||||
if decodedBytes, err := base64.StdEncoding.DecodeString(encodedValue); err == nil {
|
||||
return string(decodedBytes)
|
||||
}
|
||||
return encodedValue
|
||||
}
|
||||
}
|
||||
}
|
||||
log.Fatalf("Pattern %s not found in file %s", pattern, filePath)
|
||||
return ""
|
||||
}
|
||||
|
Loading…
Reference in new issue