Compare commits

..

No commits in common. "0f9148c4e5ec8b2a1b33fc4571578a3e59679c55" and "6dbafb28d0a706aeb596ab2f634a0a7d0951e09a" have entirely different histories.

5 changed files with 42 additions and 117 deletions

8
debian/changelog vendored
View File

@ -1,11 +1,3 @@
snapd-extra-utils (1.1.0) plucky; urgency=medium
* [snapd-seed-glue] Fix installation of snaps in Calamares Full Installation
due to a missing account key, and add an autopkgtest to ensure we catch
this ahead of time in the future (LP: #2096649).
-- Simon Quigley <tsimonq2@ubuntu.com> Thu, 20 Feb 2025 14:09:13 -0600
snapd-extra-utils (1.0.7) plucky; urgency=medium
* Add several extra Breaks/Replaces to be safe.

View File

@ -1,3 +1,3 @@
Test-Command: snapd-seed-glue/tests/snapd_seed_glue_test
Depends: snapd-seed-glue, livecd-rootfs [amd64], squashfs-tools [amd64]
Restrictions: needs-internet, build-needed, isolation-machine, needs-sudo
Depends: snapd-seed-glue, tree
Restrictions: needs-internet, build-needed

View File

@ -73,45 +73,25 @@ func downloadAssertions(storeClient *store.Store, snapInfo *snap.Info, downloadD
return fmt.Errorf("failed to fetch account-key assertion for snap %s: %w", snapInfo.SuggestedName, err)
}
// Step 4: Fetch snap-revision assertion
// Step 4: Fetch account assertion using publisher-id
accountAssertion, err := storeClient.Assertion(assertionTypes["account"], []string{publisherID}, nil)
if err != nil {
return fmt.Errorf("failed to fetch account assertion for snap %s: %w", snapInfo.SuggestedName, err)
}
// Step 5: Fetch snap-revision assertion
snapSHA384Bytes, err := hex.DecodeString(snapSHA)
if err != nil {
return fmt.Errorf("error decoding SHA3-384 hex string for snap %s: %w", snapInfo.SuggestedName, err)
}
snapSHA384Base64 := base64.RawURLEncoding.EncodeToString(snapSHA384Bytes)
//revisionKey := fmt.Sprintf("%s/global-upload", snapSHA384Base64)
revisionKey := fmt.Sprintf("%s/", snapSHA384Base64)
snapRevisionAssertion, err := storeClient.Assertion(assertionTypes["snap-revision"], []string{revisionKey}, nil)
if err != nil {
verboseLog("Failed to fetch snap-revision assertion for snap %s: %v", snapInfo.SuggestedName, err)
}
// Step 5: Fetch account assertions
publisherAccountAssertion, err := storeClient.Assertion(assertionTypes["account"], []string{publisherID}, nil)
if err != nil {
return fmt.Errorf("failed to fetch developer account assertion for snap %s: %w", snapInfo.SuggestedName, err)
}
// Step 5.1: Determine authority account from snap-declaration
authorityID, ok := snapDecl.Header("authority-id").(string)
if !ok || authorityID == "" {
return fmt.Errorf("snap-declaration assertion missing 'authority-id' header for snap %s", snapInfo.SuggestedName)
}
// Step 5.2: Fetch authority account assertion
authorityAccountAssertion, err := storeClient.Assertion(assertionTypes["account"], []string{authorityID}, nil)
if err != nil {
return fmt.Errorf("failed to fetch authority account assertion for snap %s: %w", snapInfo.SuggestedName, err)
}
// Step 5.3: Fetch developer account assertion
developerID, ok := snapRevisionAssertion.Header("developer-id").(string)
developerAccountAssertion := authorityAccountAssertion
if ok && authorityID != "" {
developerAccountAssertion, err = storeClient.Assertion(assertionTypes["account"], []string{developerID}, nil)
if err != nil {
return fmt.Errorf("failed to fetch developer account assertion for snap %s: %w", snapInfo.SuggestedName, err)
}
// Proceeding without snap-revision might be acceptable based on your use-case
}
// Step 6: Write assertions in the desired order
@ -119,20 +99,12 @@ func downloadAssertions(storeClient *store.Store, snapInfo *snap.Info, downloadD
writeAssertion("account-key", accountKeyAssertion, assertionsFile)
// 2. account
writeAssertion("account", publisherAccountAssertion, assertionsFile)
if authorityID != publisherID {
writeAssertion("account", authorityAccountAssertion, assertionsFile)
}
writeAssertion("account", accountAssertion, assertionsFile)
// 3. snap-declaration
writeAssertion("snap-declaration", snapDecl, assertionsFile)
// 4. developer account if present
if developerAccountAssertion != authorityAccountAssertion {
writeAssertion("account", developerAccountAssertion, assertionsFile)
}
// 5. snap-revision (if fetched successfully)
// 4. snap-revision (if fetched successfully)
if snapRevisionAssertion != nil {
writeAssertion("snap-revision", snapRevisionAssertion, assertionsFile)
}
@ -166,13 +138,11 @@ func writeAssertion(assertionType string, assertion asserts.Assertion, file *os.
body := assertion.Body()
bodyLength := len(body)
headers := assertion.Headers()
verboseLog("Assertion headers: %v", headers)
// Only write the account assertion if it is not Canonical
if assertionType == "account" {
value, exists := headers["username"]
if exists && value == "canonical" {
verboseLog("Skipping assertion due to duplication in account file")
return
}
}

View File

@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.16)
cmake_minimum_required(VERSION 3.14)
project(snapd_seed_glue_test)
set(CMAKE_CXX_STANDARD 23)

View File

@ -1,4 +1,4 @@
// Copyright (C) 2024-2025 Simon Quigley <tsimonq2@ubuntu.com>
// Copyright (C) 2024 Simon Quigley <tsimonq2@ubuntu.com>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
@ -10,21 +10,14 @@
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
#include <array>
#include <cstdio>
#include <cstdlib>
#include <format>
#include <fstream>
#include <iostream>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>
#if defined(__x86_64__) || defined(__amd64__)
#include <filesystem>
#include <unistd.h>
#endif
#include <cstdlib>
#include <cstdio>
#include <array>
#include <stdexcept>
#include <utility>
std::string OUTPUT_FILE_CONTENTS;
@ -46,33 +39,28 @@ std::pair<std::string, int> execute_command(const std::string& cmd) {
}
void confirm_success() {
if (OUTPUT_FILE_CONTENTS.find("Cleanup and validation completed") != std::string::npos) OUTPUT_FILE_CONTENTS.clear();
else exit(1);
if (OUTPUT_FILE_CONTENTS.find("Cleanup and validation completed") != std::string::npos) {
// Success, clear OUTPUT_FILE_CONTENTS
OUTPUT_FILE_CONTENTS.clear();
} else {
exit(1);
}
}
void run_snapd_seed_glue(const std::vector<std::string>& args, const std::string& dir = "") {
std::string cmd;
if (!dir.empty()) cmd = std::format("snapd-seed-glue/snapd-seed-glue --verbose --seed {}", dir);
else cmd = "snapd-seed-glue/snapd-seed-glue --verbose --seed hello_test";
for (const auto& arg : args) cmd += " " + arg;
void run_snapd_seed_glue(const std::vector<std::string>& args) {
std::string cmd = "snapd-seed-glue/snapd-seed-glue --verbose --seed hello_test";
for (const auto& arg : args) {
cmd += " " + arg;
}
auto [output, exit_code] = execute_command(cmd);
// Append output to OUTPUT_FILE_CONTENTS
OUTPUT_FILE_CONTENTS += output;
if (exit_code != 0) exit(exit_code);
if (exit_code != 0) {
exit(1);
}
confirm_success();
}
#if defined(__x86_64__) || defined(__amd64__)
std::string get_version_codename() {
std::ifstream file("/etc/os-release");
if (!file.is_open()) return {};
std::string line;
constexpr std::string_view key = "VERSION_CODENAME=";
while (std::getline(file, line)) if (line.starts_with(key)) return line.substr(key.size());
return "";
}
#endif
int main() {
std::cout << "[snapd-seed-glue autopkgtest] Testing snapd-seed-glue with hello...\n";
run_snapd_seed_glue({"hello"});
@ -88,38 +76,13 @@ int main() {
std::string cmd = "/usr/bin/snapd-seed-glue --verbose --seed test_dir " + invalid_snap;
auto [output, exit_code] = execute_command(cmd);
OUTPUT_FILE_CONTENTS += output;
if (exit_code != 0) std::cout << "Fail expected\n";
if (OUTPUT_FILE_CONTENTS.find("cannot install snap \"" + invalid_snap + "\": snap not found") == std::string::npos) exit(1);
#if defined(__x86_64__) || defined(__amd64__)
std::cout << "[snapd-seed-glue autopkgtest] Confirm that a livefs can be created, and snapd-seed-glue can be used...\n";
// Logic taken from lp:launchpad-buildd/lpbuildd/target/build_livefs.py
setenv("PROJECT", "lubuntu", 1);
setenv("ARCH", "amd64", 1);
setenv("SUITE", get_version_codename().c_str(), 1);
if (!std::filesystem::create_directory("auto")) exit(1);
for (std::string lb_script : {"config", "build", "clean"}) {
auto [link_output, link_exit_code] = execute_command(std::format("ln -s /usr/share/livecd-rootfs/live-build/auto/{} auto/", lb_script));
if (link_exit_code != 0) exit(link_exit_code);
if (exit_code != 0) {
std::cout << "Fail expected\n";
}
if (OUTPUT_FILE_CONTENTS.find("cannot install snap \"" + invalid_snap + "\": snap not found") != std::string::npos) {
// Expected error message found
} else {
exit(1);
}
auto [clean_output, clean_exit_code] = execute_command("sudo -E lb clean --purge");
if (clean_exit_code != 0) exit(clean_exit_code);
auto [config_output, config_exit_code] = execute_command("lb config");
if (config_exit_code != 0) exit(config_exit_code);
auto [build_output, build_exit_code] = execute_command("sudo -E lb build");
if (build_exit_code != 0) exit(build_exit_code);
auto [unsquashfs_output, unsquashfs_exit_code] = execute_command("sudo unsquashfs livecd.lubuntu.minimal.standard.squashfs /var/lib/snapd/seed/");
if (unsquashfs_exit_code != 0) exit(unsquashfs_exit_code);
auto [clean2_output, clean2_exit_code] = execute_command("sudo -E lb clean --purge");
if (clean2_exit_code != 0) exit(clean2_exit_code);
auto [chown_output, chown_exit_code] = execute_command(std::format("sudo chown -R {}:{} squashfs-root/", getuid(), getgid()));
if (chown_exit_code != 0) exit(chown_exit_code);
std::cout << "[snapd-seed-glue autopkgtest] A livefs can be created. Confirm snapd-seed-glue can be used...\n";
run_snapd_seed_glue({"firefox", "firmware-updater"}, "squashfs-root/var/lib/snapd/seed/");
run_snapd_seed_glue({"firefox", "firmware-updater", "krita", "thunderbird"}, "squashfs-root/var/lib/snapd/seed/");
#endif
return 0;
}