diff --git a/snapd-seed-glue/assertions.go b/snapd-seed-glue/assertions.go index 09b1637..0943bf6 100644 --- a/snapd-seed-glue/assertions.go +++ b/snapd-seed-glue/assertions.go @@ -1,131 +1,131 @@ package main import ( - "encoding/base64" - "encoding/hex" - "fmt" - "log" - "os" - "path/filepath" + "encoding/base64" + "encoding/hex" + "fmt" + "log" + "os" + "path/filepath" "strings" "time" - "github.com/snapcore/snapd/asserts" - "github.com/snapcore/snapd/snap" - "github.com/snapcore/snapd/store" - "gopkg.in/yaml.v3" + "github.com/snapcore/snapd/asserts" + "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/store" + "gopkg.in/yaml.v3" ) // 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. func downloadAssertions(storeClient *store.Store, snapInfo *snap.Info, downloadDir string) error { - // Define the path for the assertions file - assertionsPath := filepath.Join(downloadDir, fmt.Sprintf("%s_%d.assert", snapInfo.SuggestedName, snapInfo.Revision.N)) - - // Extract necessary fields from snapInfo - snapSHA := snapInfo.Sha3_384 - snapID := snapInfo.SnapID - publisherID := snapInfo.Publisher.ID - series := "16" // Consider making this dynamic if possible - - // Define assertion types - assertionTypes := map[string]*asserts.AssertionType{ - "snap-revision": asserts.SnapRevisionType, - "snap-declaration": asserts.SnapDeclarationType, - "account-key": asserts.AccountKeyType, - "account": asserts.AccountType, - } - - // Open the assertions file for writing - assertionsFile, err := os.Create(assertionsPath) - if err != nil { - return fmt.Errorf("failed to create assertions file: %w", err) - } - defer assertionsFile.Close() - - // Step 1: Fetch snap-declaration assertion - snapDecl, err := storeClient.Assertion(assertionTypes["snap-declaration"], []string{series, snapID}, nil) - if err != nil { - 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 - signKey, ok := snapDecl.Header("sign-key-sha3-384").(string) - if !ok || signKey == "" { - 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) - accountKeyAssertion, err := storeClient.Assertion(assertionTypes["account-key"], []string{signKey}, nil) - if err != nil { - return fmt.Errorf("failed to fetch account-key assertion for snap %s: %w", snapInfo.SuggestedName, err) - } - - // Step 4: Fetch account assertion using publisher-id - accountAssertion, err := storeClient.Assertion(assertionTypes["account"], []string{publisherID}, nil) - if err != nil { - return fmt.Errorf("failed to fetch account assertion for snap %s: %w", snapInfo.SuggestedName, err) - } - - // Step 5: Fetch snap-revision assertion - snapSHA384Bytes, err := hex.DecodeString(snapSHA) - if err != nil { - return fmt.Errorf("error decoding SHA3-384 hex string for snap %s: %w", snapInfo.SuggestedName, err) - } - snapSHA384Base64 := base64.RawURLEncoding.EncodeToString(snapSHA384Bytes) - //revisionKey := fmt.Sprintf("%s/global-upload", snapSHA384Base64) - revisionKey := fmt.Sprintf("%s/", snapSHA384Base64) - - snapRevisionAssertion, err := storeClient.Assertion(assertionTypes["snap-revision"], []string{revisionKey}, nil) - if err != nil { - 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 - } - - // Step 6: Write assertions in the desired order - // 1. account-key - writeAssertion("account-key", accountKeyAssertion, assertionsFile) - - // 2. account - writeAssertion("account", accountAssertion, assertionsFile) - - // 3. snap-declaration - writeAssertion("snap-declaration", snapDecl, assertionsFile) - - // 4. snap-revision (if fetched successfully) - if snapRevisionAssertion != nil { - writeAssertion("snap-revision", snapRevisionAssertion, assertionsFile) - } - - verboseLog("Assertions downloaded and saved to: %s", assertionsPath) - return nil + // Define the path for the assertions file + assertionsPath := filepath.Join(downloadDir, fmt.Sprintf("%s_%d.assert", snapInfo.SuggestedName, snapInfo.Revision.N)) + + // Extract necessary fields from snapInfo + snapSHA := snapInfo.Sha3_384 + snapID := snapInfo.SnapID + publisherID := snapInfo.Publisher.ID + series := "16" // Consider making this dynamic if possible + + // Define assertion types + assertionTypes := map[string]*asserts.AssertionType{ + "snap-revision": asserts.SnapRevisionType, + "snap-declaration": asserts.SnapDeclarationType, + "account-key": asserts.AccountKeyType, + "account": asserts.AccountType, + } + + // Open the assertions file for writing + assertionsFile, err := os.Create(assertionsPath) + if err != nil { + return fmt.Errorf("failed to create assertions file: %w", err) + } + defer assertionsFile.Close() + + // Step 1: Fetch snap-declaration assertion + snapDecl, err := storeClient.Assertion(assertionTypes["snap-declaration"], []string{series, snapID}, nil) + if err != nil { + 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 + signKey, ok := snapDecl.Header("sign-key-sha3-384").(string) + if !ok || signKey == "" { + 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) + accountKeyAssertion, err := storeClient.Assertion(assertionTypes["account-key"], []string{signKey}, nil) + if err != nil { + return fmt.Errorf("failed to fetch account-key assertion for snap %s: %w", snapInfo.SuggestedName, err) + } + + // Step 4: Fetch account assertion using publisher-id + accountAssertion, err := storeClient.Assertion(assertionTypes["account"], []string{publisherID}, nil) + if err != nil { + return fmt.Errorf("failed to fetch account assertion for snap %s: %w", snapInfo.SuggestedName, err) + } + + // Step 5: Fetch snap-revision assertion + snapSHA384Bytes, err := hex.DecodeString(snapSHA) + if err != nil { + return fmt.Errorf("error decoding SHA3-384 hex string for snap %s: %w", snapInfo.SuggestedName, err) + } + snapSHA384Base64 := base64.RawURLEncoding.EncodeToString(snapSHA384Bytes) + //revisionKey := fmt.Sprintf("%s/global-upload", snapSHA384Base64) + revisionKey := fmt.Sprintf("%s/", snapSHA384Base64) + + snapRevisionAssertion, err := storeClient.Assertion(assertionTypes["snap-revision"], []string{revisionKey}, nil) + if err != nil { + 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 + } + + // Step 6: Write assertions in the desired order + // 1. account-key + writeAssertion("account-key", accountKeyAssertion, assertionsFile) + + // 2. account + writeAssertion("account", accountAssertion, assertionsFile) + + // 3. snap-declaration + writeAssertion("snap-declaration", snapDecl, assertionsFile) + + // 4. snap-revision (if fetched successfully) + if snapRevisionAssertion != nil { + writeAssertion("snap-revision", snapRevisionAssertion, assertionsFile) + } + + verboseLog("Assertions downloaded and saved to: %s", assertionsPath) + return nil } func writeAssertion(assertionType string, assertion asserts.Assertion, file *os.File) { - fieldOrder := map[string][]string{ - "account-key": { - "type", "authority-id", "revision", "public-key-sha3-384", - "account-id", "name", "since", "body-length", "sign-key-sha3-384", - }, - "account": { - "type", "authority-id", "revision", "account-id", "display-name", - "timestamp", "username", "validation", "sign-key-sha3-384", - }, - "snap-declaration": { - "type", "format", "authority-id", "revision", "series", "snap-id", - "aliases", "auto-aliases", "plugs", "publisher-id", "slots", - "snap-name", "timestamp", "sign-key-sha3-384", - }, - "snap-revision": { - "type", "authority-id", "snap-sha3-384", "developer-id", - "provenance", "snap-id", "snap-revision", "snap-size", - "timestamp", "sign-key-sha3-384", - }, - } - - body := assertion.Body() - bodyLength := len(body) - headers := assertion.Headers() + fieldOrder := map[string][]string{ + "account-key": { + "type", "authority-id", "revision", "public-key-sha3-384", + "account-id", "name", "since", "body-length", "sign-key-sha3-384", + }, + "account": { + "type", "authority-id", "revision", "account-id", "display-name", + "timestamp", "username", "validation", "sign-key-sha3-384", + }, + "snap-declaration": { + "type", "format", "authority-id", "revision", "series", "snap-id", + "aliases", "auto-aliases", "plugs", "publisher-id", "slots", + "snap-name", "timestamp", "sign-key-sha3-384", + }, + "snap-revision": { + "type", "authority-id", "snap-sha3-384", "developer-id", + "provenance", "snap-id", "snap-revision", "snap-size", + "timestamp", "sign-key-sha3-384", + }, + } + + body := assertion.Body() + bodyLength := len(body) + headers := assertion.Headers() // Only write the account assertion if it is not Canonical if assertionType == "account" { @@ -147,60 +147,60 @@ func writeAssertion(assertionType string, assertion asserts.Assertion, file *os. } } - // Write headers in the specified order - for _, key := range fieldOrder[assertionType] { - value, exists := headers[key] - if !exists || value == "" { - continue - } - if key == "type" { - fmt.Fprintf(file, "%s: %s\n", key, assertionType) - } else if key == "body-length" && bodyLength > 0 { - file.WriteString(fmt.Sprintf("body-length: %d\n", bodyLength)) - continue - } else if isComplexField(key) { - fmt.Fprintf(file, "%s:\n", key) - serializeComplexField(value, file) - } else { - fmt.Fprintf(file, "%s: %s\n", key, value) - } - } + // Write headers in the specified order + for _, key := range fieldOrder[assertionType] { + value, exists := headers[key] + if !exists || value == "" { + continue + } + if key == "type" { + fmt.Fprintf(file, "%s: %s\n", key, assertionType) + } else if key == "body-length" && bodyLength > 0 { + file.WriteString(fmt.Sprintf("body-length: %d\n", bodyLength)) + continue + } else if isComplexField(key) { + fmt.Fprintf(file, "%s:\n", key) + serializeComplexField(value, file) + } else { + fmt.Fprintf(file, "%s: %s\n", key, value) + } + } file.WriteString("\n") - // Write the body if it exists - if bodyLength > 0 { - file.Write(body) - file.WriteString("\n\n") - } + // Write the body if it exists + if bodyLength > 0 { + file.Write(body) + file.WriteString("\n\n") + } - // Write the signature - _, signature := assertion.Signature() - file.Write(signature) + // Write the signature + _, signature := assertion.Signature() + file.Write(signature) if assertionType != "snap-revision" { file.WriteString("\n") } } func serializeComplexField(value interface{}, file *os.File) { - var buf strings.Builder - encoder := yaml.NewEncoder(&buf) - encoder.SetIndent(2) - defer encoder.Close() - - // Encode the value directly - if err := encoder.Encode(value); err != nil { - log.Fatalf("Error encoding YAML: %v", err) - } - - // Write the serialized YAML to the file with proper indentation - lines := strings.Split(buf.String(), "\n") - for _, line := range lines { - if line == "" { - continue - } - line = strings.ReplaceAll(line, `"true"`, "true") - line = strings.ReplaceAll(line, `"false"`, "false") - line = strings.ReplaceAll(line, `'*'`, "*") + var buf strings.Builder + encoder := yaml.NewEncoder(&buf) + encoder.SetIndent(2) + defer encoder.Close() + + // Encode the value directly + if err := encoder.Encode(value); err != nil { + log.Fatalf("Error encoding YAML: %v", err) + } + + // Write the serialized YAML to the file with proper indentation + lines := strings.Split(buf.String(), "\n") + for _, line := range lines { + if line == "" { + continue + } + line = strings.ReplaceAll(line, `"true"`, "true") + line = strings.ReplaceAll(line, `"false"`, "false") + line = strings.ReplaceAll(line, `'*'`, "*") // Check for dashes indicating list items if strings.HasPrefix(strings.TrimSpace(line), "-") && strings.Contains(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 file.WriteString(fmt.Sprintf(" %s\n", line)) } - } + } } // isComplexField checks if a field is complex (nested) in YAML 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" } diff --git a/snapd-seed-glue/cleanup.go b/snapd-seed-glue/cleanup.go index 832c945..05d8786 100644 --- a/snapd-seed-glue/cleanup.go +++ b/snapd-seed-glue/cleanup.go @@ -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) + } + } } diff --git a/snapd-seed-glue/download.go b/snapd-seed-glue/download.go index 15ed7a4..24fbfd6 100644 --- a/snapd-seed-glue/download.go +++ b/snapd-seed-glue/download.go @@ -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 } diff --git a/snapd-seed-glue/main.go b/snapd-seed-glue/main.go index b989451..b44b733 100644 --- a/snapd-seed-glue/main.go +++ b/snapd-seed-glue/main.go @@ -1,178 +1,179 @@ package main import ( - "context" - "flag" - "log" + "context" + "flag" + "log" "fmt" - "path/filepath" - "strings" + "path/filepath" + "strings" - "github.com/snapcore/snapd/snap" - "github.com/snapcore/snapd/store" + "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/store" ) // Seed structure for seed.yaml type seed struct { - Snaps []struct { - Name string `yaml:"name"` - Channel string `yaml:"channel"` - File string `yaml:"file"` - } `yaml:"snaps"` + Snaps []struct { + Name string `yaml:"name"` + Channel string `yaml:"channel"` + File string `yaml:"file"` + } `yaml:"snaps"` } var ( - ctx = context.Background() - storeClient *store.Store - verbose bool - currentSnaps []*store.CurrentSnap - requiredSnaps map[string]bool - processedSnaps = make(map[string]bool) + ctx = context.Background() + storeClient *store.Store + verbose bool + currentSnaps []*store.CurrentSnap + requiredSnaps map[string]bool + processedSnaps = make(map[string]bool) snapSizeMap = make(map[string]float64) totalSnapSize float64 ) type SnapInfo struct { - InstanceName string - SnapID string - Revision snap.Revision + InstanceName string + SnapID string + Revision snap.Revision } func main() { - // Override the default plug slot sanitizer - snap.SanitizePlugsSlots = sanitizePlugsSlots + // Override the default plug slot sanitizer + snap.SanitizePlugsSlots = sanitizePlugsSlots - // Initialize progress reporting - InitProgress() + // Initialize progress reporting + InitProgress() totalSnapSize = 0 - // Initialize the store client - storeClient = store.New(nil, nil) + // Initialize the store client + storeClient = store.New(nil, nil) - // Parse command-line flags - var seedDirectory string - flag.StringVar(&seedDirectory, "seed", "/var/lib/snapd/seed", "Specify the seed directory") - flag.BoolVar(&verbose, "verbose", false, "Enable verbose output") - flag.Parse() + // Parse command-line flags + var seedDirectory string + flag.StringVar(&seedDirectory, "seed", "/var/lib/snapd/seed", "Specify the seed directory") + flag.BoolVar(&verbose, "verbose", false, "Enable verbose output") + flag.Parse() if !verbose { fmt.Printf("2\tLoading existing snaps...\n") } - // Define directories based on the seed directory - snapsDir := filepath.Join(seedDirectory, "snaps") - assertionsDir := filepath.Join(seedDirectory, "assertions") - seedYaml := filepath.Join(seedDirectory, "seed.yaml") - - // Setup directories and seed.yaml - initializeDirectories(snapsDir, assertionsDir) - initializeSeedYaml(seedYaml) - - // Load existing snaps from seed.yaml - existingSnapsInYaml := loadExistingSnaps(seedYaml) - - // Populate currentSnaps based on existing snaps - for snapName := range existingSnapsInYaml { - snapInfo, err := getCurrentSnapInfo(assertionsDir, snapName) - if err != nil { - verboseLog("Failed to get info for existing snap %s: %v", snapName, err) - continue - } - currentSnaps = append(currentSnaps, snapInfo) - } - - // Process essential snaps - requiredSnaps = map[string]bool{"snapd": true, "bare": true} - for _, arg := range flag.Args() { - requiredSnaps[arg] = true - } + // Define directories based on the seed directory + snapsDir := filepath.Join(seedDirectory, "snaps") + assertionsDir := filepath.Join(seedDirectory, "assertions") + seedYaml := filepath.Join(seedDirectory, "seed.yaml") + + // Setup directories and seed.yaml + initializeDirectories(snapsDir, assertionsDir) + initializeSeedYaml(seedYaml) + + // Load existing snaps from seed.yaml + existingSnapsInYaml := loadExistingSnaps(seedYaml) + + // Populate currentSnaps based on existing snaps + for snapName := range existingSnapsInYaml { + snapInfo, err := getCurrentSnapInfo(assertionsDir, snapName) + if err != nil { + verboseLog("Failed to get info for existing snap %s: %v", snapName, err) + continue + } + currentSnaps = append(currentSnaps, snapInfo) + } + + // Process essential snaps + requiredSnaps = map[string]bool{"snapd": true, "bare": true} + for _, arg := range flag.Args() { + requiredSnaps[arg] = true + } if !verbose { fmt.Printf("4\tFetching information from the Snap Store...\n") } - // Collect snaps to process - snapsToProcess, err := collectSnapsToProcess(snapsDir, assertionsDir) - if err != nil { - log.Fatalf("Failed to collect snaps to process: %v", err) - } - - progressTracker.Finish("Finished collecting snap info") - - // Calculate the number of snaps to download - totalSnaps := len(snapsToProcess) - if totalSnaps == 0 { - verboseLog("No snaps to process.") - } else { - verboseLog("Total snaps to download: %d", totalSnaps) - } - - // Initialize variables to track download progress - completedSnaps := 0 - - // Update "Downloading snaps" step to 0% - progressTracker.UpdateStepProgress(0) - - // Process all the snaps that need updates - for _, snapDetails := range snapsToProcess { - if err := processSnap(snapDetails, snapsDir, assertionsDir); err != nil { - log.Fatalf("Failed to process snap %s: %v", snapDetails.InstanceName, err) - } - completedSnaps++ - - progressTracker.UpdateStepProgress(-1) - } - - // Mark "Downloading snaps" as complete - if totalSnaps > 0 { - progressTracker.Finish("Downloading snaps completed") - } else { - // If no snaps to download, skip to finalizing - progressTracker.NextStep() - } - - // Remove unnecessary snaps after processing dependencies - cleanUpCurrentSnaps(assertionsDir, snapsDir) - - // Update seed.yaml with the current required snaps - if err := updateSeedYaml(snapsDir, seedYaml, currentSnaps); err != nil { - log.Fatalf("Failed to update seed.yaml: %v", err) - } - - // Perform cleanup and validation tasks - removeStateJson(filepath.Join(seedDirectory, "..", "state.json")) - ensureAssertions(assertionsDir) - if err := validateSeed(seedYaml); err != nil { - log.Fatalf("Seed validation failed: %v", err) - } - cleanUpFiles(snapsDir, assertionsDir, seedYaml) - - // Mark "Finalizing" as complete - if progressTracker != nil { - progressTracker.Finish("Cleanup and validation completed") - } + // Collect snaps to process + snapsToProcess, err := collectSnapsToProcess(snapsDir, assertionsDir) + if err != nil { + log.Fatalf("Failed to collect snaps to process: %v", err) + } + + progressTracker.Finish("Finished collecting snap info") + + // Calculate the number of snaps to download + totalSnaps := len(snapsToProcess) + if totalSnaps == 0 { + verboseLog("No snaps to process.") + } else { + verboseLog("Total snaps to download: %d", totalSnaps) + } + + // Initialize variables to track download progress + completedSnaps := 0 + + // Update "Downloading snaps" step to 0% + progressTracker.UpdateStepProgress(0) + + // Process all the snaps that need updates + for _, snapDetails := range snapsToProcess { + if err := processSnap(snapDetails, snapsDir, assertionsDir); err != nil { + log.Fatalf("Failed to process snap %s: %v", snapDetails.InstanceName, err) + } + completedSnaps++ + + progressTracker.UpdateStepProgress(-1) + } + + // Mark "Downloading snaps" as complete + if totalSnaps > 0 { + progressTracker.Finish("Downloading snaps completed") + } else { + // If no snaps to download, skip to finalizing + progressTracker.NextStep() + } + + // Remove unnecessary snaps after processing dependencies + cleanUpCurrentSnaps(assertionsDir, snapsDir) + + // Update seed.yaml with the current required snaps + if err := updateSeedYaml(snapsDir, seedYaml, currentSnaps); err != nil { + log.Fatalf("Failed to update seed.yaml: %v", err) + } + + // Perform cleanup and validation tasks + removeStateJson(filepath.Join(seedDirectory, "..", "state.json")) + ensureAssertions(assertionsDir) + if err := validateSeed(seedYaml); err != nil { + log.Fatalf("Seed validation failed: %v", err) + } + cleanUpFiles(snapsDir, assertionsDir, seedYaml) + + // Mark "Finalizing" as complete + if progressTracker != nil { + progressTracker.Finish("Cleanup and validation completed") + } } // collectSnapsToProcess collects all snaps and their dependencies, returning only those that need updates func collectSnapsToProcess(snapsDir, assertionsDir string) ([]SnapDetails, error) { - var snapsToProcess []SnapDetails - - for snapEntry := range requiredSnaps { - // Extract channel if specified, default to "stable" - parts := strings.SplitN(snapEntry, "=", 2) - channel := "stable" - if len(parts) == 2 { - channel = parts[1] - } - snapName := parts[0] - - // Collect snap dependencies and their statuses - snapList, err := collectSnapDependencies(snapName, channel, snapsDir, assertionsDir) - if err != nil { - return nil, err - } - - // Append only those snaps that need updates - for _, snapDetails := range snapList { - verboseLog("Processing snap: %s with result: %v", snapDetails.InstanceName, snapDetails.Result) + var snapsToProcess []SnapDetails + + fallbackChannel := "latest/stable" + for snapEntry := range requiredSnaps { + // Extract channel if specified, default to "stable" + parts := strings.SplitN(snapEntry, "=", 2) + channel := "latest/stable/ubuntu-25.04" + if len(parts) == 2 { + channel = parts[1] + } + snapName := parts[0] + + // Collect snap dependencies and their statuses + snapList, err := collectSnapDependencies(snapName, channel, fallbackChannel, snapsDir, assertionsDir) + if err != nil { + return nil, err + } + + // Append only those snaps that need updates + for _, snapDetails := range snapList { + verboseLog("Processing snap: %s with result: %v", snapDetails.InstanceName, snapDetails.Result) if len(snapDetails.Result.Deltas) > 0 { for _, delta := range snapDetails.Result.Deltas { snapSize := float64(delta.Size) @@ -184,11 +185,11 @@ func collectSnapsToProcess(snapsDir, assertionsDir string) ([]SnapDetails, error snapSizeMap[snapDetails.Result.Info.SuggestedName] = 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 @@ -196,7 +197,7 @@ func sanitizePlugsSlots(info *snap.Info) {} // verboseLog logs messages only when verbose mode is enabled func verboseLog(format string, v ...interface{}) { - if verbose { - log.Printf(format, v...) - } + if verbose { + log.Printf(format, v...) + } } diff --git a/snapd-seed-glue/process.go b/snapd-seed-glue/process.go index e320763..a91cb2b 100644 --- a/snapd-seed-glue/process.go +++ b/snapd-seed-glue/process.go @@ -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 } diff --git a/snapd-seed-glue/seed.go b/snapd-seed-glue/seed.go index ce7979a..1475c28 100644 --- a/snapd-seed-glue/seed.go +++ b/snapd-seed-glue/seed.go @@ -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 } diff --git a/snapd-seed-glue/utils.go b/snapd-seed-glue/utils.go index 0aa2b0c..f89f082 100644 --- a/snapd-seed-glue/utils.go +++ b/snapd-seed-glue/utils.go @@ -1,106 +1,106 @@ package main import ( - "bufio" - "encoding/hex" - "fmt" - "io" - "log" - "os" + "bufio" + "encoding/hex" + "fmt" + "io" + "log" + "os" "path/filepath" - "strconv" - "strings" + "strconv" + "strings" - "github.com/snapcore/snapd/snap" - "github.com/snapcore/snapd/store" - "golang.org/x/crypto/sha3" + "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/store" + "golang.org/x/crypto/sha3" ) // verifyChecksum calculates the SHA3-384 checksum of a file and compares it with the expected checksum. func verifyChecksum(filePath, expectedChecksum string) (bool, error) { - file, err := os.Open(filePath) - if err != nil { - return false, fmt.Errorf("failed to open file for checksum verification: %w", err) - } - defer file.Close() - - hash := sha3.New384() - if _, err := io.Copy(hash, file); err != nil { - return false, fmt.Errorf("failed to calculate checksum: %w", err) - } - - calculatedChecksum := hex.EncodeToString(hash.Sum(nil)) - return strings.EqualFold(calculatedChecksum, expectedChecksum), nil + file, err := os.Open(filePath) + if err != nil { + return false, fmt.Errorf("failed to open file for checksum verification: %w", err) + } + defer file.Close() + + hash := sha3.New384() + if _, err := io.Copy(hash, file); err != nil { + return false, fmt.Errorf("failed to calculate checksum: %w", err) + } + + calculatedChecksum := hex.EncodeToString(hash.Sum(nil)) + return strings.EqualFold(calculatedChecksum, expectedChecksum), nil } // extractRevisionFromFile extracts the revision number from a file name by splitting at the last underscore. func extractRevisionFromFile(fileName string) string { - lastUnderscore := strings.LastIndex(fileName, "_") - if lastUnderscore == -1 { - return "" - } - revisionWithSuffix := fileName[lastUnderscore+1:] - // Handle both .snap and .assert suffixes - if strings.HasSuffix(revisionWithSuffix, ".snap") { - return strings.TrimSuffix(revisionWithSuffix, ".snap") - } else if strings.HasSuffix(revisionWithSuffix, ".assert") { - return strings.TrimSuffix(revisionWithSuffix, ".assert") - } - return revisionWithSuffix + lastUnderscore := strings.LastIndex(fileName, "_") + if lastUnderscore == -1 { + return "" + } + revisionWithSuffix := fileName[lastUnderscore+1:] + // Handle both .snap and .assert suffixes + if strings.HasSuffix(revisionWithSuffix, ".snap") { + return strings.TrimSuffix(revisionWithSuffix, ".snap") + } else if strings.HasSuffix(revisionWithSuffix, ".assert") { + return strings.TrimSuffix(revisionWithSuffix, ".assert") + } + return revisionWithSuffix } // fileExists checks if a file exists and is not a directory before we use it func fileExists(filename string) bool { - info, err := os.Stat(filename) - if os.IsNotExist(err) { - return false - } - return !info.IsDir() + info, err := os.Stat(filename) + if os.IsNotExist(err) { + return false + } + return !info.IsDir() } // parseSnapInfo extracts snap details from the assertion file and returns the snap's current revision. func parseSnapInfo(assertFilePath, snapName string) store.CurrentSnap { - currentSnap := store.CurrentSnap{InstanceName: snapName} - file, err := os.Open(assertFilePath) - if err != nil { - verboseLog("Failed to read assertion file: %v\n", err) - return currentSnap - } - defer file.Close() - - scanner := bufio.NewScanner(file) - for scanner.Scan() { - line := strings.TrimSpace(scanner.Text()) - if strings.HasPrefix(line, "snap-id:") { - currentSnap.SnapID = strings.TrimSpace(strings.SplitN(line, ":", 2)[1]) - } - if strings.HasPrefix(line, "snap-revision:") { - revisionStr := strings.TrimSpace(strings.SplitN(line, ":", 2)[1]) - revision, err := strconv.Atoi(revisionStr) - if err != nil { - verboseLog("Failed to parse snap-revision for snap %s: %v", snapName, err) - continue - } - currentSnap.Revision.N = revision - } - } - - // Validate that required fields are present - if currentSnap.SnapID == "" || currentSnap.Revision.N == 0 { - verboseLog("Incomplete snap info in assertion file: %s", assertFilePath) - } - - return currentSnap + currentSnap := store.CurrentSnap{InstanceName: snapName} + file, err := os.Open(assertFilePath) + if err != nil { + verboseLog("Failed to read assertion file: %v\n", err) + return currentSnap + } + defer file.Close() + + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if strings.HasPrefix(line, "snap-id:") { + currentSnap.SnapID = strings.TrimSpace(strings.SplitN(line, ":", 2)[1]) + } + if strings.HasPrefix(line, "snap-revision:") { + revisionStr := strings.TrimSpace(strings.SplitN(line, ":", 2)[1]) + revision, err := strconv.Atoi(revisionStr) + if err != nil { + verboseLog("Failed to parse snap-revision for snap %s: %v", snapName, err) + continue + } + currentSnap.Revision.N = revision + } + } + + // Validate that required fields are present + if currentSnap.SnapID == "" || currentSnap.Revision.N == 0 { + verboseLog("Incomplete snap info in assertion file: %s", assertFilePath) + } + + return currentSnap } // isSnapInCurrentSnaps checks if a snap is already in currentSnaps func isSnapInCurrentSnaps(snapName string) (bool, snap.Revision) { - for _, snap := range currentSnaps { - if snap.InstanceName == snapName { - return true, snap.Revision - } - } - return false, snap.Revision{} + for _, snap := range currentSnaps { + if snap.InstanceName == snapName { + return true, snap.Revision + } + } + return false, 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 func initializeDirectories(snapsDir, assertionsDir string) { - if err := os.MkdirAll(snapsDir, 0755); err != nil { - log.Fatalf("Failed to create snaps directory: %v", err) - } - if err := os.MkdirAll(assertionsDir, 0755); err != nil { - log.Fatalf("Failed to create assertions directory: %v", err) - } + if err := os.MkdirAll(snapsDir, 0755); err != nil { + log.Fatalf("Failed to create snaps directory: %v", err) + } + if err := os.MkdirAll(assertionsDir, 0755); err != nil { + log.Fatalf("Failed to create assertions directory: %v", err) + } } // getCurrentSnapInfo retrieves current snap information from assertions func getCurrentSnapInfo(assertionsDir, snapName string) (*store.CurrentSnap, error) { - assertionFiles, err := filepath.Glob(filepath.Join(assertionsDir, fmt.Sprintf("%s_*.assert", snapName))) - if err != nil || len(assertionFiles) == 0 { - return nil, fmt.Errorf("no assertion file found for snap: %s", snapName) - } - - assertionFile := assertionFiles[0] - currentSnap := parseSnapInfo(assertionFile, snapName) - if currentSnap.SnapID == "" || currentSnap.Revision.N == 0 { - 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) - return ¤tSnap, nil + assertionFiles, err := filepath.Glob(filepath.Join(assertionsDir, fmt.Sprintf("%s_*.assert", snapName))) + if err != nil || len(assertionFiles) == 0 { + return nil, fmt.Errorf("no assertion file found for snap: %s", snapName) + } + + assertionFile := assertionFiles[0] + currentSnap := parseSnapInfo(assertionFile, snapName) + if currentSnap.SnapID == "" || currentSnap.Revision.N == 0 { + 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) + return ¤tSnap, nil } // verifySnapIntegrity verifies the integrity of a snap file func verifySnapIntegrity(filePath, expectedChecksum string) bool { - checksumMatches, err := verifyChecksum(filePath, expectedChecksum) - if err != nil { - verboseLog("Checksum verification failed for %s: %v", filePath, err) - return false - } - return checksumMatches + checksumMatches, err := verifyChecksum(filePath, expectedChecksum) + if err != nil { + verboseLog("Checksum verification failed for %s: %v", filePath, err) + return false + } + return checksumMatches } diff --git a/snapd-seed-glue/validation.go b/snapd-seed-glue/validation.go index 118205d..319d232 100644 --- a/snapd-seed-glue/validation.go +++ b/snapd-seed-glue/validation.go @@ -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 "" }