Fix bad tabbing, default to the 25.04 branch
This commit is contained in:
parent
e57ad37808
commit
4d6176bc13
@ -1,131 +1,131 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/snapcore/snapd/asserts"
|
"github.com/snapcore/snapd/asserts"
|
||||||
"github.com/snapcore/snapd/snap"
|
"github.com/snapcore/snapd/snap"
|
||||||
"github.com/snapcore/snapd/store"
|
"github.com/snapcore/snapd/store"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
// downloadAssertions dynamically fetches the necessary assertions and saves them to a file.
|
// downloadAssertions dynamically fetches the necessary assertions and saves them to a file.
|
||||||
// It ensures that the account-key assertion is written first in the .assert file.
|
// It ensures that the account-key assertion is written first in the .assert file.
|
||||||
func downloadAssertions(storeClient *store.Store, snapInfo *snap.Info, downloadDir string) error {
|
func downloadAssertions(storeClient *store.Store, snapInfo *snap.Info, downloadDir string) error {
|
||||||
// Define the path for the assertions file
|
// Define the path for the assertions file
|
||||||
assertionsPath := filepath.Join(downloadDir, fmt.Sprintf("%s_%d.assert", snapInfo.SuggestedName, snapInfo.Revision.N))
|
assertionsPath := filepath.Join(downloadDir, fmt.Sprintf("%s_%d.assert", snapInfo.SuggestedName, snapInfo.Revision.N))
|
||||||
|
|
||||||
// Extract necessary fields from snapInfo
|
// Extract necessary fields from snapInfo
|
||||||
snapSHA := snapInfo.Sha3_384
|
snapSHA := snapInfo.Sha3_384
|
||||||
snapID := snapInfo.SnapID
|
snapID := snapInfo.SnapID
|
||||||
publisherID := snapInfo.Publisher.ID
|
publisherID := snapInfo.Publisher.ID
|
||||||
series := "16" // Consider making this dynamic if possible
|
series := "16" // Consider making this dynamic if possible
|
||||||
|
|
||||||
// Define assertion types
|
// Define assertion types
|
||||||
assertionTypes := map[string]*asserts.AssertionType{
|
assertionTypes := map[string]*asserts.AssertionType{
|
||||||
"snap-revision": asserts.SnapRevisionType,
|
"snap-revision": asserts.SnapRevisionType,
|
||||||
"snap-declaration": asserts.SnapDeclarationType,
|
"snap-declaration": asserts.SnapDeclarationType,
|
||||||
"account-key": asserts.AccountKeyType,
|
"account-key": asserts.AccountKeyType,
|
||||||
"account": asserts.AccountType,
|
"account": asserts.AccountType,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open the assertions file for writing
|
// Open the assertions file for writing
|
||||||
assertionsFile, err := os.Create(assertionsPath)
|
assertionsFile, err := os.Create(assertionsPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create assertions file: %w", err)
|
return fmt.Errorf("failed to create assertions file: %w", err)
|
||||||
}
|
}
|
||||||
defer assertionsFile.Close()
|
defer assertionsFile.Close()
|
||||||
|
|
||||||
// Step 1: Fetch snap-declaration assertion
|
// Step 1: Fetch snap-declaration assertion
|
||||||
snapDecl, err := storeClient.Assertion(assertionTypes["snap-declaration"], []string{series, snapID}, nil)
|
snapDecl, err := storeClient.Assertion(assertionTypes["snap-declaration"], []string{series, snapID}, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to fetch snap-declaration assertion for snap %s: %w", snapInfo.SuggestedName, err)
|
return fmt.Errorf("failed to fetch snap-declaration assertion for snap %s: %w", snapInfo.SuggestedName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 2: Extract sign-key-sha3-384 from snap-declaration
|
// Step 2: Extract sign-key-sha3-384 from snap-declaration
|
||||||
signKey, ok := snapDecl.Header("sign-key-sha3-384").(string)
|
signKey, ok := snapDecl.Header("sign-key-sha3-384").(string)
|
||||||
if !ok || signKey == "" {
|
if !ok || signKey == "" {
|
||||||
return fmt.Errorf("snap-declaration assertion missing 'sign-key-sha3-384' header for snap %s", snapInfo.SuggestedName)
|
return fmt.Errorf("snap-declaration assertion missing 'sign-key-sha3-384' header for snap %s", snapInfo.SuggestedName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 3: Fetch account-key assertion using sign-key-sha3-384 (no decoding)
|
// Step 3: Fetch account-key assertion using sign-key-sha3-384 (no decoding)
|
||||||
accountKeyAssertion, err := storeClient.Assertion(assertionTypes["account-key"], []string{signKey}, nil)
|
accountKeyAssertion, err := storeClient.Assertion(assertionTypes["account-key"], []string{signKey}, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to fetch account-key assertion for snap %s: %w", snapInfo.SuggestedName, err)
|
return fmt.Errorf("failed to fetch account-key assertion for snap %s: %w", snapInfo.SuggestedName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 4: Fetch account assertion using publisher-id
|
// Step 4: Fetch account assertion using publisher-id
|
||||||
accountAssertion, err := storeClient.Assertion(assertionTypes["account"], []string{publisherID}, nil)
|
accountAssertion, err := storeClient.Assertion(assertionTypes["account"], []string{publisherID}, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to fetch account assertion for snap %s: %w", snapInfo.SuggestedName, err)
|
return fmt.Errorf("failed to fetch account assertion for snap %s: %w", snapInfo.SuggestedName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 5: Fetch snap-revision assertion
|
// Step 5: Fetch snap-revision assertion
|
||||||
snapSHA384Bytes, err := hex.DecodeString(snapSHA)
|
snapSHA384Bytes, err := hex.DecodeString(snapSHA)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error decoding SHA3-384 hex string for snap %s: %w", snapInfo.SuggestedName, err)
|
return fmt.Errorf("error decoding SHA3-384 hex string for snap %s: %w", snapInfo.SuggestedName, err)
|
||||||
}
|
}
|
||||||
snapSHA384Base64 := base64.RawURLEncoding.EncodeToString(snapSHA384Bytes)
|
snapSHA384Base64 := base64.RawURLEncoding.EncodeToString(snapSHA384Bytes)
|
||||||
//revisionKey := fmt.Sprintf("%s/global-upload", snapSHA384Base64)
|
//revisionKey := fmt.Sprintf("%s/global-upload", snapSHA384Base64)
|
||||||
revisionKey := fmt.Sprintf("%s/", snapSHA384Base64)
|
revisionKey := fmt.Sprintf("%s/", snapSHA384Base64)
|
||||||
|
|
||||||
snapRevisionAssertion, err := storeClient.Assertion(assertionTypes["snap-revision"], []string{revisionKey}, nil)
|
snapRevisionAssertion, err := storeClient.Assertion(assertionTypes["snap-revision"], []string{revisionKey}, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
verboseLog("Failed to fetch snap-revision assertion for snap %s: %v", snapInfo.SuggestedName, err)
|
verboseLog("Failed to fetch snap-revision assertion for snap %s: %v", snapInfo.SuggestedName, err)
|
||||||
// Proceeding without snap-revision might be acceptable based on your use-case
|
// Proceeding without snap-revision might be acceptable based on your use-case
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 6: Write assertions in the desired order
|
// Step 6: Write assertions in the desired order
|
||||||
// 1. account-key
|
// 1. account-key
|
||||||
writeAssertion("account-key", accountKeyAssertion, assertionsFile)
|
writeAssertion("account-key", accountKeyAssertion, assertionsFile)
|
||||||
|
|
||||||
// 2. account
|
// 2. account
|
||||||
writeAssertion("account", accountAssertion, assertionsFile)
|
writeAssertion("account", accountAssertion, assertionsFile)
|
||||||
|
|
||||||
// 3. snap-declaration
|
// 3. snap-declaration
|
||||||
writeAssertion("snap-declaration", snapDecl, assertionsFile)
|
writeAssertion("snap-declaration", snapDecl, assertionsFile)
|
||||||
|
|
||||||
// 4. snap-revision (if fetched successfully)
|
// 4. snap-revision (if fetched successfully)
|
||||||
if snapRevisionAssertion != nil {
|
if snapRevisionAssertion != nil {
|
||||||
writeAssertion("snap-revision", snapRevisionAssertion, assertionsFile)
|
writeAssertion("snap-revision", snapRevisionAssertion, assertionsFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
verboseLog("Assertions downloaded and saved to: %s", assertionsPath)
|
verboseLog("Assertions downloaded and saved to: %s", assertionsPath)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeAssertion(assertionType string, assertion asserts.Assertion, file *os.File) {
|
func writeAssertion(assertionType string, assertion asserts.Assertion, file *os.File) {
|
||||||
fieldOrder := map[string][]string{
|
fieldOrder := map[string][]string{
|
||||||
"account-key": {
|
"account-key": {
|
||||||
"type", "authority-id", "revision", "public-key-sha3-384",
|
"type", "authority-id", "revision", "public-key-sha3-384",
|
||||||
"account-id", "name", "since", "body-length", "sign-key-sha3-384",
|
"account-id", "name", "since", "body-length", "sign-key-sha3-384",
|
||||||
},
|
},
|
||||||
"account": {
|
"account": {
|
||||||
"type", "authority-id", "revision", "account-id", "display-name",
|
"type", "authority-id", "revision", "account-id", "display-name",
|
||||||
"timestamp", "username", "validation", "sign-key-sha3-384",
|
"timestamp", "username", "validation", "sign-key-sha3-384",
|
||||||
},
|
},
|
||||||
"snap-declaration": {
|
"snap-declaration": {
|
||||||
"type", "format", "authority-id", "revision", "series", "snap-id",
|
"type", "format", "authority-id", "revision", "series", "snap-id",
|
||||||
"aliases", "auto-aliases", "plugs", "publisher-id", "slots",
|
"aliases", "auto-aliases", "plugs", "publisher-id", "slots",
|
||||||
"snap-name", "timestamp", "sign-key-sha3-384",
|
"snap-name", "timestamp", "sign-key-sha3-384",
|
||||||
},
|
},
|
||||||
"snap-revision": {
|
"snap-revision": {
|
||||||
"type", "authority-id", "snap-sha3-384", "developer-id",
|
"type", "authority-id", "snap-sha3-384", "developer-id",
|
||||||
"provenance", "snap-id", "snap-revision", "snap-size",
|
"provenance", "snap-id", "snap-revision", "snap-size",
|
||||||
"timestamp", "sign-key-sha3-384",
|
"timestamp", "sign-key-sha3-384",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
body := assertion.Body()
|
body := assertion.Body()
|
||||||
bodyLength := len(body)
|
bodyLength := len(body)
|
||||||
headers := assertion.Headers()
|
headers := assertion.Headers()
|
||||||
|
|
||||||
// Only write the account assertion if it is not Canonical
|
// Only write the account assertion if it is not Canonical
|
||||||
if assertionType == "account" {
|
if assertionType == "account" {
|
||||||
@ -147,60 +147,60 @@ func writeAssertion(assertionType string, assertion asserts.Assertion, file *os.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write headers in the specified order
|
// Write headers in the specified order
|
||||||
for _, key := range fieldOrder[assertionType] {
|
for _, key := range fieldOrder[assertionType] {
|
||||||
value, exists := headers[key]
|
value, exists := headers[key]
|
||||||
if !exists || value == "" {
|
if !exists || value == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if key == "type" {
|
if key == "type" {
|
||||||
fmt.Fprintf(file, "%s: %s\n", key, assertionType)
|
fmt.Fprintf(file, "%s: %s\n", key, assertionType)
|
||||||
} else if key == "body-length" && bodyLength > 0 {
|
} else if key == "body-length" && bodyLength > 0 {
|
||||||
file.WriteString(fmt.Sprintf("body-length: %d\n", bodyLength))
|
file.WriteString(fmt.Sprintf("body-length: %d\n", bodyLength))
|
||||||
continue
|
continue
|
||||||
} else if isComplexField(key) {
|
} else if isComplexField(key) {
|
||||||
fmt.Fprintf(file, "%s:\n", key)
|
fmt.Fprintf(file, "%s:\n", key)
|
||||||
serializeComplexField(value, file)
|
serializeComplexField(value, file)
|
||||||
} else {
|
} else {
|
||||||
fmt.Fprintf(file, "%s: %s\n", key, value)
|
fmt.Fprintf(file, "%s: %s\n", key, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
file.WriteString("\n")
|
file.WriteString("\n")
|
||||||
|
|
||||||
// Write the body if it exists
|
// Write the body if it exists
|
||||||
if bodyLength > 0 {
|
if bodyLength > 0 {
|
||||||
file.Write(body)
|
file.Write(body)
|
||||||
file.WriteString("\n\n")
|
file.WriteString("\n\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write the signature
|
// Write the signature
|
||||||
_, signature := assertion.Signature()
|
_, signature := assertion.Signature()
|
||||||
file.Write(signature)
|
file.Write(signature)
|
||||||
if assertionType != "snap-revision" {
|
if assertionType != "snap-revision" {
|
||||||
file.WriteString("\n")
|
file.WriteString("\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func serializeComplexField(value interface{}, file *os.File) {
|
func serializeComplexField(value interface{}, file *os.File) {
|
||||||
var buf strings.Builder
|
var buf strings.Builder
|
||||||
encoder := yaml.NewEncoder(&buf)
|
encoder := yaml.NewEncoder(&buf)
|
||||||
encoder.SetIndent(2)
|
encoder.SetIndent(2)
|
||||||
defer encoder.Close()
|
defer encoder.Close()
|
||||||
|
|
||||||
// Encode the value directly
|
// Encode the value directly
|
||||||
if err := encoder.Encode(value); err != nil {
|
if err := encoder.Encode(value); err != nil {
|
||||||
log.Fatalf("Error encoding YAML: %v", err)
|
log.Fatalf("Error encoding YAML: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write the serialized YAML to the file with proper indentation
|
// Write the serialized YAML to the file with proper indentation
|
||||||
lines := strings.Split(buf.String(), "\n")
|
lines := strings.Split(buf.String(), "\n")
|
||||||
for _, line := range lines {
|
for _, line := range lines {
|
||||||
if line == "" {
|
if line == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
line = strings.ReplaceAll(line, `"true"`, "true")
|
line = strings.ReplaceAll(line, `"true"`, "true")
|
||||||
line = strings.ReplaceAll(line, `"false"`, "false")
|
line = strings.ReplaceAll(line, `"false"`, "false")
|
||||||
line = strings.ReplaceAll(line, `'*'`, "*")
|
line = strings.ReplaceAll(line, `'*'`, "*")
|
||||||
// Check for dashes indicating list items
|
// Check for dashes indicating list items
|
||||||
if strings.HasPrefix(strings.TrimSpace(line), "-") && strings.Contains(line, ":") {
|
if strings.HasPrefix(strings.TrimSpace(line), "-") && strings.Contains(line, ":") {
|
||||||
before, after, found := strings.Cut(line, "- ")
|
before, after, found := strings.Cut(line, "- ")
|
||||||
@ -216,10 +216,10 @@ func serializeComplexField(value interface{}, file *os.File) {
|
|||||||
// For any other non-empty lines, indent them correctly
|
// For any other non-empty lines, indent them correctly
|
||||||
file.WriteString(fmt.Sprintf(" %s\n", line))
|
file.WriteString(fmt.Sprintf(" %s\n", line))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// isComplexField checks if a field is complex (nested) in YAML
|
// isComplexField checks if a field is complex (nested) in YAML
|
||||||
func isComplexField(key string) bool {
|
func isComplexField(key string) bool {
|
||||||
return key == "aliases" || key == "auto-aliases" || key == "plugs" || key == "slots" || key == "allow-installation" || key == "allow-connection"
|
return key == "aliases" || key == "auto-aliases" || key == "plugs" || key == "slots" || key == "allow-installation" || key == "allow-connection"
|
||||||
}
|
}
|
||||||
|
@ -1,152 +1,152 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/snapcore/snapd/store"
|
"github.com/snapcore/snapd/store"
|
||||||
)
|
)
|
||||||
|
|
||||||
// cleanUpFiles removes partial, old, and orphaned snap and assertion files from the download and assertions directories.
|
// cleanUpFiles removes partial, old, and orphaned snap and assertion files from the download and assertions directories.
|
||||||
func cleanUpFiles(snapsDir, assertionsDir, seedYaml string) {
|
func cleanUpFiles(snapsDir, assertionsDir, seedYaml string) {
|
||||||
verboseLog("Starting cleanup process...")
|
verboseLog("Starting cleanup process...")
|
||||||
|
|
||||||
// Load the seed.yaml data
|
// Load the seed.yaml data
|
||||||
seedData := loadSeedData(seedYaml)
|
seedData := loadSeedData(seedYaml)
|
||||||
|
|
||||||
// Create a map of valid snap and assertion files based on seed.yaml
|
// Create a map of valid snap and assertion files based on seed.yaml
|
||||||
validSnaps := make(map[string]bool)
|
validSnaps := make(map[string]bool)
|
||||||
validAssertions := make(map[string]bool)
|
validAssertions := make(map[string]bool)
|
||||||
|
|
||||||
// Populate valid snaps and assertions from the seed.yaml data
|
// Populate valid snaps and assertions from the seed.yaml data
|
||||||
for _, snap := range seedData.Snaps {
|
for _, snap := range seedData.Snaps {
|
||||||
// Ensure correct extraction of revision
|
// Ensure correct extraction of revision
|
||||||
revision := extractRevisionFromFile(snap.File)
|
revision := extractRevisionFromFile(snap.File)
|
||||||
if revision == "" {
|
if revision == "" {
|
||||||
verboseLog("Failed to extract revision from file name: %s", snap.File)
|
verboseLog("Failed to extract revision from file name: %s", snap.File)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
snapFileName := fmt.Sprintf("%s_%s.snap", snap.Name, revision)
|
snapFileName := fmt.Sprintf("%s_%s.snap", snap.Name, revision)
|
||||||
assertionFileName := fmt.Sprintf("%s_%s.assert", snap.Name, revision)
|
assertionFileName := fmt.Sprintf("%s_%s.assert", snap.Name, revision)
|
||||||
|
|
||||||
validSnaps[snapFileName] = true
|
validSnaps[snapFileName] = true
|
||||||
validAssertions[assertionFileName] = true
|
validAssertions[assertionFileName] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log valid snaps and assertions
|
// Log valid snaps and assertions
|
||||||
verboseLog("Valid Snaps: %v", validSnaps)
|
verboseLog("Valid Snaps: %v", validSnaps)
|
||||||
verboseLog("Valid Assertions: %v", validAssertions)
|
verboseLog("Valid Assertions: %v", validAssertions)
|
||||||
|
|
||||||
// Remove outdated or partial snap files
|
// Remove outdated or partial snap files
|
||||||
files, err := os.ReadDir(snapsDir)
|
files, err := os.ReadDir(snapsDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
verboseLog("Error reading snaps directory for cleanup: %v", err)
|
verboseLog("Error reading snaps directory for cleanup: %v", err)
|
||||||
} else {
|
} else {
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
filePath := filepath.Join(snapsDir, file.Name())
|
filePath := filepath.Join(snapsDir, file.Name())
|
||||||
if strings.HasSuffix(file.Name(), ".partial") || strings.HasSuffix(file.Name(), ".delta") {
|
if strings.HasSuffix(file.Name(), ".partial") || strings.HasSuffix(file.Name(), ".delta") {
|
||||||
verboseLog("Removing partial/delta file: %s\n", filePath)
|
verboseLog("Removing partial/delta file: %s\n", filePath)
|
||||||
if err := os.Remove(filePath); err != nil {
|
if err := os.Remove(filePath); err != nil {
|
||||||
verboseLog("Failed to remove file %s: %v", filePath, err)
|
verboseLog("Failed to remove file %s: %v", filePath, err)
|
||||||
} else if verbose {
|
} else if verbose {
|
||||||
verboseLog("Removed partial/delta file: %s", filePath)
|
verboseLog("Removed partial/delta file: %s", filePath)
|
||||||
}
|
}
|
||||||
} else if strings.HasSuffix(file.Name(), ".snap") {
|
} else if strings.HasSuffix(file.Name(), ".snap") {
|
||||||
if !validSnaps[file.Name()] {
|
if !validSnaps[file.Name()] {
|
||||||
verboseLog("Removing outdated or orphaned snap file: %s\n", filePath)
|
verboseLog("Removing outdated or orphaned snap file: %s\n", filePath)
|
||||||
if err := os.Remove(filePath); err != nil {
|
if err := os.Remove(filePath); err != nil {
|
||||||
verboseLog("Failed to remove snap file %s: %v", filePath, err)
|
verboseLog("Failed to remove snap file %s: %v", filePath, err)
|
||||||
} else if verbose {
|
} else if verbose {
|
||||||
verboseLog("Removed snap file: %s", filePath)
|
verboseLog("Removed snap file: %s", filePath)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
verboseLog("Snap file %s is valid and retained.\n", file.Name())
|
verboseLog("Snap file %s is valid and retained.\n", file.Name())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove orphaned assertion files
|
// Remove orphaned assertion files
|
||||||
files, err = os.ReadDir(assertionsDir)
|
files, err = os.ReadDir(assertionsDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
verboseLog("Error reading assertions directory for cleanup: %v", err)
|
verboseLog("Error reading assertions directory for cleanup: %v", err)
|
||||||
} else {
|
} else {
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
filePath := filepath.Join(assertionsDir, file.Name())
|
filePath := filepath.Join(assertionsDir, file.Name())
|
||||||
if strings.HasSuffix(file.Name(), ".assert") {
|
if strings.HasSuffix(file.Name(), ".assert") {
|
||||||
if !validAssertions[file.Name()] {
|
if !validAssertions[file.Name()] {
|
||||||
verboseLog("Removing orphaned assertion file: %s\n", filePath)
|
verboseLog("Removing orphaned assertion file: %s\n", filePath)
|
||||||
if err := os.Remove(filePath); err != nil {
|
if err := os.Remove(filePath); err != nil {
|
||||||
verboseLog("Failed to remove assertion file %s: %v", filePath, err)
|
verboseLog("Failed to remove assertion file %s: %v", filePath, err)
|
||||||
} else if verbose {
|
} else if verbose {
|
||||||
verboseLog("Removed assertion file: %s", filePath)
|
verboseLog("Removed assertion file: %s", filePath)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
verboseLog("Assertion file %s is valid and retained.\n", file.Name())
|
verboseLog("Assertion file %s is valid and retained.\n", file.Name())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
verboseLog("Cleanup process completed.")
|
verboseLog("Cleanup process completed.")
|
||||||
}
|
}
|
||||||
|
|
||||||
// removeOrphanedFiles deletes the assertion and snap file corresponding to the removed snap.
|
// removeOrphanedFiles deletes the assertion and snap file corresponding to the removed snap.
|
||||||
func removeOrphanedFiles(snapName string, revision int, assertionsDir string, snapsDir string) {
|
func removeOrphanedFiles(snapName string, revision int, assertionsDir string, snapsDir string) {
|
||||||
assertionFilePath := filepath.Join(assertionsDir, fmt.Sprintf("%s_%d.assert", snapName, revision))
|
assertionFilePath := filepath.Join(assertionsDir, fmt.Sprintf("%s_%d.assert", snapName, revision))
|
||||||
snapFilePath := filepath.Join(snapsDir, fmt.Sprintf("%s_%d.snap", snapName, revision))
|
snapFilePath := filepath.Join(snapsDir, fmt.Sprintf("%s_%d.snap", snapName, revision))
|
||||||
if fileExists(assertionFilePath) {
|
if fileExists(assertionFilePath) {
|
||||||
err := os.Remove(assertionFilePath)
|
err := os.Remove(assertionFilePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
verboseLog("Failed to remove assertion file %s: %v", assertionFilePath, err)
|
verboseLog("Failed to remove assertion file %s: %v", assertionFilePath, err)
|
||||||
} else {
|
} else {
|
||||||
verboseLog("Removed assertion file: %s", assertionFilePath)
|
verboseLog("Removed assertion file: %s", assertionFilePath)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
verboseLog("Assertion file %s does not exist. No action taken.", assertionFilePath)
|
verboseLog("Assertion file %s does not exist. No action taken.", assertionFilePath)
|
||||||
}
|
}
|
||||||
if fileExists(snapFilePath) {
|
if fileExists(snapFilePath) {
|
||||||
err := os.Remove(snapFilePath)
|
err := os.Remove(snapFilePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
verboseLog("Failed to remove snap file %s: %v", snapFilePath, err)
|
verboseLog("Failed to remove snap file %s: %v", snapFilePath, err)
|
||||||
} else {
|
} else {
|
||||||
verboseLog("Removed snap file: %s", snapFilePath)
|
verboseLog("Removed snap file: %s", snapFilePath)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
verboseLog("Snap file %s does not exist. No action taken.", snapFilePath)
|
verboseLog("Snap file %s does not exist. No action taken.", snapFilePath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// cleanUpCurrentSnaps removes snaps from currentSnaps that are not marked as required.
|
// cleanUpCurrentSnaps removes snaps from currentSnaps that are not marked as required.
|
||||||
func cleanUpCurrentSnaps(assertionsDir string, snapsDir string) {
|
func cleanUpCurrentSnaps(assertionsDir string, snapsDir string) {
|
||||||
var filteredSnaps []*store.CurrentSnap
|
var filteredSnaps []*store.CurrentSnap
|
||||||
|
|
||||||
for _, snap := range currentSnaps {
|
for _, snap := range currentSnaps {
|
||||||
if requiredSnaps[snap.InstanceName] {
|
if requiredSnaps[snap.InstanceName] {
|
||||||
filteredSnaps = append(filteredSnaps, snap)
|
filteredSnaps = append(filteredSnaps, snap)
|
||||||
} else {
|
} else {
|
||||||
verboseLog("Removing unnecessary snap: %s\n", snap.InstanceName)
|
verboseLog("Removing unnecessary snap: %s\n", snap.InstanceName)
|
||||||
removeOrphanedFiles(snap.InstanceName, snap.Revision.N, assertionsDir, snapsDir)
|
removeOrphanedFiles(snap.InstanceName, snap.Revision.N, assertionsDir, snapsDir)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
currentSnaps = filteredSnaps
|
currentSnaps = filteredSnaps
|
||||||
|
|
||||||
// Log the updated currentSnaps
|
// Log the updated currentSnaps
|
||||||
verboseLog("Filtered currentSnaps after cleanup:")
|
verboseLog("Filtered currentSnaps after cleanup:")
|
||||||
for _, snap := range currentSnaps {
|
for _, snap := range currentSnaps {
|
||||||
verboseLog("- %s_%d.snap", snap.InstanceName, snap.Revision.N)
|
verboseLog("- %s_%d.snap", snap.InstanceName, snap.Revision.N)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// removeStateJson removes the state.json file if it exists
|
// removeStateJson removes the state.json file if it exists
|
||||||
func removeStateJson(stateJsonPath string) {
|
func removeStateJson(stateJsonPath string) {
|
||||||
if _, err := os.Stat(stateJsonPath); err == nil {
|
if _, err := os.Stat(stateJsonPath); err == nil {
|
||||||
if err := os.Remove(stateJsonPath); err != nil {
|
if err := os.Remove(stateJsonPath); err != nil {
|
||||||
verboseLog("Failed to remove state.json: %v", err)
|
verboseLog("Failed to remove state.json: %v", err)
|
||||||
} else if verbose {
|
} else if verbose {
|
||||||
verboseLog("Removed state.json at %s", stateJsonPath)
|
verboseLog("Removed state.json at %s", stateJsonPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,152 +1,152 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/snapcore/snapd/snap"
|
"github.com/snapcore/snapd/snap"
|
||||||
"github.com/snapcore/snapd/store"
|
"github.com/snapcore/snapd/store"
|
||||||
)
|
)
|
||||||
|
|
||||||
// downloadSnap downloads a snap file with retry logic
|
// downloadSnap downloads a snap file with retry logic
|
||||||
func downloadSnap(storeClient *store.Store, snapInfo *snap.Info, downloadPath string) error {
|
func downloadSnap(storeClient *store.Store, snapInfo *snap.Info, downloadPath string) error {
|
||||||
downloadInfo := &snap.DownloadInfo{
|
downloadInfo := &snap.DownloadInfo{
|
||||||
DownloadURL: snapInfo.DownloadURL,
|
DownloadURL: snapInfo.DownloadURL,
|
||||||
}
|
}
|
||||||
|
|
||||||
pbar := NewProgressMeter(snapInfo.SuggestedName, snapInfo.Version, false)
|
pbar := NewProgressMeter(snapInfo.SuggestedName, snapInfo.Version, false)
|
||||||
progressTracker.UpdateStepProgress(0)
|
progressTracker.UpdateStepProgress(0)
|
||||||
|
|
||||||
for attempts := 1; attempts <= 5; attempts++ {
|
for attempts := 1; attempts <= 5; attempts++ {
|
||||||
verboseLog("Attempt %d to download snap: %s", attempts, downloadPath)
|
verboseLog("Attempt %d to download snap: %s", attempts, downloadPath)
|
||||||
err := storeClient.Download(ctx, snapInfo.SnapID, downloadPath, downloadInfo, pbar, nil, nil)
|
err := storeClient.Download(ctx, snapInfo.SnapID, downloadPath, downloadInfo, pbar, nil, nil)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
pbar.Finished()
|
pbar.Finished()
|
||||||
return nil // Successful download
|
return nil // Successful download
|
||||||
}
|
}
|
||||||
if verbose {
|
if verbose {
|
||||||
verboseLog("Attempt %d to download %s failed: %v", attempts, snapInfo.SuggestedName, err)
|
verboseLog("Attempt %d to download %s failed: %v", attempts, snapInfo.SuggestedName, err)
|
||||||
} else if progressTracker != nil {
|
} else if progressTracker != nil {
|
||||||
progressTracker.UpdateStepProgress(0)
|
progressTracker.UpdateStepProgress(0)
|
||||||
}
|
}
|
||||||
if attempts == 5 {
|
if attempts == 5 {
|
||||||
return fmt.Errorf("snap download failed after 5 attempts: %v", err)
|
return fmt.Errorf("snap download failed after 5 attempts: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return fmt.Errorf("snap download failed after 5 attempts")
|
return fmt.Errorf("snap download failed after 5 attempts")
|
||||||
}
|
}
|
||||||
|
|
||||||
// downloadSnapDeltaWithRetries downloads the delta file with retry logic and exponential backoff.
|
// downloadSnapDeltaWithRetries downloads the delta file with retry logic and exponential backoff.
|
||||||
func downloadSnapDeltaWithRetries(storeClient *store.Store, delta *snap.DeltaInfo, result *store.SnapActionResult, deltaPath string, maxRetries int, snapName string) error {
|
func downloadSnapDeltaWithRetries(storeClient *store.Store, delta *snap.DeltaInfo, result *store.SnapActionResult, deltaPath string, maxRetries int, snapName string) error {
|
||||||
if !verbose {
|
if !verbose {
|
||||||
verboseLog("Downloading delta for %s", snapName)
|
verboseLog("Downloading delta for %s", snapName)
|
||||||
}
|
}
|
||||||
var lastErr error
|
var lastErr error
|
||||||
backoff := 1 * time.Second
|
backoff := 1 * time.Second
|
||||||
|
|
||||||
for attempts := 1; attempts <= maxRetries; attempts++ {
|
for attempts := 1; attempts <= maxRetries; attempts++ {
|
||||||
verboseLog("Attempt %d to download delta: %s", attempts, deltaPath)
|
verboseLog("Attempt %d to download delta: %s", attempts, deltaPath)
|
||||||
err := downloadSnapDelta(storeClient, delta, result, deltaPath)
|
err := downloadSnapDelta(storeClient, delta, result, deltaPath)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
lastErr = err
|
lastErr = err
|
||||||
verboseLog("Attempt %d to download delta failed: %v", attempts, err)
|
verboseLog("Attempt %d to download delta failed: %v", attempts, err)
|
||||||
time.Sleep(backoff)
|
time.Sleep(backoff)
|
||||||
backoff *= 2
|
backoff *= 2
|
||||||
}
|
}
|
||||||
return fmt.Errorf("delta download failed after %d attempts: %v", maxRetries, lastErr)
|
return fmt.Errorf("delta download failed after %d attempts: %v", maxRetries, lastErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// downloadSnapDelta downloads the delta file.
|
// downloadSnapDelta downloads the delta file.
|
||||||
func downloadSnapDelta(storeClient *store.Store, delta *snap.DeltaInfo, result *store.SnapActionResult, deltaPath string) error {
|
func downloadSnapDelta(storeClient *store.Store, delta *snap.DeltaInfo, result *store.SnapActionResult, deltaPath string) error {
|
||||||
verboseLog("Downloading delta from revision %d to %d from: %s", delta.FromRevision, delta.ToRevision, delta.DownloadURL)
|
verboseLog("Downloading delta from revision %d to %d from: %s", delta.FromRevision, delta.ToRevision, delta.DownloadURL)
|
||||||
|
|
||||||
downloadInfo := &snap.DownloadInfo{
|
downloadInfo := &snap.DownloadInfo{
|
||||||
DownloadURL: delta.DownloadURL,
|
DownloadURL: delta.DownloadURL,
|
||||||
Size: delta.Size,
|
Size: delta.Size,
|
||||||
Sha3_384: delta.Sha3_384,
|
Sha3_384: delta.Sha3_384,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use the SnapID from the associated SnapActionResult's Info
|
// Use the SnapID from the associated SnapActionResult's Info
|
||||||
snapID := result.Info.SnapID
|
snapID := result.Info.SnapID
|
||||||
|
|
||||||
pbar := NewProgressMeter(result.Info.SuggestedName, result.Info.Version, true)
|
pbar := NewProgressMeter(result.Info.SuggestedName, result.Info.Version, true)
|
||||||
progressTracker.UpdateStepProgress(0)
|
progressTracker.UpdateStepProgress(0)
|
||||||
|
|
||||||
// Download the delta file
|
// Download the delta file
|
||||||
if err := storeClient.Download(ctx, snapID, deltaPath, downloadInfo, pbar, nil, nil); err != nil {
|
if err := storeClient.Download(ctx, snapID, deltaPath, downloadInfo, pbar, nil, nil); err != nil {
|
||||||
progressTracker.UpdateStepProgress(0)
|
progressTracker.UpdateStepProgress(0)
|
||||||
return fmt.Errorf("delta download failed: %v", err)
|
return fmt.Errorf("delta download failed: %v", err)
|
||||||
}
|
}
|
||||||
verboseLog("Downloaded %s to %s", delta.DownloadURL, deltaPath)
|
verboseLog("Downloaded %s to %s", delta.DownloadURL, deltaPath)
|
||||||
pbar.Finished()
|
pbar.Finished()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// downloadAndApplySnap handles the downloading and delta application process.
|
// downloadAndApplySnap handles the downloading and delta application process.
|
||||||
// It returns the snap information and an error if any.
|
// It returns the snap information and an error if any.
|
||||||
func downloadAndApplySnap(storeClient *store.Store, result *store.SnapActionResult, snapsDir, assertionsDir string, currentSnap *store.CurrentSnap) (*snap.Info, error) {
|
func downloadAndApplySnap(storeClient *store.Store, result *store.SnapActionResult, snapsDir, assertionsDir string, currentSnap *store.CurrentSnap) (*snap.Info, error) {
|
||||||
if result == nil || result.Info == nil {
|
if result == nil || result.Info == nil {
|
||||||
verboseLog("No updates available for snap. Skipping download and assertions.")
|
verboseLog("No updates available for snap. Skipping download and assertions.")
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
snapInfo := result.Info
|
snapInfo := result.Info
|
||||||
downloadPath := filepath.Join(snapsDir, fmt.Sprintf("%s_%d.snap", snapInfo.SuggestedName, snapInfo.Revision.N))
|
downloadPath := filepath.Join(snapsDir, fmt.Sprintf("%s_%d.snap", snapInfo.SuggestedName, snapInfo.Revision.N))
|
||||||
|
|
||||||
// Check if a delta can be applied
|
// Check if a delta can be applied
|
||||||
if currentSnap != nil && len(result.Deltas) > 0 {
|
if currentSnap != nil && len(result.Deltas) > 0 {
|
||||||
for _, delta := range result.Deltas {
|
for _, delta := range result.Deltas {
|
||||||
deltaPath := filepath.Join(snapsDir, fmt.Sprintf("%s_%d_to_%d.delta", snapInfo.SuggestedName, delta.FromRevision, delta.ToRevision))
|
deltaPath := filepath.Join(snapsDir, fmt.Sprintf("%s_%d_to_%d.delta", snapInfo.SuggestedName, delta.FromRevision, delta.ToRevision))
|
||||||
if err := downloadSnapDeltaWithRetries(storeClient, &delta, result, deltaPath, 5, snapInfo.SuggestedName); err == nil {
|
if err := downloadSnapDeltaWithRetries(storeClient, &delta, result, deltaPath, 5, snapInfo.SuggestedName); err == nil {
|
||||||
oldSnapPath := filepath.Join(snapsDir, fmt.Sprintf("%s_%d.snap", snapInfo.SuggestedName, delta.FromRevision))
|
oldSnapPath := filepath.Join(snapsDir, fmt.Sprintf("%s_%d.snap", snapInfo.SuggestedName, delta.FromRevision))
|
||||||
if fileExists(oldSnapPath) {
|
if fileExists(oldSnapPath) {
|
||||||
if err := applyDelta(oldSnapPath, deltaPath, downloadPath); err == nil {
|
if err := applyDelta(oldSnapPath, deltaPath, downloadPath); err == nil {
|
||||||
verboseLog("Delta applied successfully for snap %s", snapInfo.SuggestedName)
|
verboseLog("Delta applied successfully for snap %s", snapInfo.SuggestedName)
|
||||||
// Download assertions after successful snap download
|
// Download assertions after successful snap download
|
||||||
if err := downloadAssertions(storeClient, snapInfo, assertionsDir); err != nil {
|
if err := downloadAssertions(storeClient, snapInfo, assertionsDir); err != nil {
|
||||||
return nil, fmt.Errorf("failed to download assertions for snap %s: %w", snapInfo.SuggestedName, err)
|
return nil, fmt.Errorf("failed to download assertions for snap %s: %w", snapInfo.SuggestedName, err)
|
||||||
}
|
}
|
||||||
return snapInfo, nil // Successful delta application
|
return snapInfo, nil // Successful delta application
|
||||||
} else {
|
} else {
|
||||||
verboseLog("Failed to apply delta for snap %s: %v", snapInfo.SuggestedName, err)
|
verboseLog("Failed to apply delta for snap %s: %v", snapInfo.SuggestedName, err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
verboseLog("Old snap file %s does not exist. Cannot apply delta.", oldSnapPath)
|
verboseLog("Old snap file %s does not exist. Cannot apply delta.", oldSnapPath)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
verboseLog("Attempt to download delta for snap %s failed: %v", snapInfo.SuggestedName, err)
|
verboseLog("Attempt to download delta for snap %s failed: %v", snapInfo.SuggestedName, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If no delta was applied or no deltas are available, fallback to downloading the full snap
|
// If no delta was applied or no deltas are available, fallback to downloading the full snap
|
||||||
if err := downloadSnap(storeClient, snapInfo, downloadPath); err != nil {
|
if err := downloadSnap(storeClient, snapInfo, downloadPath); err != nil {
|
||||||
return nil, fmt.Errorf("failed to download snap %s: %w", snapInfo.SuggestedName, err)
|
return nil, fmt.Errorf("failed to download snap %s: %w", snapInfo.SuggestedName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Download assertions after successful snap download
|
// Download assertions after successful snap download
|
||||||
if err := downloadAssertions(storeClient, snapInfo, assertionsDir); err != nil {
|
if err := downloadAssertions(storeClient, snapInfo, assertionsDir); err != nil {
|
||||||
return nil, fmt.Errorf("failed to download assertions for snap %s: %w", snapInfo.SuggestedName, err)
|
return nil, fmt.Errorf("failed to download assertions for snap %s: %w", snapInfo.SuggestedName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
verboseLog("Downloaded and applied snap: %s, revision: %d", snapInfo.SuggestedName, snapInfo.Revision.N)
|
verboseLog("Downloaded and applied snap: %s, revision: %d", snapInfo.SuggestedName, snapInfo.Revision.N)
|
||||||
return snapInfo, nil
|
return snapInfo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// applyDelta applies the downloaded delta using xdelta3.
|
// applyDelta applies the downloaded delta using xdelta3.
|
||||||
func applyDelta(oldSnapPath, deltaPath, newSnapPath string) error {
|
func applyDelta(oldSnapPath, deltaPath, newSnapPath string) error {
|
||||||
verboseLog("Applying delta from %s to %s using %s", oldSnapPath, newSnapPath, deltaPath)
|
verboseLog("Applying delta from %s to %s using %s", oldSnapPath, newSnapPath, deltaPath)
|
||||||
|
|
||||||
cmd := exec.Command("xdelta3", "-d", "-s", oldSnapPath, deltaPath, newSnapPath)
|
cmd := exec.Command("xdelta3", "-d", "-s", oldSnapPath, deltaPath, newSnapPath)
|
||||||
output, err := cmd.CombinedOutput()
|
output, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
verboseLog("xdelta3 output: %s", string(output))
|
verboseLog("xdelta3 output: %s", string(output))
|
||||||
return fmt.Errorf("failed to apply delta: %v - %s", err, string(output))
|
return fmt.Errorf("failed to apply delta: %v - %s", err, string(output))
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -1,178 +1,179 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"flag"
|
"flag"
|
||||||
"log"
|
"log"
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/snapcore/snapd/snap"
|
"github.com/snapcore/snapd/snap"
|
||||||
"github.com/snapcore/snapd/store"
|
"github.com/snapcore/snapd/store"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Seed structure for seed.yaml
|
// Seed structure for seed.yaml
|
||||||
type seed struct {
|
type seed struct {
|
||||||
Snaps []struct {
|
Snaps []struct {
|
||||||
Name string `yaml:"name"`
|
Name string `yaml:"name"`
|
||||||
Channel string `yaml:"channel"`
|
Channel string `yaml:"channel"`
|
||||||
File string `yaml:"file"`
|
File string `yaml:"file"`
|
||||||
} `yaml:"snaps"`
|
} `yaml:"snaps"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ctx = context.Background()
|
ctx = context.Background()
|
||||||
storeClient *store.Store
|
storeClient *store.Store
|
||||||
verbose bool
|
verbose bool
|
||||||
currentSnaps []*store.CurrentSnap
|
currentSnaps []*store.CurrentSnap
|
||||||
requiredSnaps map[string]bool
|
requiredSnaps map[string]bool
|
||||||
processedSnaps = make(map[string]bool)
|
processedSnaps = make(map[string]bool)
|
||||||
snapSizeMap = make(map[string]float64)
|
snapSizeMap = make(map[string]float64)
|
||||||
totalSnapSize float64
|
totalSnapSize float64
|
||||||
)
|
)
|
||||||
|
|
||||||
type SnapInfo struct {
|
type SnapInfo struct {
|
||||||
InstanceName string
|
InstanceName string
|
||||||
SnapID string
|
SnapID string
|
||||||
Revision snap.Revision
|
Revision snap.Revision
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// Override the default plug slot sanitizer
|
// Override the default plug slot sanitizer
|
||||||
snap.SanitizePlugsSlots = sanitizePlugsSlots
|
snap.SanitizePlugsSlots = sanitizePlugsSlots
|
||||||
|
|
||||||
// Initialize progress reporting
|
// Initialize progress reporting
|
||||||
InitProgress()
|
InitProgress()
|
||||||
totalSnapSize = 0
|
totalSnapSize = 0
|
||||||
|
|
||||||
// Initialize the store client
|
// Initialize the store client
|
||||||
storeClient = store.New(nil, nil)
|
storeClient = store.New(nil, nil)
|
||||||
|
|
||||||
// Parse command-line flags
|
// Parse command-line flags
|
||||||
var seedDirectory string
|
var seedDirectory string
|
||||||
flag.StringVar(&seedDirectory, "seed", "/var/lib/snapd/seed", "Specify the seed directory")
|
flag.StringVar(&seedDirectory, "seed", "/var/lib/snapd/seed", "Specify the seed directory")
|
||||||
flag.BoolVar(&verbose, "verbose", false, "Enable verbose output")
|
flag.BoolVar(&verbose, "verbose", false, "Enable verbose output")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
if !verbose {
|
if !verbose {
|
||||||
fmt.Printf("2\tLoading existing snaps...\n")
|
fmt.Printf("2\tLoading existing snaps...\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Define directories based on the seed directory
|
// Define directories based on the seed directory
|
||||||
snapsDir := filepath.Join(seedDirectory, "snaps")
|
snapsDir := filepath.Join(seedDirectory, "snaps")
|
||||||
assertionsDir := filepath.Join(seedDirectory, "assertions")
|
assertionsDir := filepath.Join(seedDirectory, "assertions")
|
||||||
seedYaml := filepath.Join(seedDirectory, "seed.yaml")
|
seedYaml := filepath.Join(seedDirectory, "seed.yaml")
|
||||||
|
|
||||||
// Setup directories and seed.yaml
|
// Setup directories and seed.yaml
|
||||||
initializeDirectories(snapsDir, assertionsDir)
|
initializeDirectories(snapsDir, assertionsDir)
|
||||||
initializeSeedYaml(seedYaml)
|
initializeSeedYaml(seedYaml)
|
||||||
|
|
||||||
// Load existing snaps from seed.yaml
|
// Load existing snaps from seed.yaml
|
||||||
existingSnapsInYaml := loadExistingSnaps(seedYaml)
|
existingSnapsInYaml := loadExistingSnaps(seedYaml)
|
||||||
|
|
||||||
// Populate currentSnaps based on existing snaps
|
// Populate currentSnaps based on existing snaps
|
||||||
for snapName := range existingSnapsInYaml {
|
for snapName := range existingSnapsInYaml {
|
||||||
snapInfo, err := getCurrentSnapInfo(assertionsDir, snapName)
|
snapInfo, err := getCurrentSnapInfo(assertionsDir, snapName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
verboseLog("Failed to get info for existing snap %s: %v", snapName, err)
|
verboseLog("Failed to get info for existing snap %s: %v", snapName, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
currentSnaps = append(currentSnaps, snapInfo)
|
currentSnaps = append(currentSnaps, snapInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process essential snaps
|
// Process essential snaps
|
||||||
requiredSnaps = map[string]bool{"snapd": true, "bare": true}
|
requiredSnaps = map[string]bool{"snapd": true, "bare": true}
|
||||||
for _, arg := range flag.Args() {
|
for _, arg := range flag.Args() {
|
||||||
requiredSnaps[arg] = true
|
requiredSnaps[arg] = true
|
||||||
}
|
}
|
||||||
if !verbose {
|
if !verbose {
|
||||||
fmt.Printf("4\tFetching information from the Snap Store...\n")
|
fmt.Printf("4\tFetching information from the Snap Store...\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect snaps to process
|
// Collect snaps to process
|
||||||
snapsToProcess, err := collectSnapsToProcess(snapsDir, assertionsDir)
|
snapsToProcess, err := collectSnapsToProcess(snapsDir, assertionsDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to collect snaps to process: %v", err)
|
log.Fatalf("Failed to collect snaps to process: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
progressTracker.Finish("Finished collecting snap info")
|
progressTracker.Finish("Finished collecting snap info")
|
||||||
|
|
||||||
// Calculate the number of snaps to download
|
// Calculate the number of snaps to download
|
||||||
totalSnaps := len(snapsToProcess)
|
totalSnaps := len(snapsToProcess)
|
||||||
if totalSnaps == 0 {
|
if totalSnaps == 0 {
|
||||||
verboseLog("No snaps to process.")
|
verboseLog("No snaps to process.")
|
||||||
} else {
|
} else {
|
||||||
verboseLog("Total snaps to download: %d", totalSnaps)
|
verboseLog("Total snaps to download: %d", totalSnaps)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize variables to track download progress
|
// Initialize variables to track download progress
|
||||||
completedSnaps := 0
|
completedSnaps := 0
|
||||||
|
|
||||||
// Update "Downloading snaps" step to 0%
|
// Update "Downloading snaps" step to 0%
|
||||||
progressTracker.UpdateStepProgress(0)
|
progressTracker.UpdateStepProgress(0)
|
||||||
|
|
||||||
// Process all the snaps that need updates
|
// Process all the snaps that need updates
|
||||||
for _, snapDetails := range snapsToProcess {
|
for _, snapDetails := range snapsToProcess {
|
||||||
if err := processSnap(snapDetails, snapsDir, assertionsDir); err != nil {
|
if err := processSnap(snapDetails, snapsDir, assertionsDir); err != nil {
|
||||||
log.Fatalf("Failed to process snap %s: %v", snapDetails.InstanceName, err)
|
log.Fatalf("Failed to process snap %s: %v", snapDetails.InstanceName, err)
|
||||||
}
|
}
|
||||||
completedSnaps++
|
completedSnaps++
|
||||||
|
|
||||||
progressTracker.UpdateStepProgress(-1)
|
progressTracker.UpdateStepProgress(-1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark "Downloading snaps" as complete
|
// Mark "Downloading snaps" as complete
|
||||||
if totalSnaps > 0 {
|
if totalSnaps > 0 {
|
||||||
progressTracker.Finish("Downloading snaps completed")
|
progressTracker.Finish("Downloading snaps completed")
|
||||||
} else {
|
} else {
|
||||||
// If no snaps to download, skip to finalizing
|
// If no snaps to download, skip to finalizing
|
||||||
progressTracker.NextStep()
|
progressTracker.NextStep()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove unnecessary snaps after processing dependencies
|
// Remove unnecessary snaps after processing dependencies
|
||||||
cleanUpCurrentSnaps(assertionsDir, snapsDir)
|
cleanUpCurrentSnaps(assertionsDir, snapsDir)
|
||||||
|
|
||||||
// Update seed.yaml with the current required snaps
|
// Update seed.yaml with the current required snaps
|
||||||
if err := updateSeedYaml(snapsDir, seedYaml, currentSnaps); err != nil {
|
if err := updateSeedYaml(snapsDir, seedYaml, currentSnaps); err != nil {
|
||||||
log.Fatalf("Failed to update seed.yaml: %v", err)
|
log.Fatalf("Failed to update seed.yaml: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Perform cleanup and validation tasks
|
// Perform cleanup and validation tasks
|
||||||
removeStateJson(filepath.Join(seedDirectory, "..", "state.json"))
|
removeStateJson(filepath.Join(seedDirectory, "..", "state.json"))
|
||||||
ensureAssertions(assertionsDir)
|
ensureAssertions(assertionsDir)
|
||||||
if err := validateSeed(seedYaml); err != nil {
|
if err := validateSeed(seedYaml); err != nil {
|
||||||
log.Fatalf("Seed validation failed: %v", err)
|
log.Fatalf("Seed validation failed: %v", err)
|
||||||
}
|
}
|
||||||
cleanUpFiles(snapsDir, assertionsDir, seedYaml)
|
cleanUpFiles(snapsDir, assertionsDir, seedYaml)
|
||||||
|
|
||||||
// Mark "Finalizing" as complete
|
// Mark "Finalizing" as complete
|
||||||
if progressTracker != nil {
|
if progressTracker != nil {
|
||||||
progressTracker.Finish("Cleanup and validation completed")
|
progressTracker.Finish("Cleanup and validation completed")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// collectSnapsToProcess collects all snaps and their dependencies, returning only those that need updates
|
// collectSnapsToProcess collects all snaps and their dependencies, returning only those that need updates
|
||||||
func collectSnapsToProcess(snapsDir, assertionsDir string) ([]SnapDetails, error) {
|
func collectSnapsToProcess(snapsDir, assertionsDir string) ([]SnapDetails, error) {
|
||||||
var snapsToProcess []SnapDetails
|
var snapsToProcess []SnapDetails
|
||||||
|
|
||||||
for snapEntry := range requiredSnaps {
|
fallbackChannel := "latest/stable"
|
||||||
// Extract channel if specified, default to "stable"
|
for snapEntry := range requiredSnaps {
|
||||||
parts := strings.SplitN(snapEntry, "=", 2)
|
// Extract channel if specified, default to "stable"
|
||||||
channel := "stable"
|
parts := strings.SplitN(snapEntry, "=", 2)
|
||||||
if len(parts) == 2 {
|
channel := "latest/stable/ubuntu-25.04"
|
||||||
channel = parts[1]
|
if len(parts) == 2 {
|
||||||
}
|
channel = parts[1]
|
||||||
snapName := parts[0]
|
}
|
||||||
|
snapName := parts[0]
|
||||||
|
|
||||||
// Collect snap dependencies and their statuses
|
// Collect snap dependencies and their statuses
|
||||||
snapList, err := collectSnapDependencies(snapName, channel, snapsDir, assertionsDir)
|
snapList, err := collectSnapDependencies(snapName, channel, fallbackChannel, snapsDir, assertionsDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Append only those snaps that need updates
|
// Append only those snaps that need updates
|
||||||
for _, snapDetails := range snapList {
|
for _, snapDetails := range snapList {
|
||||||
verboseLog("Processing snap: %s with result: %v", snapDetails.InstanceName, snapDetails.Result)
|
verboseLog("Processing snap: %s with result: %v", snapDetails.InstanceName, snapDetails.Result)
|
||||||
if len(snapDetails.Result.Deltas) > 0 {
|
if len(snapDetails.Result.Deltas) > 0 {
|
||||||
for _, delta := range snapDetails.Result.Deltas {
|
for _, delta := range snapDetails.Result.Deltas {
|
||||||
snapSize := float64(delta.Size)
|
snapSize := float64(delta.Size)
|
||||||
@ -184,11 +185,11 @@ func collectSnapsToProcess(snapsDir, assertionsDir string) ([]SnapDetails, error
|
|||||||
snapSizeMap[snapDetails.Result.Info.SuggestedName] = snapSize
|
snapSizeMap[snapDetails.Result.Info.SuggestedName] = snapSize
|
||||||
totalSnapSize += snapSize
|
totalSnapSize += snapSize
|
||||||
}
|
}
|
||||||
snapsToProcess = append(snapsToProcess, snapDetails)
|
snapsToProcess = append(snapsToProcess, snapDetails)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return snapsToProcess, nil
|
return snapsToProcess, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// sanitizePlugsSlots is a placeholder function to sanitize plug slots in snap.Info
|
// sanitizePlugsSlots is a placeholder function to sanitize plug slots in snap.Info
|
||||||
@ -196,7 +197,7 @@ func sanitizePlugsSlots(info *snap.Info) {}
|
|||||||
|
|
||||||
// verboseLog logs messages only when verbose mode is enabled
|
// verboseLog logs messages only when verbose mode is enabled
|
||||||
func verboseLog(format string, v ...interface{}) {
|
func verboseLog(format string, v ...interface{}) {
|
||||||
if verbose {
|
if verbose {
|
||||||
log.Printf(format, v...)
|
log.Printf(format, v...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,220 +1,243 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/snapcore/snapd/snap"
|
"github.com/snapcore/snapd/snap"
|
||||||
"github.com/snapcore/snapd/store"
|
"github.com/snapcore/snapd/store"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SnapDetails struct remains unchanged
|
// SnapDetails struct remains unchanged
|
||||||
type SnapDetails struct {
|
type SnapDetails struct {
|
||||||
InstanceName string
|
InstanceName string
|
||||||
Channel string
|
Channel string
|
||||||
CurrentSnap *store.CurrentSnap
|
CurrentSnap *store.CurrentSnap
|
||||||
Result *store.SnapActionResult
|
Result *store.SnapActionResult
|
||||||
}
|
}
|
||||||
|
|
||||||
// collectSnapDependencies collects all dependencies for a given snap, marking them as requiredSnaps regardless of whether they need updates.
|
// collectSnapDependencies collects all dependencies for a given snap, marking them as requiredSnaps regardless of whether they need updates.
|
||||||
func collectSnapDependencies(snapName, channel, snapsDir, assertionsDir string) ([]SnapDetails, error) {
|
func collectSnapDependencies(snapName, channel, fallbackChannel, snapsDir, assertionsDir string) ([]SnapDetails, error) {
|
||||||
var snapDetailsList []SnapDetails
|
var snapDetailsList []SnapDetails
|
||||||
|
|
||||||
if processedSnaps[snapName] {
|
if processedSnaps[snapName] {
|
||||||
verboseLog("Snap %s has already been processed. Skipping.", snapName)
|
verboseLog("Snap %s has already been processed. Skipping.", snapName)
|
||||||
return snapDetailsList, nil // Added 0 for int64
|
return snapDetailsList, nil // Added 0 for int64
|
||||||
}
|
}
|
||||||
|
|
||||||
oldSnapPath, oldSnap := findPreviousSnap(snapsDir, assertionsDir, snapName)
|
oldSnapPath, oldSnap := findPreviousSnap(snapsDir, assertionsDir, snapName)
|
||||||
|
|
||||||
var result *store.SnapActionResult
|
var result *store.SnapActionResult
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
// Fetch or refresh snap information
|
// Fetch or refresh snap information
|
||||||
if oldSnap == nil || oldSnap.SnapID == "" || oldSnap.Revision.N == 0 {
|
if oldSnap == nil || oldSnap.SnapID == "" || oldSnap.Revision.N == 0 {
|
||||||
result, err = fetchOrRefreshSnapInfo(snapName, nil, channel)
|
result, err = fetchOrRefreshSnapInfo(snapName, nil, channel)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err // Added 0 for int64
|
if strings.Contains(err.Error(), "no snap revision available as specified") {
|
||||||
}
|
result, err = fetchOrRefreshSnapInfo(snapName, nil, fallbackChannel)
|
||||||
} else {
|
if err != nil {
|
||||||
result, err = fetchOrRefreshSnapInfo(snapName, oldSnap, channel)
|
return nil, err
|
||||||
if err != nil && strings.Contains(err.Error(), "snap has no updates available") {
|
}
|
||||||
result, err = fetchOrRefreshSnapInfo(snapName, nil, channel)
|
} else {
|
||||||
if err != nil {
|
return nil, err
|
||||||
return nil, err // Added 0 for int64
|
}
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
}
|
result, err = fetchOrRefreshSnapInfo(snapName, oldSnap, channel)
|
||||||
|
if err != nil {
|
||||||
|
if 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 {
|
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
|
return nil, fmt.Errorf("invalid snap information returned for %s: SnapID or Revision is missing", snapName) // Added 0 for int64
|
||||||
}
|
}
|
||||||
|
|
||||||
info := result.Info
|
info := result.Info
|
||||||
newSnap := &store.CurrentSnap{
|
newSnap := &store.CurrentSnap{
|
||||||
InstanceName: snapName,
|
InstanceName: snapName,
|
||||||
SnapID: info.SnapID,
|
SnapID: info.SnapID,
|
||||||
Revision: snap.Revision{N: info.Revision.N},
|
Revision: snap.Revision{N: info.Revision.N},
|
||||||
}
|
}
|
||||||
snapInCurrentSnaps, oldRevision := isSnapInCurrentSnaps(snapName)
|
snapInCurrentSnaps, oldRevision := isSnapInCurrentSnaps(snapName)
|
||||||
if snapInCurrentSnaps {
|
if snapInCurrentSnaps {
|
||||||
removeSnapFromCurrentSnaps(snapName, oldRevision)
|
removeSnapFromCurrentSnaps(snapName, oldRevision)
|
||||||
}
|
}
|
||||||
currentSnaps = append(currentSnaps, newSnap)
|
currentSnaps = append(currentSnaps, newSnap)
|
||||||
processedSnaps[snapName] = true
|
processedSnaps[snapName] = true
|
||||||
|
|
||||||
needsUpdate := (oldSnapPath == "" || oldSnap.Revision.N < info.Revision.N)
|
needsUpdate := (oldSnapPath == "" || oldSnap.Revision.N < info.Revision.N)
|
||||||
|
|
||||||
if needsUpdate {
|
if needsUpdate {
|
||||||
snapDetailsList = append(snapDetailsList, SnapDetails{
|
snapDetailsList = append(snapDetailsList, SnapDetails{
|
||||||
InstanceName: snapName,
|
InstanceName: snapName,
|
||||||
Channel: channel,
|
Channel: channel,
|
||||||
CurrentSnap: newSnap,
|
CurrentSnap: newSnap,
|
||||||
Result: result,
|
Result: result,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
// Mark the snap as required even if no update is needed
|
// Mark the snap as required even if no update is needed
|
||||||
requiredSnaps[snapName] = true
|
requiredSnaps[snapName] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Safely handle dependencies
|
// Safely handle dependencies
|
||||||
tracker := snap.SimplePrereqTracker{}
|
tracker := snap.SimplePrereqTracker{}
|
||||||
missingPrereqs := tracker.MissingProviderContentTags(info, nil)
|
missingPrereqs := tracker.MissingProviderContentTags(info, nil)
|
||||||
for prereq := range missingPrereqs {
|
for prereq := range missingPrereqs {
|
||||||
if !processedSnaps[prereq] {
|
if !processedSnaps[prereq] {
|
||||||
verboseLog("Collecting dependencies for prerequisite snap: %s for %s", prereq, snapName)
|
verboseLog("Collecting dependencies for prerequisite snap: %s for %s", prereq, snapName)
|
||||||
prereqDetails, err := collectSnapDependencies(prereq, "stable", snapsDir, assertionsDir)
|
prereqDetails, err := collectSnapDependencies(prereq, channel, fallbackChannel, snapsDir, assertionsDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Additional logging for dependency resolution issues
|
// Additional logging for dependency resolution issues
|
||||||
verboseLog("Failed to collect dependencies for prerequisite %s for snap %s: %v", prereq, snapName, err)
|
verboseLog("Failed to collect dependencies for prerequisite %s for snap %s: %v", prereq, snapName, err)
|
||||||
return nil, fmt.Errorf("failed to collect dependencies for prerequisite %s for snap %s: %v", prereq, snapName, err) // Added 0 for int64
|
return nil, fmt.Errorf("failed to collect dependencies for prerequisite %s for snap %s: %v", prereq, snapName, err) // Added 0 for int64
|
||||||
}
|
}
|
||||||
snapDetailsList = append(snapDetailsList, prereqDetails...)
|
snapDetailsList = append(snapDetailsList, prereqDetails...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Also handle base snaps safely
|
// Also handle base snaps safely
|
||||||
if info.Base != "" && !processedSnaps[info.Base] {
|
if info.Base != "" && !processedSnaps[info.Base] {
|
||||||
verboseLog("Collecting dependencies for base snap: %s for %s", info.Base, snapName)
|
verboseLog("Collecting dependencies for base snap: %s for %s", info.Base, snapName)
|
||||||
baseDetails, err := collectSnapDependencies(info.Base, "stable", snapsDir, assertionsDir)
|
baseDetails, err := collectSnapDependencies(info.Base, channel, fallbackChannel, snapsDir, assertionsDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
verboseLog("Failed to collect dependencies for base snap %s for snap %s: %v", info.Base, snapName, err)
|
verboseLog("Failed to collect dependencies for base snap %s for snap %s: %v", info.Base, snapName, err)
|
||||||
return nil, fmt.Errorf("failed to collect dependencies for base snap %s for snap %s: %v", info.Base, snapName, err) // Added 0 for int64
|
return nil, fmt.Errorf("failed to collect dependencies for base snap %s for snap %s: %v", info.Base, snapName, err) // Added 0 for int64
|
||||||
}
|
}
|
||||||
snapDetailsList = append(snapDetailsList, baseDetails...)
|
snapDetailsList = append(snapDetailsList, baseDetails...)
|
||||||
}
|
}
|
||||||
|
|
||||||
return snapDetailsList, nil
|
return snapDetailsList, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// processSnap handles the downloading and applying of a snap if updates are available.
|
// processSnap handles the downloading and applying of a snap if updates are available.
|
||||||
// It gracefully handles the "no updates available" scenario and ensures dependencies are marked as required.
|
// It gracefully handles the "no updates available" scenario and ensures dependencies are marked as required.
|
||||||
func processSnap(snapDetails SnapDetails, snapsDir, assertionsDir string) error {
|
func processSnap(snapDetails SnapDetails, snapsDir, assertionsDir string) error {
|
||||||
verboseLog("Processing snap: %s on channel: %s", snapDetails.InstanceName, snapDetails.Channel)
|
verboseLog("Processing snap: %s on channel: %s", snapDetails.InstanceName, snapDetails.Channel)
|
||||||
|
|
||||||
// Proceed with downloading the snap (either full or delta) using downloadAndApplySnap
|
// Proceed with downloading the snap (either full or delta) using downloadAndApplySnap
|
||||||
snapInfo, err := downloadAndApplySnap(storeClient, snapDetails.Result, snapsDir, assertionsDir, snapDetails.CurrentSnap)
|
snapInfo, err := downloadAndApplySnap(storeClient, snapDetails.Result, snapsDir, assertionsDir, snapDetails.CurrentSnap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to download snap %s: %w", snapDetails.InstanceName, err)
|
return fmt.Errorf("failed to download snap %s: %w", snapDetails.InstanceName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark the snap as required after successful download and application
|
// Mark the snap as required after successful download and application
|
||||||
requiredSnaps[snapDetails.InstanceName] = true
|
requiredSnaps[snapDetails.InstanceName] = true
|
||||||
verboseLog("Downloaded and applied snap: %s, revision: %d", snapInfo.SuggestedName, snapInfo.Revision.N)
|
verboseLog("Downloaded and applied snap: %s, revision: %d", snapInfo.SuggestedName, snapInfo.Revision.N)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// fetchOrRefreshSnapInfo retrieves snap information and returns the SnapActionResult including deltas.
|
// fetchOrRefreshSnapInfo retrieves snap information and returns the SnapActionResult including deltas.
|
||||||
func fetchOrRefreshSnapInfo(snapName string, currentSnap *store.CurrentSnap, channel string) (*store.SnapActionResult, error) {
|
func fetchOrRefreshSnapInfo(snapName string, currentSnap *store.CurrentSnap, channel string) (*store.SnapActionResult, error) {
|
||||||
var actions []*store.SnapAction
|
var actions []*store.SnapAction
|
||||||
var includeSnap []*store.CurrentSnap
|
var includeSnap []*store.CurrentSnap
|
||||||
if currentSnap != nil {
|
if currentSnap != nil {
|
||||||
verboseLog("Crafting refresh SnapAction for %s", snapName)
|
verboseLog("Crafting refresh SnapAction for %s", snapName)
|
||||||
actions = append(actions, &store.SnapAction{
|
actions = append(actions, &store.SnapAction{
|
||||||
Action: "refresh",
|
Action: "refresh",
|
||||||
SnapID: currentSnap.SnapID,
|
SnapID: currentSnap.SnapID,
|
||||||
InstanceName: snapName,
|
InstanceName: snapName,
|
||||||
Channel: channel,
|
Channel: channel,
|
||||||
})
|
})
|
||||||
includeSnap = []*store.CurrentSnap{currentSnap}
|
includeSnap = []*store.CurrentSnap{currentSnap}
|
||||||
} else {
|
} else {
|
||||||
verboseLog("Crafting install SnapAction for %s", snapName)
|
verboseLog("Crafting install SnapAction for %s", snapName)
|
||||||
actions = append(actions, &store.SnapAction{
|
actions = append(actions, &store.SnapAction{
|
||||||
Action: "install",
|
Action: "install",
|
||||||
InstanceName: snapName,
|
InstanceName: snapName,
|
||||||
Channel: channel,
|
Channel: channel,
|
||||||
})
|
})
|
||||||
includeSnap = []*store.CurrentSnap{}
|
includeSnap = []*store.CurrentSnap{}
|
||||||
}
|
}
|
||||||
|
|
||||||
results, _, err := storeClient.SnapAction(ctx, includeSnap, actions, nil, nil, nil)
|
results, _, err := storeClient.SnapAction(ctx, includeSnap, actions, nil, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
verboseLog("SnapAction error for %s: %v", snapName, err)
|
verboseLog("SnapAction error for %s: %v", snapName, err)
|
||||||
if strings.Contains(err.Error(), "snap has no updates available") && currentSnap != nil {
|
if strings.Contains(err.Error(), "snap has no updates available") && currentSnap != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("snap action failed for %s: %w", snapName, err)
|
return nil, fmt.Errorf("snap action failed for %s: %w", snapName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(results) == 0 || results[0].Info == nil {
|
if len(results) == 0 || results[0].Info == nil {
|
||||||
return nil, fmt.Errorf("no snap info returned for snap %s", snapName)
|
return nil, fmt.Errorf("no snap info returned for snap %s", snapName)
|
||||||
}
|
}
|
||||||
|
|
||||||
result := &results[0]
|
result := &results[0]
|
||||||
info := result.Info
|
info := result.Info
|
||||||
|
|
||||||
// Validate necessary fields in the snap information
|
// Validate necessary fields in the snap information
|
||||||
if info.SnapID == "" || info.Revision.N == 0 {
|
if info.SnapID == "" || info.Revision.N == 0 {
|
||||||
return nil, fmt.Errorf("invalid snap information for %s: SnapID or Revision is missing", snapName)
|
return nil, fmt.Errorf("invalid snap information for %s: SnapID or Revision is missing", snapName)
|
||||||
}
|
}
|
||||||
|
|
||||||
verboseLog("Fetched latest snap info for %s: SnapID: %s, Revision: %d", snapName, info.SnapID, info.Revision.N)
|
verboseLog("Fetched latest snap info for %s: SnapID: %s, Revision: %d", snapName, info.SnapID, info.Revision.N)
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// findPreviousSnap locates the previous snap revision in the downloads directory.
|
// findPreviousSnap locates the previous snap revision in the downloads directory.
|
||||||
func findPreviousSnap(downloadDir, assertionsDir, snapName string) (string, *store.CurrentSnap) {
|
func findPreviousSnap(downloadDir, assertionsDir, snapName string) (string, *store.CurrentSnap) {
|
||||||
var currentSnap store.CurrentSnap
|
var currentSnap store.CurrentSnap
|
||||||
files, err := os.ReadDir(downloadDir)
|
files, err := os.ReadDir(downloadDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
verboseLog("Error reading directory: %v", err)
|
verboseLog("Error reading directory: %v", err)
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var latestRevision int
|
var latestRevision int
|
||||||
var latestSnapPath string
|
var latestSnapPath string
|
||||||
|
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
if strings.HasPrefix(file.Name(), snapName+"_") && strings.HasSuffix(file.Name(), ".snap") {
|
if strings.HasPrefix(file.Name(), snapName+"_") && strings.HasSuffix(file.Name(), ".snap") {
|
||||||
revisionStr := extractRevisionFromFile(file.Name())
|
revisionStr := extractRevisionFromFile(file.Name())
|
||||||
if revisionStr == "" {
|
if revisionStr == "" {
|
||||||
verboseLog("Failed to extract revision from file name: %s", file.Name())
|
verboseLog("Failed to extract revision from file name: %s", file.Name())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
revision, err := strconv.Atoi(revisionStr)
|
revision, err := strconv.Atoi(revisionStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
verboseLog("Failed to parse revision number for file %s: %v", file.Name(), err)
|
verboseLog("Failed to parse revision number for file %s: %v", file.Name(), err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
verboseLog("Found %s with revision %d", file.Name(), revision)
|
verboseLog("Found %s with revision %d", file.Name(), revision)
|
||||||
|
|
||||||
if revision > latestRevision {
|
if revision > latestRevision {
|
||||||
latestRevision = revision
|
latestRevision = revision
|
||||||
latestSnapPath = filepath.Join(downloadDir, file.Name())
|
latestSnapPath = filepath.Join(downloadDir, file.Name())
|
||||||
|
|
||||||
// Parse the corresponding assertion file
|
// Parse the corresponding assertion file
|
||||||
assertFilePath := filepath.Join(assertionsDir, strings.Replace(file.Name(), ".snap", ".assert", 1))
|
assertFilePath := filepath.Join(assertionsDir, strings.Replace(file.Name(), ".snap", ".assert", 1))
|
||||||
currentSnap = parseSnapInfo(assertFilePath, snapName)
|
currentSnap = parseSnapInfo(assertFilePath, snapName)
|
||||||
currentSnap.Revision.N = revision
|
currentSnap.Revision.N = revision
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if latestSnapPath != "" {
|
if latestSnapPath != "" {
|
||||||
return latestSnapPath, ¤tSnap
|
return latestSnapPath, ¤tSnap
|
||||||
}
|
}
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
@ -1,114 +1,114 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
"github.com/snapcore/snapd/store"
|
"github.com/snapcore/snapd/store"
|
||||||
)
|
)
|
||||||
|
|
||||||
// initializeSeedYaml ensures that seed.yaml exists; if not, creates it.
|
// initializeSeedYaml ensures that seed.yaml exists; if not, creates it.
|
||||||
func initializeSeedYaml(seedYaml string) {
|
func initializeSeedYaml(seedYaml string) {
|
||||||
if _, err := os.Stat(seedYaml); os.IsNotExist(err) {
|
if _, err := os.Stat(seedYaml); os.IsNotExist(err) {
|
||||||
file, err := os.Create(seedYaml)
|
file, err := os.Create(seedYaml)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to create seed.yaml: %v", err)
|
log.Fatalf("Failed to create seed.yaml: %v", err)
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
file.WriteString("snaps:\n")
|
file.WriteString("snaps:\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadSeedData loads seed data from seed.yaml
|
// loadSeedData loads seed data from seed.yaml
|
||||||
func loadSeedData(seedYaml string) seed {
|
func loadSeedData(seedYaml string) seed {
|
||||||
file, err := ioutil.ReadFile(seedYaml)
|
file, err := ioutil.ReadFile(seedYaml)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to read seed.yaml: %v", err)
|
log.Fatalf("Failed to read seed.yaml: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var seedData seed
|
var seedData seed
|
||||||
if err := yaml.Unmarshal(file, &seedData); err != nil {
|
if err := yaml.Unmarshal(file, &seedData); err != nil {
|
||||||
log.Fatalf("Failed to parse seed.yaml: %v", err)
|
log.Fatalf("Failed to parse seed.yaml: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return seedData
|
return seedData
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadExistingSnaps loads snaps from seed.yaml into a map
|
// loadExistingSnaps loads snaps from seed.yaml into a map
|
||||||
func loadExistingSnaps(seedYaml string) map[string]bool {
|
func loadExistingSnaps(seedYaml string) map[string]bool {
|
||||||
file, err := ioutil.ReadFile(seedYaml)
|
file, err := ioutil.ReadFile(seedYaml)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to read seed.yaml: %v", err)
|
log.Fatalf("Failed to read seed.yaml: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var seedData seed
|
var seedData seed
|
||||||
if err := yaml.Unmarshal(file, &seedData); err != nil {
|
if err := yaml.Unmarshal(file, &seedData); err != nil {
|
||||||
log.Fatalf("Failed to parse seed.yaml: %v", err)
|
log.Fatalf("Failed to parse seed.yaml: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
existing := make(map[string]bool)
|
existing := make(map[string]bool)
|
||||||
for _, snap := range seedData.Snaps {
|
for _, snap := range seedData.Snaps {
|
||||||
existing[snap.Name] = true
|
existing[snap.Name] = true
|
||||||
verboseLog("Found %s in seed.yaml\n", snap.Name)
|
verboseLog("Found %s in seed.yaml\n", snap.Name)
|
||||||
}
|
}
|
||||||
return existing
|
return existing
|
||||||
}
|
}
|
||||||
|
|
||||||
// updateSeedYaml updates the seed.yaml file with the current required snaps
|
// updateSeedYaml updates the seed.yaml file with the current required snaps
|
||||||
func updateSeedYaml(snapsDir, seedYaml string, currentSnaps []*store.CurrentSnap) error {
|
func updateSeedYaml(snapsDir, seedYaml string, currentSnaps []*store.CurrentSnap) error {
|
||||||
// Log the snaps to be written
|
// Log the snaps to be written
|
||||||
verboseLog("CurrentSnaps to be written to seed.yaml:")
|
verboseLog("CurrentSnaps to be written to seed.yaml:")
|
||||||
for _, snapInfo := range currentSnaps {
|
for _, snapInfo := range currentSnaps {
|
||||||
verboseLog("- %s_%d.snap", snapInfo.InstanceName, snapInfo.Revision.N)
|
verboseLog("- %s_%d.snap", snapInfo.InstanceName, snapInfo.Revision.N)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load existing seed data
|
// Load existing seed data
|
||||||
file, err := ioutil.ReadFile(seedYaml)
|
file, err := ioutil.ReadFile(seedYaml)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to read seed.yaml: %w", err)
|
return fmt.Errorf("failed to read seed.yaml: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var seedData seed
|
var seedData seed
|
||||||
if err := yaml.Unmarshal(file, &seedData); err != nil {
|
if err := yaml.Unmarshal(file, &seedData); err != nil {
|
||||||
return fmt.Errorf("failed to parse seed.yaml: %w", err)
|
return fmt.Errorf("failed to parse seed.yaml: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear existing snaps
|
// Clear existing snaps
|
||||||
seedData.Snaps = []struct {
|
seedData.Snaps = []struct {
|
||||||
Name string `yaml:"name"`
|
Name string `yaml:"name"`
|
||||||
Channel string `yaml:"channel"`
|
Channel string `yaml:"channel"`
|
||||||
File string `yaml:"file"`
|
File string `yaml:"file"`
|
||||||
}{}
|
}{}
|
||||||
|
|
||||||
// Populate seedData with currentSnaps
|
// Populate seedData with currentSnaps
|
||||||
for _, snapInfo := range currentSnaps {
|
for _, snapInfo := range currentSnaps {
|
||||||
snapFileName := fmt.Sprintf("%s_%d.snap", snapInfo.InstanceName, snapInfo.Revision.N)
|
snapFileName := fmt.Sprintf("%s_%d.snap", snapInfo.InstanceName, snapInfo.Revision.N)
|
||||||
snapData := struct {
|
snapData := struct {
|
||||||
Name string `yaml:"name"`
|
Name string `yaml:"name"`
|
||||||
Channel string `yaml:"channel"`
|
Channel string `yaml:"channel"`
|
||||||
File string `yaml:"file"`
|
File string `yaml:"file"`
|
||||||
}{
|
}{
|
||||||
Name: snapInfo.InstanceName,
|
Name: snapInfo.InstanceName,
|
||||||
Channel: "stable", // Assuming 'stable' channel; modify as needed
|
Channel: "stable", // Assuming 'stable' channel; modify as needed
|
||||||
File: snapFileName,
|
File: snapFileName,
|
||||||
}
|
}
|
||||||
seedData.Snaps = append(seedData.Snaps, snapData)
|
seedData.Snaps = append(seedData.Snaps, snapData)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Marshal the updated seedData back to YAML
|
// Marshal the updated seedData back to YAML
|
||||||
updatedYAML, err := yaml.Marshal(&seedData)
|
updatedYAML, err := yaml.Marshal(&seedData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to marshal updated seed data: %w", err)
|
return fmt.Errorf("failed to marshal updated seed data: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write the updated YAML back to seed.yaml
|
// Write the updated YAML back to seed.yaml
|
||||||
if err := ioutil.WriteFile(seedYaml, updatedYAML, 0644); err != nil {
|
if err := ioutil.WriteFile(seedYaml, updatedYAML, 0644); err != nil {
|
||||||
return fmt.Errorf("failed to write updated seed.yaml: %w", err)
|
return fmt.Errorf("failed to write updated seed.yaml: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
verboseLog("Updated seed.yaml with current snaps.")
|
verboseLog("Updated seed.yaml with current snaps.")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -1,106 +1,106 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/snapcore/snapd/snap"
|
"github.com/snapcore/snapd/snap"
|
||||||
"github.com/snapcore/snapd/store"
|
"github.com/snapcore/snapd/store"
|
||||||
"golang.org/x/crypto/sha3"
|
"golang.org/x/crypto/sha3"
|
||||||
)
|
)
|
||||||
|
|
||||||
// verifyChecksum calculates the SHA3-384 checksum of a file and compares it with the expected checksum.
|
// verifyChecksum calculates the SHA3-384 checksum of a file and compares it with the expected checksum.
|
||||||
func verifyChecksum(filePath, expectedChecksum string) (bool, error) {
|
func verifyChecksum(filePath, expectedChecksum string) (bool, error) {
|
||||||
file, err := os.Open(filePath)
|
file, err := os.Open(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("failed to open file for checksum verification: %w", err)
|
return false, fmt.Errorf("failed to open file for checksum verification: %w", err)
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
hash := sha3.New384()
|
hash := sha3.New384()
|
||||||
if _, err := io.Copy(hash, file); err != nil {
|
if _, err := io.Copy(hash, file); err != nil {
|
||||||
return false, fmt.Errorf("failed to calculate checksum: %w", err)
|
return false, fmt.Errorf("failed to calculate checksum: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
calculatedChecksum := hex.EncodeToString(hash.Sum(nil))
|
calculatedChecksum := hex.EncodeToString(hash.Sum(nil))
|
||||||
return strings.EqualFold(calculatedChecksum, expectedChecksum), nil
|
return strings.EqualFold(calculatedChecksum, expectedChecksum), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// extractRevisionFromFile extracts the revision number from a file name by splitting at the last underscore.
|
// extractRevisionFromFile extracts the revision number from a file name by splitting at the last underscore.
|
||||||
func extractRevisionFromFile(fileName string) string {
|
func extractRevisionFromFile(fileName string) string {
|
||||||
lastUnderscore := strings.LastIndex(fileName, "_")
|
lastUnderscore := strings.LastIndex(fileName, "_")
|
||||||
if lastUnderscore == -1 {
|
if lastUnderscore == -1 {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
revisionWithSuffix := fileName[lastUnderscore+1:]
|
revisionWithSuffix := fileName[lastUnderscore+1:]
|
||||||
// Handle both .snap and .assert suffixes
|
// Handle both .snap and .assert suffixes
|
||||||
if strings.HasSuffix(revisionWithSuffix, ".snap") {
|
if strings.HasSuffix(revisionWithSuffix, ".snap") {
|
||||||
return strings.TrimSuffix(revisionWithSuffix, ".snap")
|
return strings.TrimSuffix(revisionWithSuffix, ".snap")
|
||||||
} else if strings.HasSuffix(revisionWithSuffix, ".assert") {
|
} else if strings.HasSuffix(revisionWithSuffix, ".assert") {
|
||||||
return strings.TrimSuffix(revisionWithSuffix, ".assert")
|
return strings.TrimSuffix(revisionWithSuffix, ".assert")
|
||||||
}
|
}
|
||||||
return revisionWithSuffix
|
return revisionWithSuffix
|
||||||
}
|
}
|
||||||
|
|
||||||
// fileExists checks if a file exists and is not a directory before we use it
|
// fileExists checks if a file exists and is not a directory before we use it
|
||||||
func fileExists(filename string) bool {
|
func fileExists(filename string) bool {
|
||||||
info, err := os.Stat(filename)
|
info, err := os.Stat(filename)
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return !info.IsDir()
|
return !info.IsDir()
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseSnapInfo extracts snap details from the assertion file and returns the snap's current revision.
|
// parseSnapInfo extracts snap details from the assertion file and returns the snap's current revision.
|
||||||
func parseSnapInfo(assertFilePath, snapName string) store.CurrentSnap {
|
func parseSnapInfo(assertFilePath, snapName string) store.CurrentSnap {
|
||||||
currentSnap := store.CurrentSnap{InstanceName: snapName}
|
currentSnap := store.CurrentSnap{InstanceName: snapName}
|
||||||
file, err := os.Open(assertFilePath)
|
file, err := os.Open(assertFilePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
verboseLog("Failed to read assertion file: %v\n", err)
|
verboseLog("Failed to read assertion file: %v\n", err)
|
||||||
return currentSnap
|
return currentSnap
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
scanner := bufio.NewScanner(file)
|
scanner := bufio.NewScanner(file)
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
line := strings.TrimSpace(scanner.Text())
|
line := strings.TrimSpace(scanner.Text())
|
||||||
if strings.HasPrefix(line, "snap-id:") {
|
if strings.HasPrefix(line, "snap-id:") {
|
||||||
currentSnap.SnapID = strings.TrimSpace(strings.SplitN(line, ":", 2)[1])
|
currentSnap.SnapID = strings.TrimSpace(strings.SplitN(line, ":", 2)[1])
|
||||||
}
|
}
|
||||||
if strings.HasPrefix(line, "snap-revision:") {
|
if strings.HasPrefix(line, "snap-revision:") {
|
||||||
revisionStr := strings.TrimSpace(strings.SplitN(line, ":", 2)[1])
|
revisionStr := strings.TrimSpace(strings.SplitN(line, ":", 2)[1])
|
||||||
revision, err := strconv.Atoi(revisionStr)
|
revision, err := strconv.Atoi(revisionStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
verboseLog("Failed to parse snap-revision for snap %s: %v", snapName, err)
|
verboseLog("Failed to parse snap-revision for snap %s: %v", snapName, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
currentSnap.Revision.N = revision
|
currentSnap.Revision.N = revision
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate that required fields are present
|
// Validate that required fields are present
|
||||||
if currentSnap.SnapID == "" || currentSnap.Revision.N == 0 {
|
if currentSnap.SnapID == "" || currentSnap.Revision.N == 0 {
|
||||||
verboseLog("Incomplete snap info in assertion file: %s", assertFilePath)
|
verboseLog("Incomplete snap info in assertion file: %s", assertFilePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
return currentSnap
|
return currentSnap
|
||||||
}
|
}
|
||||||
|
|
||||||
// isSnapInCurrentSnaps checks if a snap is already in currentSnaps
|
// isSnapInCurrentSnaps checks if a snap is already in currentSnaps
|
||||||
func isSnapInCurrentSnaps(snapName string) (bool, snap.Revision) {
|
func isSnapInCurrentSnaps(snapName string) (bool, snap.Revision) {
|
||||||
for _, snap := range currentSnaps {
|
for _, snap := range currentSnaps {
|
||||||
if snap.InstanceName == snapName {
|
if snap.InstanceName == snapName {
|
||||||
return true, snap.Revision
|
return true, snap.Revision
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false, snap.Revision{}
|
return false, snap.Revision{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeSnapFromCurrentSnaps(snapName string, revision snap.Revision) {
|
func removeSnapFromCurrentSnaps(snapName string, revision snap.Revision) {
|
||||||
@ -115,36 +115,36 @@ func removeSnapFromCurrentSnaps(snapName string, revision snap.Revision) {
|
|||||||
|
|
||||||
// initializeDirectories ensures that the snaps and assertions directories exist
|
// initializeDirectories ensures that the snaps and assertions directories exist
|
||||||
func initializeDirectories(snapsDir, assertionsDir string) {
|
func initializeDirectories(snapsDir, assertionsDir string) {
|
||||||
if err := os.MkdirAll(snapsDir, 0755); err != nil {
|
if err := os.MkdirAll(snapsDir, 0755); err != nil {
|
||||||
log.Fatalf("Failed to create snaps directory: %v", err)
|
log.Fatalf("Failed to create snaps directory: %v", err)
|
||||||
}
|
}
|
||||||
if err := os.MkdirAll(assertionsDir, 0755); err != nil {
|
if err := os.MkdirAll(assertionsDir, 0755); err != nil {
|
||||||
log.Fatalf("Failed to create assertions directory: %v", err)
|
log.Fatalf("Failed to create assertions directory: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// getCurrentSnapInfo retrieves current snap information from assertions
|
// getCurrentSnapInfo retrieves current snap information from assertions
|
||||||
func getCurrentSnapInfo(assertionsDir, snapName string) (*store.CurrentSnap, error) {
|
func getCurrentSnapInfo(assertionsDir, snapName string) (*store.CurrentSnap, error) {
|
||||||
assertionFiles, err := filepath.Glob(filepath.Join(assertionsDir, fmt.Sprintf("%s_*.assert", snapName)))
|
assertionFiles, err := filepath.Glob(filepath.Join(assertionsDir, fmt.Sprintf("%s_*.assert", snapName)))
|
||||||
if err != nil || len(assertionFiles) == 0 {
|
if err != nil || len(assertionFiles) == 0 {
|
||||||
return nil, fmt.Errorf("no assertion file found for snap: %s", snapName)
|
return nil, fmt.Errorf("no assertion file found for snap: %s", snapName)
|
||||||
}
|
}
|
||||||
|
|
||||||
assertionFile := assertionFiles[0]
|
assertionFile := assertionFiles[0]
|
||||||
currentSnap := parseSnapInfo(assertionFile, snapName)
|
currentSnap := parseSnapInfo(assertionFile, snapName)
|
||||||
if currentSnap.SnapID == "" || currentSnap.Revision.N == 0 {
|
if currentSnap.SnapID == "" || currentSnap.Revision.N == 0 {
|
||||||
return nil, fmt.Errorf("incomplete snap info in assertion file for snap: %s", snapName)
|
return nil, fmt.Errorf("incomplete snap info in assertion file for snap: %s", snapName)
|
||||||
}
|
}
|
||||||
verboseLog("Found snap info for %s: SnapID: %s, Revision: %d", snapName, currentSnap.SnapID, currentSnap.Revision.N)
|
verboseLog("Found snap info for %s: SnapID: %s, Revision: %d", snapName, currentSnap.SnapID, currentSnap.Revision.N)
|
||||||
return ¤tSnap, nil
|
return ¤tSnap, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// verifySnapIntegrity verifies the integrity of a snap file
|
// verifySnapIntegrity verifies the integrity of a snap file
|
||||||
func verifySnapIntegrity(filePath, expectedChecksum string) bool {
|
func verifySnapIntegrity(filePath, expectedChecksum string) bool {
|
||||||
checksumMatches, err := verifyChecksum(filePath, expectedChecksum)
|
checksumMatches, err := verifyChecksum(filePath, expectedChecksum)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
verboseLog("Checksum verification failed for %s: %v", filePath, err)
|
verboseLog("Checksum verification failed for %s: %v", filePath, err)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return checksumMatches
|
return checksumMatches
|
||||||
}
|
}
|
||||||
|
@ -1,108 +1,108 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// validateSeed validates the seed using snap debug
|
// validateSeed validates the seed using snap debug
|
||||||
func validateSeed(seedYaml string) error {
|
func validateSeed(seedYaml string) error {
|
||||||
cmd := exec.Command("snap", "debug", "validate-seed", seedYaml)
|
cmd := exec.Command("snap", "debug", "validate-seed", seedYaml)
|
||||||
output, err := cmd.CombinedOutput()
|
output, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("validation failed with output: %s, error: %v", string(output), err)
|
return fmt.Errorf("validation failed with output: %s, error: %v", string(output), err)
|
||||||
}
|
}
|
||||||
verboseLog("Seed validation successful: %s", string(output))
|
verboseLog("Seed validation successful: %s", string(output))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ensureAssertions ensures that essential assertions are present
|
// ensureAssertions ensures that essential assertions are present
|
||||||
func ensureAssertions(assertionsDir string) {
|
func ensureAssertions(assertionsDir string) {
|
||||||
model := "generic-classic"
|
model := "generic-classic"
|
||||||
brand := "generic"
|
brand := "generic"
|
||||||
series := "16" // Hardcoded series as snap.Info does not have a Series field
|
series := "16" // Hardcoded series as snap.Info does not have a Series field
|
||||||
|
|
||||||
modelAssertionPath := filepath.Join(assertionsDir, "model")
|
modelAssertionPath := filepath.Join(assertionsDir, "model")
|
||||||
accountKeyAssertionPath := filepath.Join(assertionsDir, "account-key")
|
accountKeyAssertionPath := filepath.Join(assertionsDir, "account-key")
|
||||||
accountAssertionPath := filepath.Join(assertionsDir, "account")
|
accountAssertionPath := filepath.Join(assertionsDir, "account")
|
||||||
|
|
||||||
// Check and generate model assertion
|
// Check and generate model assertion
|
||||||
if _, err := os.Stat(modelAssertionPath); os.IsNotExist(err) {
|
if _, err := os.Stat(modelAssertionPath); os.IsNotExist(err) {
|
||||||
output, err := exec.Command("snap", "known", "--remote", "model", "series="+series, "model="+model, "brand-id="+brand).CombinedOutput()
|
output, err := exec.Command("snap", "known", "--remote", "model", "series="+series, "model="+model, "brand-id="+brand).CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to fetch model assertion: %v, Output: %s", err, string(output))
|
log.Fatalf("Failed to fetch model assertion: %v, Output: %s", err, string(output))
|
||||||
}
|
}
|
||||||
if err := ioutil.WriteFile(modelAssertionPath, output, 0644); err != nil {
|
if err := ioutil.WriteFile(modelAssertionPath, output, 0644); err != nil {
|
||||||
log.Fatalf("Failed to write model assertion: %v", err)
|
log.Fatalf("Failed to write model assertion: %v", err)
|
||||||
}
|
}
|
||||||
verboseLog("Fetched and saved model assertion to %s", modelAssertionPath)
|
verboseLog("Fetched and saved model assertion to %s", modelAssertionPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate account-key assertion if not exists
|
// Generate account-key assertion if not exists
|
||||||
if _, err := os.Stat(accountKeyAssertionPath); os.IsNotExist(err) {
|
if _, err := os.Stat(accountKeyAssertionPath); os.IsNotExist(err) {
|
||||||
signKeySha3 := grepPattern(modelAssertionPath, "sign-key-sha3-384: ")
|
signKeySha3 := grepPattern(modelAssertionPath, "sign-key-sha3-384: ")
|
||||||
if signKeySha3 == "" {
|
if signKeySha3 == "" {
|
||||||
log.Fatalf("Failed to extract sign-key-sha3-384 from model assertion.")
|
log.Fatalf("Failed to extract sign-key-sha3-384 from model assertion.")
|
||||||
}
|
}
|
||||||
decodedSignKey, err := base64.StdEncoding.DecodeString(signKeySha3)
|
decodedSignKey, err := base64.StdEncoding.DecodeString(signKeySha3)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to decode sign-key-sha3-384: %v", err)
|
log.Fatalf("Failed to decode sign-key-sha3-384: %v", err)
|
||||||
}
|
}
|
||||||
encodedSignKey := base64.StdEncoding.EncodeToString(decodedSignKey)
|
encodedSignKey := base64.StdEncoding.EncodeToString(decodedSignKey)
|
||||||
output, err := exec.Command("snap", "known", "--remote", "account-key", "public-key-sha3-384="+encodedSignKey).CombinedOutput()
|
output, err := exec.Command("snap", "known", "--remote", "account-key", "public-key-sha3-384="+encodedSignKey).CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to fetch account-key assertion: %v, Output: %s", err, string(output))
|
log.Fatalf("Failed to fetch account-key assertion: %v, Output: %s", err, string(output))
|
||||||
}
|
}
|
||||||
if err := ioutil.WriteFile(accountKeyAssertionPath, output, 0644); err != nil {
|
if err := ioutil.WriteFile(accountKeyAssertionPath, output, 0644); err != nil {
|
||||||
log.Fatalf("Failed to write account-key assertion: %v", err)
|
log.Fatalf("Failed to write account-key assertion: %v", err)
|
||||||
}
|
}
|
||||||
verboseLog("Fetched and saved account-key assertion to %s", accountKeyAssertionPath)
|
verboseLog("Fetched and saved account-key assertion to %s", accountKeyAssertionPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate account assertion if not exists
|
// Generate account assertion if not exists
|
||||||
if _, err := os.Stat(accountAssertionPath); os.IsNotExist(err) {
|
if _, err := os.Stat(accountAssertionPath); os.IsNotExist(err) {
|
||||||
accountId := grepPattern(accountKeyAssertionPath, "account-id: ")
|
accountId := grepPattern(accountKeyAssertionPath, "account-id: ")
|
||||||
if accountId == "" {
|
if accountId == "" {
|
||||||
log.Fatalf("Failed to extract account-id from account-key assertion.")
|
log.Fatalf("Failed to extract account-id from account-key assertion.")
|
||||||
}
|
}
|
||||||
output, err := exec.Command("snap", "known", "--remote", "account", "account-id="+accountId).CombinedOutput()
|
output, err := exec.Command("snap", "known", "--remote", "account", "account-id="+accountId).CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to fetch account assertion: %v, Output: %s", err, string(output))
|
log.Fatalf("Failed to fetch account assertion: %v, Output: %s", err, string(output))
|
||||||
}
|
}
|
||||||
if err := ioutil.WriteFile(accountAssertionPath, output, 0644); err != nil {
|
if err := ioutil.WriteFile(accountAssertionPath, output, 0644); err != nil {
|
||||||
log.Fatalf("Failed to write account assertion: %v", err)
|
log.Fatalf("Failed to write account assertion: %v", err)
|
||||||
}
|
}
|
||||||
verboseLog("Fetched and saved account assertion to %s", accountAssertionPath)
|
verboseLog("Fetched and saved account assertion to %s", accountAssertionPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// grepPattern extracts a specific pattern from a file
|
// grepPattern extracts a specific pattern from a file
|
||||||
func grepPattern(filePath, pattern string) string {
|
func grepPattern(filePath, pattern string) string {
|
||||||
content, err := ioutil.ReadFile(filePath)
|
content, err := ioutil.ReadFile(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to read from file %s: %v", filePath, err)
|
log.Fatalf("Failed to read from file %s: %v", filePath, err)
|
||||||
}
|
}
|
||||||
lines := strings.Split(string(content), "\n")
|
lines := strings.Split(string(content), "\n")
|
||||||
for _, line := range lines {
|
for _, line := range lines {
|
||||||
if strings.Contains(line, pattern) {
|
if strings.Contains(line, pattern) {
|
||||||
parts := strings.SplitN(line, ":", 2)
|
parts := strings.SplitN(line, ":", 2)
|
||||||
if len(parts) == 2 {
|
if len(parts) == 2 {
|
||||||
encodedValue := strings.TrimSpace(parts[1])
|
encodedValue := strings.TrimSpace(parts[1])
|
||||||
// Check if the value is base64 encoded
|
// Check if the value is base64 encoded
|
||||||
if decodedBytes, err := base64.StdEncoding.DecodeString(encodedValue); err == nil {
|
if decodedBytes, err := base64.StdEncoding.DecodeString(encodedValue); err == nil {
|
||||||
return string(decodedBytes)
|
return string(decodedBytes)
|
||||||
}
|
}
|
||||||
return encodedValue
|
return encodedValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.Fatalf("Pattern %s not found in file %s", pattern, filePath)
|
log.Fatalf("Pattern %s not found in file %s", pattern, filePath)
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user