From 1689d29e306acd4b24b8cb7bb248f27bb5cdbf3f Mon Sep 17 00:00:00 2001 From: Lincoln Wallace Date: Wed, 17 Jul 2024 11:22:19 -0300 Subject: [PATCH 01/41] add: extension env-injector Add extension to inject environment variables into snaps by it's command-chain. This extension takes snap options and transform then into environment variables. For the env-exporter program, Rust Tier 2 toolchains are marked as unsupported. Signed-off-by: Lincoln Wallace --- extensions/env-injector/Cargo.toml | 21 ++++ extensions/env-injector/src/main.rs | 161 +++++++++++++++++++++++++++ schema/snapcraft.json | 1 + snapcraft/extensions/env_injector.py | 138 +++++++++++++++++++++++ snapcraft/extensions/registry.py | 2 + 5 files changed, 323 insertions(+) create mode 100644 extensions/env-injector/Cargo.toml create mode 100644 extensions/env-injector/src/main.rs create mode 100644 snapcraft/extensions/env_injector.py diff --git a/extensions/env-injector/Cargo.toml b/extensions/env-injector/Cargo.toml new file mode 100644 index 0000000000..94a73715ca --- /dev/null +++ b/extensions/env-injector/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "env-exporter" +version = "0.1.0" +edition = "2021" + +[dependencies] +http-body-util = "0.1.2" +hyper = {git = "https://github.com/hyperium/hyper", branch = "master"} +hyper-util = "0.1.6" +hyperlocal = {git = "https://github.com/softprops/hyperlocal", branch = "main"} +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +tokio = { version = "1.35", features = ["full"] } + + +[profile.release] +panic = "abort" +strip = true +opt-level = "z" +lto = true +codegen-units = 1 diff --git a/extensions/env-injector/src/main.rs b/extensions/env-injector/src/main.rs new file mode 100644 index 0000000000..6148080581 --- /dev/null +++ b/extensions/env-injector/src/main.rs @@ -0,0 +1,161 @@ +use http_body_util::{BodyExt, Full}; +use hyper::body::Bytes; +use hyper::{Method, Request}; +use hyper_util::client::legacy::Client; +use hyperlocal::{UnixClientExt, UnixConnector, Uri}; +use std::error::Error; +use std::process::Command; +use std::collections::HashMap; +use std::path::Path; +use std::io::BufRead; + +const SNAPD_SOCKET: &str = "/run/snapd-snap.socket"; + +async fn snapdapi_req() -> Result> { + let url: hyperlocal::Uri = Uri::new(SNAPD_SOCKET, "/v2/snapctl").into(); + + let client: Client> = Client::unix(); + + let snap_context = std::env::var("SNAP_CONTEXT")?; + + let request_body = format!( + r#"{{"context-id":"{}","args":["get", "env", "envfile", "apps"]}}"#, + snap_context + ); + + let req: Request> = Request::builder() + .method(Method::POST) + .uri(url) + .body(Full::from(request_body))?; + + let mut res = client.request(req).await?; + + let mut body: Vec = Vec::new(); + + while let Some(frame_result) = res.frame().await { + let frame = frame_result?; + + if let Some(segment) = frame.data_ref() { + body.extend_from_slice(segment); + } + } + + Ok(serde_json::from_slice(&body)?) +} + +fn set_env_vars(app: &str, json: &serde_json::Value) -> Result<(), Box> { + let stdout_str = json["result"]["stdout"].as_str().ok_or("Invalid stdout")?; + let stdout_json: serde_json::Value = serde_json::from_str(stdout_str)?; + + fn process_env(env: &serde_json::Value) -> HashMap { + env.as_object() + .unwrap() + .iter() + .map(|(k, v)| { + let key = k.to_uppercase().replace("-", "_"); + (key, v.to_string()) + }) + .collect() + } + + if let Some(global_env) = stdout_json["env"].as_object() { + for (key, value) in process_env(&serde_json::Value::Object(global_env.clone())) { + std::env::set_var(key, value.trim_matches('"')); + } + } + + if let Some(app_env) = stdout_json["apps"][app]["env"].as_object() { + for (key, value) in process_env(&serde_json::Value::Object(app_env.clone())) { + std::env::set_var(key, value.trim_matches('"')); + } + } + + Ok(()) +} + +fn source_env_file(file_path: &str) -> std::io::Result> { + let path = Path::new(file_path); + + if !path.exists() { + eprintln!("File does not exist: {}", file_path); + return Err(std::io::Error::new(std::io::ErrorKind::NotFound, "File does not exist")); + } + + if !path.is_file() { + eprintln!("File is not readable: {}", file_path); + return Err(std::io::Error::new(std::io::ErrorKind::PermissionDenied, "File is not readable")); + } + + let file = std::fs::File::open(file_path)?; + let reader = std::io::BufReader::new(file); + + let mut env_vars = HashMap::new(); + for line in reader.lines() { + let line = line?; + if !line.trim().is_empty() && !line.starts_with('#') { + if let Some((key, value)) = line.split_once('=') { + env_vars.insert(key.to_string(), value.to_string()); + } + } + } + + Ok(env_vars) +} + +fn set_env_vars_from_file(app: &str, json: &serde_json::Value) -> Result<(), Box> { + // Extract the stdout JSON string and parse it + let stdout_str = json["result"]["stdout"].as_str().ok_or("Invalid stdout")?; + let stdout_json: serde_json::Value = serde_json::from_str(stdout_str)?; + + // Source the global envfile first + if let Some(global_envfile) = stdout_json["envfile"].as_str() { + if let Ok(env_vars) = source_env_file(global_envfile) { + for (key, value) in env_vars { + std::env::set_var(key, value); + } + } + } + + // Source the app-specific envfile + if let Some(app_envfile) = stdout_json["apps"][app]["envfile"].as_str() { + if let Ok(env_vars) = source_env_file(app_envfile) { + for (key, value) in env_vars { + std::env::set_var(key, value); + } + } + } + + Ok(()) +} + +#[tokio::main] +async fn run() -> Result<(), Box> { + + let json = snapdapi_req().await?; + + let app = std::env::var("env_alias")?; + + set_env_vars_from_file(&app, &json)?; + set_env_vars(&app, &json)?; + + Ok(()) +} + +fn main()-> Result<(), Box> { + + let args: Vec = std::env::args().collect(); + + if args.len() < 2 { + eprintln!("Usage: {} ", args[0]); + std::process::exit(1); + } + + let command = args[1].clone(); + let args = args[2..].to_vec(); + + run()?; + + Command::new(command).args(args).status()?; + + Ok(()) +} diff --git a/schema/snapcraft.json b/schema/snapcraft.json index 9da873f184..f0890b80f3 100644 --- a/schema/snapcraft.json +++ b/schema/snapcraft.json @@ -906,6 +906,7 @@ "uniqueItems": true, "items": { "enum": [ + "env-injector", "flutter-stable", "flutter-beta", "flutter-dev", diff --git a/snapcraft/extensions/env_injector.py b/snapcraft/extensions/env_injector.py new file mode 100644 index 0000000000..b8a2ebc45f --- /dev/null +++ b/snapcraft/extensions/env_injector.py @@ -0,0 +1,138 @@ +# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- +# +# Copyright 2024 Canonical Ltd. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 3 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""Extension to automatically set environment variables on snaps.""" + +from typing import Any, Dict, Optional, Tuple + +from overrides import overrides + +from .extension import Extension, get_extensions_data_dir + +class EnvInjectorExtension(Extension): + """Extension to automatically set environment variables on snaps. + + This extension allows you to transform snap options into environment + variables + + It configures your application to run a command-chain that transforms the + snap options into environment variables automatically. + + - To set global environment variables for all applications **inside** the snap: + + .. code-block:: shell + sudo snap set env.= + + - To set environment variables for a specific application **inside** the snap: + + .. code-block:: shell + sudo snap set apps..env.= + + - To set environment file inside the snap: + + .. code-block:: shell + sudo snap set env-file= + + """ + + @staticmethod + @overrides + def get_supported_bases() -> Tuple[str, ...]: + return ("core24",) + + + @staticmethod + @overrides + def get_supported_confinement() -> Tuple[str, ...]: + return ("strict", "devmode", "classic") + + + @staticmethod + @overrides + def is_experimental(base: Optional[str]) -> bool: + return True + + @overrides + def get_root_snippet(self) -> Dict[str, Any]: + return {} + + + @overrides + def get_app_snippet(self, *, app_name: str) -> Dict[str, Any]: + """Return the app snippet to apply.""" + return { + "command-chain": [ "bin/command-chain/env-exporter"], + "environment": { + "env_alias": f"{app_name}", + } + } + + @overrides + def get_part_snippet(self, *, plugin_name: str) -> Dict[str, Any]: + return { + "build-environment": [ + { "VARS_INJECT_EXTENSION": "true" } + ] + } + + @overrides + def get_parts_snippet(self) -> Dict[str, Any]: + toolchain = self.get_toolchain() + if (toolchain is None): + raise ValueError( + f"Unsupported architecture for env-injector extension: {self.arch}" + ) + return { + f"env-injector/env-injector": { + "source": f"{get_extensions_data_dir()}/env-injector", + "plugin": "nil", + "build-snaps": [ + f"rustup", + ], + "build-packages": [ + f"musl-tools", # for static linking + f"upx-ucl", # for binary compression + ], + "override-build": f""" + + rustup default stable + rustup target add {toolchain} + + cargo build --target {toolchain} --release + mkdir -p $SNAPCRAFT_PART_INSTALL/bin/command-chain + + cp target/{toolchain}/release/env-exporter $SNAPCRAFT_PART_INSTALL/bin/command-chain + + # compress the binary + upx --best --lzma target/{toolchain}/release/env-exporter + cp target/{toolchain}/release/env-exporter $SNAPCRAFT_PART_INSTALL/bin/command-chain/env-exporter-upx + + """, + + } + } + + def get_toolchain(self): + # Dictionary mapping architecture names + toolchain = { + 'amd64': 'x86_64-unknown-linux-gnu', + 'arm64': 'aarch64-unknown-linux-gnu', + # 'armhf': 'armv8-unknown-linux-gnueabihf', # Tier 2 toolchain + # 'riscv64': 'riscv64gc-unknown-linux-gnu', # Tier 2 toolchain + # 'ppc64el': 'powerpc64-unknown-linux-gnu', # Tier 2 toolchain + # 's390x': 's390x-unknown-linux-gnu', # Tier 2 toolchain + } + return toolchain.get(self.arch) \ No newline at end of file diff --git a/snapcraft/extensions/registry.py b/snapcraft/extensions/registry.py index 42c60af1d6..64906dad27 100644 --- a/snapcraft/extensions/registry.py +++ b/snapcraft/extensions/registry.py @@ -31,6 +31,7 @@ from .ros2_jazzy_desktop import ROS2JazzyDesktopExtension from .ros2_jazzy_ros_base import ROS2JazzyRosBaseExtension from .ros2_jazzy_ros_core import ROS2JazzyRosCoreExtension +from .env_injector import EnvInjectorExtension if TYPE_CHECKING: from .extension import Extension @@ -49,6 +50,7 @@ "ros2-jazzy-desktop": ROS2JazzyDesktopExtension, "kde-neon": KDENeon, "kde-neon-6": KDENeon6, + "env-injector": EnvInjectorExtension, } From c4394366c31708f4ef54d3dd5da2ea78f986be66 Mon Sep 17 00:00:00 2001 From: Lincoln Wallace Date: Tue, 23 Jul 2024 10:06:57 -0300 Subject: [PATCH 02/41] test: add spread tests for env-injector extension - add: spread test: * hello.c: application which consumes env variables * task.yaml: Tests to verify env-injector functionality Signed-off-by: Lincoln Wallace --- snapcraft/extensions/env_injector.py | 36 ++++++------- snapcraft/extensions/registry.py | 4 +- .../spread/extensions/env-injector/task.yaml | 53 +++++++++++++++++++ .../snaps/env-injector-hello/CMakeLists.txt | 4 ++ .../snaps/env-injector-hello/hello.c | 37 +++++++++++++ .../env-injector-hello/snap/snapcraft.yaml | 18 +++++++ tests/unit/commands/test_list_extensions.py | 2 + tests/unit/extensions/test_registry.py | 1 + 8 files changed, 132 insertions(+), 23 deletions(-) create mode 100644 tests/spread/extensions/env-injector/task.yaml create mode 100644 tests/spread/extensions/snaps/env-injector-hello/CMakeLists.txt create mode 100644 tests/spread/extensions/snaps/env-injector-hello/hello.c create mode 100644 tests/spread/extensions/snaps/env-injector-hello/snap/snapcraft.yaml diff --git a/snapcraft/extensions/env_injector.py b/snapcraft/extensions/env_injector.py index b8a2ebc45f..2146e49a7d 100644 --- a/snapcraft/extensions/env_injector.py +++ b/snapcraft/extensions/env_injector.py @@ -22,6 +22,7 @@ from .extension import Extension, get_extensions_data_dir + class EnvInjectorExtension(Extension): """Extension to automatically set environment variables on snaps. @@ -32,7 +33,7 @@ class EnvInjectorExtension(Extension): snap options into environment variables automatically. - To set global environment variables for all applications **inside** the snap: - + .. code-block:: shell sudo snap set env.= @@ -53,58 +54,51 @@ class EnvInjectorExtension(Extension): def get_supported_bases() -> Tuple[str, ...]: return ("core24",) - @staticmethod @overrides def get_supported_confinement() -> Tuple[str, ...]: return ("strict", "devmode", "classic") - @staticmethod @overrides def is_experimental(base: Optional[str]) -> bool: return True - + @overrides def get_root_snippet(self) -> Dict[str, Any]: return {} - @overrides def get_app_snippet(self, *, app_name: str) -> Dict[str, Any]: """Return the app snippet to apply.""" return { - "command-chain": [ "bin/command-chain/env-exporter"], + "command-chain": ["bin/command-chain/env-exporter"], "environment": { "env_alias": f"{app_name}", - } + }, } @overrides def get_part_snippet(self, *, plugin_name: str) -> Dict[str, Any]: - return { - "build-environment": [ - { "VARS_INJECT_EXTENSION": "true" } - ] - } + return {"build-environment": [{"SNAPCRAFT_ENV_INJECTOR_EXTENSION": "true"}]} @overrides def get_parts_snippet(self) -> Dict[str, Any]: toolchain = self.get_toolchain() - if (toolchain is None): + if toolchain is None: raise ValueError( f"Unsupported architecture for env-injector extension: {self.arch}" ) return { - f"env-injector/env-injector": { + "env-injector/env-injector": { "source": f"{get_extensions_data_dir()}/env-injector", "plugin": "nil", "build-snaps": [ - f"rustup", + "rustup", ], "build-packages": [ - f"musl-tools", # for static linking - f"upx-ucl", # for binary compression + "musl-tools", # for static linking + "upx-ucl", # for binary compression ], "override-build": f""" @@ -121,18 +115,18 @@ def get_parts_snippet(self) -> Dict[str, Any]: cp target/{toolchain}/release/env-exporter $SNAPCRAFT_PART_INSTALL/bin/command-chain/env-exporter-upx """, - } } def get_toolchain(self): + """Get the Rust toolchain for the current architecture.""" # Dictionary mapping architecture names toolchain = { - 'amd64': 'x86_64-unknown-linux-gnu', - 'arm64': 'aarch64-unknown-linux-gnu', + "amd64": "x86_64-unknown-linux-gnu", + "arm64": "aarch64-unknown-linux-gnu", # 'armhf': 'armv8-unknown-linux-gnueabihf', # Tier 2 toolchain # 'riscv64': 'riscv64gc-unknown-linux-gnu', # Tier 2 toolchain # 'ppc64el': 'powerpc64-unknown-linux-gnu', # Tier 2 toolchain # 's390x': 's390x-unknown-linux-gnu', # Tier 2 toolchain } - return toolchain.get(self.arch) \ No newline at end of file + return toolchain.get(self.arch) diff --git a/snapcraft/extensions/registry.py b/snapcraft/extensions/registry.py index 64906dad27..125bec4bef 100644 --- a/snapcraft/extensions/registry.py +++ b/snapcraft/extensions/registry.py @@ -20,6 +20,7 @@ from snapcraft import errors +from .env_injector import EnvInjectorExtension from .gnome import GNOME from .kde_neon import KDENeon from .kde_neon_6 import KDENeon6 @@ -31,7 +32,6 @@ from .ros2_jazzy_desktop import ROS2JazzyDesktopExtension from .ros2_jazzy_ros_base import ROS2JazzyRosBaseExtension from .ros2_jazzy_ros_core import ROS2JazzyRosCoreExtension -from .env_injector import EnvInjectorExtension if TYPE_CHECKING: from .extension import Extension @@ -39,6 +39,7 @@ ExtensionType = Type[Extension] _EXTENSIONS: Dict[str, "ExtensionType"] = { + "env-injector": EnvInjectorExtension, "gnome": GNOME, "ros2-humble": ROS2HumbleExtension, "ros2-humble-ros-core": ROS2HumbleRosCoreExtension, @@ -50,7 +51,6 @@ "ros2-jazzy-desktop": ROS2JazzyDesktopExtension, "kde-neon": KDENeon, "kde-neon-6": KDENeon6, - "env-injector": EnvInjectorExtension, } diff --git a/tests/spread/extensions/env-injector/task.yaml b/tests/spread/extensions/env-injector/task.yaml new file mode 100644 index 0000000000..3ab765df02 --- /dev/null +++ b/tests/spread/extensions/env-injector/task.yaml @@ -0,0 +1,53 @@ +summary: Build and run a basic hello-world snap using extensions + +systems: + - ubuntu-24.04 + +environment: + + SNAPCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS: "1" + SNAP_DIR: ../snaps/env-injector-hello + SNAP: env-injector-hello + +prepare: | + + #shellcheck source=tests/spread/tools/snapcraft-yaml.sh + . "$TOOLS_DIR/snapcraft-yaml.sh" + set_base "$SNAP_DIR/snap/snapcraft.yaml" + +restore: | + + cd "$SNAP_DIR" + snapcraft clean + rm -f ./*.snap + rm -rf ./squashfs-root + #shellcheck source=tests/spread/tools/snapcraft-yaml.sh + . "$TOOLS_DIR/snapcraft-yaml.sh" + restore_yaml "snap/snapcraft.yaml" + +execute: | + + cd "$SNAP_DIR" + snapcraft + + unsquashfs "${SNAP}"_1.0_*.snap + + # Check that the env-exporter program is present + [ -f squashfs-root/bin/command-chain/env-exporter ] + + # Check that the hello command is present + [ -f squashfs-root/usr/local/bin/hello ] + + snap install "${SNAP}"_1.0_*.snap --dangerous + + # Create envfile + echo 'HELLO_WORLD="Hello, World"' >> envfile.env + + # Set env vars: Global, App specific, and envfile + snap set env-injector-hello env.hello="Hello" + snap set env-injector-hello apps.hello.env.world="World" + snap set env-injector-hello envfile="${SNAP_DIR}"/enfile.env + + # Run the hello command + env-injector-hello.hello + diff --git a/tests/spread/extensions/snaps/env-injector-hello/CMakeLists.txt b/tests/spread/extensions/snaps/env-injector-hello/CMakeLists.txt new file mode 100644 index 0000000000..7c574d9c5b --- /dev/null +++ b/tests/spread/extensions/snaps/env-injector-hello/CMakeLists.txt @@ -0,0 +1,4 @@ +cmake_minimum_required(VERSION 3.5) +project(hello C) +add_executable(hello hello.c) +install(TARGETS hello RUNTIME DESTINATION bin) diff --git a/tests/spread/extensions/snaps/env-injector-hello/hello.c b/tests/spread/extensions/snaps/env-injector-hello/hello.c new file mode 100644 index 0000000000..4d442b0068 --- /dev/null +++ b/tests/spread/extensions/snaps/env-injector-hello/hello.c @@ -0,0 +1,37 @@ +#include +#include +#include + +int main(int argc, char const *argv[]) { + const char *envs[] = { + "HELLO", // This is set globally + "WORLD", // This is set for the app + "HELLO_WORLD", // This is set from env file + }; + + const char *expected[] = { + "Hello", + "World", + "Hello World", + }; + + int num_envs = sizeof(envs) / sizeof(envs[0]); + + for (int i = 0; i < num_envs; ++i) { + const char *env = getenv(envs[i]); + + if (env == NULL) { + fprintf(stderr, "\n[ERROR] Env. variable %s is not set.\n", envs[i]); + return 1; + } + + if (strcmp(env, expected[i]) != 0) { + fprintf(stderr, "\n[ERROR] Env. variable %s isn't set to the expected value.\n", envs[i]); + fprintf(stderr, "Expected: %s\n", expected[i]); + fprintf(stderr, "Got: %s\n", env); + return 1; + } + } + + return 0; +} diff --git a/tests/spread/extensions/snaps/env-injector-hello/snap/snapcraft.yaml b/tests/spread/extensions/snaps/env-injector-hello/snap/snapcraft.yaml new file mode 100644 index 0000000000..1fd271d2d9 --- /dev/null +++ b/tests/spread/extensions/snaps/env-injector-hello/snap/snapcraft.yaml @@ -0,0 +1,18 @@ +name: env-injector-hello +version: "1.0" +summary: test the env-injector extension +description: This is a basic snap for testing env-injector extension + +grade: devel +base: core24 +confinement: strict + +apps: + env-hello: + command: usr/local/bin/hello + extensions: [ env-injector ] + +parts: + hello: + plugin: cmake + source: . diff --git a/tests/unit/commands/test_list_extensions.py b/tests/unit/commands/test_list_extensions.py index 1900e1a4d7..07bc97c1ec 100644 --- a/tests/unit/commands/test_list_extensions.py +++ b/tests/unit/commands/test_list_extensions.py @@ -38,6 +38,7 @@ def test_command(emitter, command): """\ Extension name Supported bases ---------------------- ---------------------- + env-injector core24 fake-extension core22, core24 flutter-beta core18 flutter-dev core18 @@ -87,6 +88,7 @@ def test_command_extension_dups(emitter, command): """\ Extension name Supported bases ---------------------- ---------------------- + env-injector core24 flutter-beta core18 flutter-dev core18 flutter-master core18 diff --git a/tests/unit/extensions/test_registry.py b/tests/unit/extensions/test_registry.py index c823c92c60..0fefd4048c 100644 --- a/tests/unit/extensions/test_registry.py +++ b/tests/unit/extensions/test_registry.py @@ -24,6 +24,7 @@ @pytest.mark.usefixtures("fake_extension_experimental") def test_get_extension_names(): assert extensions.get_extension_names() == [ + "env-injector", "gnome", "ros2-humble", "ros2-humble-ros-core", From 774f067bdd46b2f65abf3cb0bef7c49658f949db Mon Sep 17 00:00:00 2001 From: Callahan Kovacs Date: Wed, 17 Jul 2024 10:57:51 -0500 Subject: [PATCH 03/41] feat(extensions): pass app name to `get_app_snippet()` Signed-off-by: Callahan Kovacs --- snapcraft/extensions/_ros2_humble_meta.py | 4 ++-- snapcraft/extensions/_ros2_jazzy_meta.py | 4 ++-- snapcraft/extensions/_utils.py | 2 +- snapcraft/extensions/extension.py | 7 +++++-- snapcraft/extensions/gnome.py | 2 +- snapcraft/extensions/kde_neon.py | 2 +- snapcraft/extensions/kde_neon_6.py | 2 +- snapcraft/extensions/ros2_humble.py | 2 +- snapcraft/extensions/ros2_jazzy.py | 2 +- tests/unit/conftest.py | 10 +++++----- tests/unit/extensions/test_gnome.py | 4 ++-- tests/unit/extensions/test_kde_neon.py | 2 +- tests/unit/extensions/test_kde_neon_6.py | 2 +- tests/unit/parts/extensions/test_ros2_humble.py | 2 +- tests/unit/parts/extensions/test_ros2_humble_meta.py | 2 +- tests/unit/parts/extensions/test_ros2_jazzy.py | 2 +- tests/unit/parts/extensions/test_ros2_jazzy_meta.py | 2 +- 17 files changed, 28 insertions(+), 25 deletions(-) diff --git a/snapcraft/extensions/_ros2_humble_meta.py b/snapcraft/extensions/_ros2_humble_meta.py index be3d3ecff8..3d23330d71 100644 --- a/snapcraft/extensions/_ros2_humble_meta.py +++ b/snapcraft/extensions/_ros2_humble_meta.py @@ -64,8 +64,8 @@ def get_root_snippet(self) -> Dict[str, Any]: return root_snippet @overrides - def get_app_snippet(self) -> Dict[str, Any]: - app_snippet = super().get_app_snippet() + def get_app_snippet(self, *, app_name: str) -> Dict[str, Any]: + app_snippet = super().get_app_snippet(app_name=app_name) python_paths = app_snippet["environment"]["PYTHONPATH"] new_python_paths = [ f"$SNAP/opt/ros/underlay_ws/opt/ros/{self.ROS_DISTRO}/lib/python3.10/site-packages", diff --git a/snapcraft/extensions/_ros2_jazzy_meta.py b/snapcraft/extensions/_ros2_jazzy_meta.py index 9dcf31ad0d..b230119661 100644 --- a/snapcraft/extensions/_ros2_jazzy_meta.py +++ b/snapcraft/extensions/_ros2_jazzy_meta.py @@ -64,8 +64,8 @@ def get_root_snippet(self) -> Dict[str, Any]: return root_snippet @overrides - def get_app_snippet(self) -> Dict[str, Any]: - app_snippet = super().get_app_snippet() + def get_app_snippet(self, *, app_name: str) -> Dict[str, Any]: + app_snippet = super().get_app_snippet(app_name=app_name) python_paths = app_snippet["environment"]["PYTHONPATH"] new_python_paths = [ f"$SNAP/opt/ros/underlay_ws/opt/ros/{self.ROS_DISTRO}/lib/python3.12/site-packages", diff --git a/snapcraft/extensions/_utils.py b/snapcraft/extensions/_utils.py index 3aa885be35..d382ca44db 100644 --- a/snapcraft/extensions/_utils.py +++ b/snapcraft/extensions/_utils.py @@ -79,8 +79,8 @@ def _apply_extension( ) # Apply the app-specific components of the extension (if any) - app_extension = extension.get_app_snippet() for app_name in app_names: + app_extension = extension.get_app_snippet(app_name=app_name) app_definition = yaml_data["apps"][app_name] for property_name, property_value in app_extension.items(): app_definition[property_name] = _apply_extension_property( diff --git a/snapcraft/extensions/extension.py b/snapcraft/extensions/extension.py index 52cdfd902e..bf3c4bb04d 100644 --- a/snapcraft/extensions/extension.py +++ b/snapcraft/extensions/extension.py @@ -66,8 +66,11 @@ def get_root_snippet(self) -> Dict[str, Any]: """Return the root snippet to apply.""" @abc.abstractmethod - def get_app_snippet(self) -> Dict[str, Any]: - """Return the app snippet to apply.""" + def get_app_snippet(self, *, app_name: str) -> Dict[str, Any]: + """Return the app snippet to apply. + + :param app_name: the name of the app where the snippet will be applied + """ @abc.abstractmethod def get_part_snippet(self, *, plugin_name: str) -> Dict[str, Any]: diff --git a/snapcraft/extensions/gnome.py b/snapcraft/extensions/gnome.py index 30e7eeeb23..0f0c3be7ab 100644 --- a/snapcraft/extensions/gnome.py +++ b/snapcraft/extensions/gnome.py @@ -85,7 +85,7 @@ def is_experimental(base: Optional[str]) -> bool: return False @overrides - def get_app_snippet(self) -> Dict[str, Any]: + def get_app_snippet(self, *, app_name: str) -> Dict[str, Any]: command_chain = ["snap/command-chain/desktop-launch"] if self.yaml_data["base"] == "core24": command_chain.insert(0, "snap/command-chain/gpu-2404-wrapper") diff --git a/snapcraft/extensions/kde_neon.py b/snapcraft/extensions/kde_neon.py index 587418b055..e4f0f7b534 100644 --- a/snapcraft/extensions/kde_neon.py +++ b/snapcraft/extensions/kde_neon.py @@ -89,7 +89,7 @@ def is_experimental(base: Optional[str]) -> bool: return False @overrides - def get_app_snippet(self) -> Dict[str, Any]: + def get_app_snippet(self, *, app_name: str) -> Dict[str, Any]: return { "command-chain": ["snap/command-chain/desktop-launch"], "plugs": ["desktop", "desktop-legacy", "opengl", "wayland", "x11"], diff --git a/snapcraft/extensions/kde_neon_6.py b/snapcraft/extensions/kde_neon_6.py index ab4a362774..765b981762 100644 --- a/snapcraft/extensions/kde_neon_6.py +++ b/snapcraft/extensions/kde_neon_6.py @@ -86,7 +86,7 @@ def is_experimental(base: Optional[str]) -> bool: return False @overrides - def get_app_snippet(self) -> Dict[str, Any]: + def get_app_snippet(self, *, app_name: str) -> Dict[str, Any]: return { "command-chain": ["snap/command-chain/desktop-launch6"], "plugs": [ diff --git a/snapcraft/extensions/ros2_humble.py b/snapcraft/extensions/ros2_humble.py index 56f33f56dc..5467c3d884 100644 --- a/snapcraft/extensions/ros2_humble.py +++ b/snapcraft/extensions/ros2_humble.py @@ -87,7 +87,7 @@ def get_root_snippet(self) -> Dict[str, Any]: } @overrides - def get_app_snippet(self) -> Dict[str, Any]: + def get_app_snippet(self, *, app_name: str) -> Dict[str, Any]: python_paths = [ f"$SNAP/opt/ros/{self.ROS_DISTRO}/lib/python3.10/site-packages", "$SNAP/usr/lib/python3/dist-packages", diff --git a/snapcraft/extensions/ros2_jazzy.py b/snapcraft/extensions/ros2_jazzy.py index 635d23fd2a..ae17efd5ba 100644 --- a/snapcraft/extensions/ros2_jazzy.py +++ b/snapcraft/extensions/ros2_jazzy.py @@ -85,7 +85,7 @@ def get_root_snippet(self) -> Dict[str, Any]: } @overrides - def get_app_snippet(self) -> Dict[str, Any]: + def get_app_snippet(self, *, app_name: str) -> Dict[str, Any]: python_paths = [ f"$SNAP/opt/ros/{self.ROS_DISTRO}/lib/python3.12/site-packages", "$SNAP/usr/lib/python3/dist-packages", diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index 285cddb152..6f1b79b3cf 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -97,7 +97,7 @@ def is_experimental(base: Optional[str] = None) -> bool: def get_root_snippet(self) -> Dict[str, Any]: return {"grade": "fake-grade"} - def get_app_snippet(self) -> Dict[str, Any]: + def get_app_snippet(self, *, app_name: str) -> Dict[str, Any]: return {"plugs": ["fake-plug"]} def get_part_snippet(self, *, plugin_name: str) -> Dict[str, Any]: @@ -136,7 +136,7 @@ def is_experimental(base: Optional[str] = None) -> bool: def get_root_snippet(self) -> Dict[str, Any]: return {} - def get_app_snippet(self) -> Dict[str, Any]: + def get_app_snippet(self, *, app_name: str) -> Dict[str, Any]: return {"plugs": ["fake-plug", "fake-plug-extra"]} def get_part_snippet(self, *, plugin_name: str) -> Dict[str, Any]: @@ -170,7 +170,7 @@ def is_experimental(base: Optional[str] = None) -> bool: def get_root_snippet(self) -> Dict[str, Any]: return {"grade": "fake-grade"} - def get_app_snippet(self) -> Dict[str, Any]: + def get_app_snippet(self, *, app_name: str) -> Dict[str, Any]: return {"plugs": ["fake-plug"]} def get_part_snippet(self, *, plugin_name: str) -> Dict[str, Any]: @@ -206,7 +206,7 @@ def is_experimental(base: Optional[str] = None) -> bool: def get_root_snippet(self) -> Dict[str, Any]: return {} - def get_app_snippet(self) -> Dict[str, Any]: + def get_app_snippet(self, *, app_name: str) -> Dict[str, Any]: return {} def get_part_snippet(self, *, plugin_name: str) -> Dict[str, Any]: @@ -242,7 +242,7 @@ def is_experimental(base: Optional[str] = None) -> bool: def get_root_snippet(self) -> Dict[str, Any]: return {} - def get_app_snippet(self) -> Dict[str, Any]: + def get_app_snippet(self, *, app_name: str) -> Dict[str, Any]: return {"plugs": ["fake-plug", "fake-plug-extra"]} def get_part_snippet(self, *, plugin_name: str) -> Dict[str, Any]: diff --git a/tests/unit/extensions/test_gnome.py b/tests/unit/extensions/test_gnome.py index 7276dbb402..217194a2ce 100644 --- a/tests/unit/extensions/test_gnome.py +++ b/tests/unit/extensions/test_gnome.py @@ -81,14 +81,14 @@ def test_is_experimental(base): def test_get_app_snippet(gnome_extension): - assert gnome_extension.get_app_snippet() == { + assert gnome_extension.get_app_snippet(app_name="test-app") == { "command-chain": ["snap/command-chain/desktop-launch"], "plugs": ["desktop", "desktop-legacy", "gsettings", "opengl", "wayland", "x11"], } def test_get_app_snippet_core24(gnome_extension_core24): - assert gnome_extension_core24.get_app_snippet() == { + assert gnome_extension_core24.get_app_snippet(app_name="test-app") == { "command-chain": [ "snap/command-chain/gpu-2404-wrapper", "snap/command-chain/desktop-launch", diff --git a/tests/unit/extensions/test_kde_neon.py b/tests/unit/extensions/test_kde_neon.py index dda264abb4..35387a2a62 100644 --- a/tests/unit/extensions/test_kde_neon.py +++ b/tests/unit/extensions/test_kde_neon.py @@ -81,7 +81,7 @@ def test_is_experimental(): def test_get_app_snippet(kde_neon_extension): - assert kde_neon_extension.get_app_snippet() == { + assert kde_neon_extension.get_app_snippet(app_name="test-app") == { "command-chain": ["snap/command-chain/desktop-launch"], "plugs": ["desktop", "desktop-legacy", "opengl", "wayland", "x11"], } diff --git a/tests/unit/extensions/test_kde_neon_6.py b/tests/unit/extensions/test_kde_neon_6.py index 47340f290f..b59d474d6e 100644 --- a/tests/unit/extensions/test_kde_neon_6.py +++ b/tests/unit/extensions/test_kde_neon_6.py @@ -88,7 +88,7 @@ def test_is_experimental(): def test_get_app_snippet(kde_neon_6_extension): - assert kde_neon_6_extension.get_app_snippet() == { + assert kde_neon_6_extension.get_app_snippet(app_name="test-app") == { "command-chain": ["snap/command-chain/desktop-launch6"], "plugs": [ "desktop", diff --git a/tests/unit/parts/extensions/test_ros2_humble.py b/tests/unit/parts/extensions/test_ros2_humble.py index 53ad5b921b..4bbd91d771 100644 --- a/tests/unit/parts/extensions/test_ros2_humble.py +++ b/tests/unit/parts/extensions/test_ros2_humble.py @@ -110,7 +110,7 @@ def test_get_app_snippet(self, setup_method_fixture): "${PYTHONPATH}", ] extension = setup_method_fixture() - assert extension.get_app_snippet() == { + assert extension.get_app_snippet(app_name="test-app") == { "command-chain": ["snap/command-chain/ros2-launch"], "environment": { "ROS_VERSION": "2", diff --git a/tests/unit/parts/extensions/test_ros2_humble_meta.py b/tests/unit/parts/extensions/test_ros2_humble_meta.py index 67a439be02..1ec41625b2 100644 --- a/tests/unit/parts/extensions/test_ros2_humble_meta.py +++ b/tests/unit/parts/extensions/test_ros2_humble_meta.py @@ -142,7 +142,7 @@ def test_get_app_snippet(self, extension_name, extension_class, meta, meta_dev): "$SNAP/opt/ros/underlay_ws/usr/lib/python3/dist-packages", ] extension = setup_method_fixture(extension_class) - assert extension.get_app_snippet() == { + assert extension.get_app_snippet(app_name="test-app") == { "command-chain": ["snap/command-chain/ros2-launch"], "environment": { "ROS_VERSION": "2", diff --git a/tests/unit/parts/extensions/test_ros2_jazzy.py b/tests/unit/parts/extensions/test_ros2_jazzy.py index 664d52ad7b..8a33a13d51 100644 --- a/tests/unit/parts/extensions/test_ros2_jazzy.py +++ b/tests/unit/parts/extensions/test_ros2_jazzy.py @@ -110,7 +110,7 @@ def test_get_app_snippet(self, setup_method_fixture): "${PYTHONPATH}", ] extension = setup_method_fixture() - assert extension.get_app_snippet() == { + assert extension.get_app_snippet(app_name="test-app") == { "command-chain": ["snap/command-chain/ros2-launch"], "environment": { "ROS_VERSION": "2", diff --git a/tests/unit/parts/extensions/test_ros2_jazzy_meta.py b/tests/unit/parts/extensions/test_ros2_jazzy_meta.py index 7f5b996220..a772858e2b 100644 --- a/tests/unit/parts/extensions/test_ros2_jazzy_meta.py +++ b/tests/unit/parts/extensions/test_ros2_jazzy_meta.py @@ -142,7 +142,7 @@ def test_get_app_snippet(self, extension_name, extension_class, meta, meta_dev): "$SNAP/opt/ros/underlay_ws/usr/lib/python3/dist-packages", ] extension = setup_method_fixture(extension_class) - assert extension.get_app_snippet() == { + assert extension.get_app_snippet(app_name="test-app") == { "command-chain": ["snap/command-chain/ros2-launch"], "environment": { "ROS_VERSION": "2", From 04dd07eb8ef6a8c6a681fec6a5e4be783664e3c2 Mon Sep 17 00:00:00 2001 From: Lincoln Wallace Date: Wed, 24 Jul 2024 15:57:01 -0300 Subject: [PATCH 04/41] refact: address review comments Signed-off-by: Lincoln Wallace --- extensions/env-injector/Cargo.toml | 5 ++--- snapcraft/extensions/env_injector.py | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/extensions/env-injector/Cargo.toml b/extensions/env-injector/Cargo.toml index 94a73715ca..fe2b664a4c 100644 --- a/extensions/env-injector/Cargo.toml +++ b/extensions/env-injector/Cargo.toml @@ -5,14 +5,13 @@ edition = "2021" [dependencies] http-body-util = "0.1.2" -hyper = {git = "https://github.com/hyperium/hyper", branch = "master"} +hyper = {git = "https://github.com/hyperium/hyper", tag = "v1.4.1"} hyper-util = "0.1.6" -hyperlocal = {git = "https://github.com/softprops/hyperlocal", branch = "main"} +hyperlocal = {git = "https://github.com/softprops/hyperlocal", tag = "v0.9.1"} serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" tokio = { version = "1.35", features = ["full"] } - [profile.release] panic = "abort" strip = true diff --git a/snapcraft/extensions/env_injector.py b/snapcraft/extensions/env_injector.py index 2146e49a7d..e48df9dc5c 100644 --- a/snapcraft/extensions/env_injector.py +++ b/snapcraft/extensions/env_injector.py @@ -80,7 +80,7 @@ def get_app_snippet(self, *, app_name: str) -> Dict[str, Any]: @overrides def get_part_snippet(self, *, plugin_name: str) -> Dict[str, Any]: - return {"build-environment": [{"SNAPCRAFT_ENV_INJECTOR_EXTENSION": "true"}]} + return {} @overrides def get_parts_snippet(self) -> Dict[str, Any]: From a6374acbf8a0d215c610532fa00f59afdbb75945 Mon Sep 17 00:00:00 2001 From: Lincoln Wallace Date: Fri, 2 Aug 2024 00:33:47 -0300 Subject: [PATCH 05/41] feat: address reviews, add more edge cases to test Signed-off-by: Lincoln Wallace --- extensions/env-injector/src/main.rs | 51 +++++++++++++++---- snapcraft/extensions/env_injector.py | 4 +- .../spread/extensions/env-injector/task.yaml | 27 +++++++--- .../snaps/env-injector-hello/CMakeLists.txt | 6 +++ .../snaps/env-injector-hello/hello.c | 44 +++++++++++++--- .../snaps/env-injector-hello/hello2.c | 46 +++++++++++++++++ .../env-injector-hello/snap/snapcraft.yaml | 12 ++++- 7 files changed, 166 insertions(+), 24 deletions(-) create mode 100644 tests/spread/extensions/snaps/env-injector-hello/hello2.c diff --git a/extensions/env-injector/src/main.rs b/extensions/env-injector/src/main.rs index 6148080581..7c3a35bcd6 100644 --- a/extensions/env-injector/src/main.rs +++ b/extensions/env-injector/src/main.rs @@ -43,20 +43,51 @@ async fn snapdapi_req() -> Result HashMap { + let obj = env.as_object() + .ok_or("Expected an object (JSON input)").unwrap(); + let mut map = HashMap::new(); + + for (k, v) in obj { + if v.is_object() || v.is_array() { + eprintln!( + "ERROR: Invalid value detected.\n\ + Key: {}\n\ + Reason: Environment variable names must not contain dots.\n\ + Action: Skipped\n", + v + ); + continue; + } + + let key = k.to_uppercase().replace("-", "_"); + let value = match v { + serde_json::Value::String(s) => s.clone(), + serde_json::Value::Number(n) => n.to_string(), + serde_json::Value::Bool(b) => b.to_string(), + _ => { + eprintln!( + "ERROR: Invalid type for environment variable value detected.\n\ + val: {}\n\ + Reason: It must be a string, number or boolean\n\ + Action: Skipped\n", + k + ); + continue; + }, + }; + map.insert(key, value); + } + map +} + + + + fn set_env_vars(app: &str, json: &serde_json::Value) -> Result<(), Box> { let stdout_str = json["result"]["stdout"].as_str().ok_or("Invalid stdout")?; let stdout_json: serde_json::Value = serde_json::from_str(stdout_str)?; - fn process_env(env: &serde_json::Value) -> HashMap { - env.as_object() - .unwrap() - .iter() - .map(|(k, v)| { - let key = k.to_uppercase().replace("-", "_"); - (key, v.to_string()) - }) - .collect() - } if let Some(global_env) = stdout_json["env"].as_object() { for (key, value) in process_env(&serde_json::Value::Object(global_env.clone())) { diff --git a/snapcraft/extensions/env_injector.py b/snapcraft/extensions/env_injector.py index e48df9dc5c..252ad6d13b 100644 --- a/snapcraft/extensions/env_injector.py +++ b/snapcraft/extensions/env_injector.py @@ -46,6 +46,9 @@ class EnvInjectorExtension(Extension): .. code-block:: shell sudo snap set env-file= + - To set environment file for a specific app: + .. code-block:: shell + sudo snap set apps..envfile= """ @@ -97,7 +100,6 @@ def get_parts_snippet(self) -> Dict[str, Any]: "rustup", ], "build-packages": [ - "musl-tools", # for static linking "upx-ucl", # for binary compression ], "override-build": f""" diff --git a/tests/spread/extensions/env-injector/task.yaml b/tests/spread/extensions/env-injector/task.yaml index 3ab765df02..c2ee87d0de 100644 --- a/tests/spread/extensions/env-injector/task.yaml +++ b/tests/spread/extensions/env-injector/task.yaml @@ -41,13 +41,28 @@ execute: | snap install "${SNAP}"_1.0_*.snap --dangerous # Create envfile - echo 'HELLO_WORLD="Hello, World"' >> envfile.env + echo 'HELLO_WORLD=Hello World' >> envfile.env - # Set env vars: Global, App specific, and envfile - snap set env-injector-hello env.hello="Hello" - snap set env-injector-hello apps.hello.env.world="World" - snap set env-injector-hello envfile="${SNAP_DIR}"/enfile.env + # Set env vars: Global + snap set env-injector-hello env.global="World" + + # Set env vars: specific to each app + snap set env-injector-hello apps.hello1.env.hello="Hello" + snap set env-injector-hello apps.hello2.env.specific="City" + + # To Check if key with dot will be rejected + snap set env-injector-hello env.word.dot="wrong" + + # To test order of env vars + echo 'ORDER="From envfile"' >> envfile.env + snap set env-injector-hello apps.hello1.env.order="from app-specific" + snap set env-injector-hello envfile="${SNAP_DIR}"/envfile.env # Run the hello command - env-injector-hello.hello + # Those will fail if the env vars are not set as expected + env-injector-hello.hello1 + env-injector-hello.hello2 + # Test if the alias it's rewrite + snap set env-injector-hello apps.hello-demo.env.specific="City" + env-injector-hello.hello-demo diff --git a/tests/spread/extensions/snaps/env-injector-hello/CMakeLists.txt b/tests/spread/extensions/snaps/env-injector-hello/CMakeLists.txt index 7c574d9c5b..b4d8ba4757 100644 --- a/tests/spread/extensions/snaps/env-injector-hello/CMakeLists.txt +++ b/tests/spread/extensions/snaps/env-injector-hello/CMakeLists.txt @@ -1,4 +1,10 @@ cmake_minimum_required(VERSION 3.5) project(hello C) + +# Add executable for hello add_executable(hello hello.c) install(TARGETS hello RUNTIME DESTINATION bin) + +# Add executable for hello2 +add_executable(hello2 hello2.c) +install(TARGETS hello2 RUNTIME DESTINATION bin) \ No newline at end of file diff --git a/tests/spread/extensions/snaps/env-injector-hello/hello.c b/tests/spread/extensions/snaps/env-injector-hello/hello.c index 4d442b0068..3aee1818f7 100644 --- a/tests/spread/extensions/snaps/env-injector-hello/hello.c +++ b/tests/spread/extensions/snaps/env-injector-hello/hello.c @@ -4,34 +4,66 @@ int main(int argc, char const *argv[]) { const char *envs[] = { - "HELLO", // This is set globally - "WORLD", // This is set for the app - "HELLO_WORLD", // This is set from env file + "GLOBAL", // This is set globally + "HELLO", // This is set for the app + "HELLO_WORLD", // This is set from global env file }; const char *expected[] = { - "Hello", "World", + "Hello", "Hello World", }; int num_envs = sizeof(envs) / sizeof(envs[0]); + // Check Global, App specific and global env file for (int i = 0; i < num_envs; ++i) { const char *env = getenv(envs[i]); if (env == NULL) { fprintf(stderr, "\n[ERROR] Env. variable %s is not set.\n", envs[i]); - return 1; + exit(1); } if (strcmp(env, expected[i]) != 0) { fprintf(stderr, "\n[ERROR] Env. variable %s isn't set to the expected value.\n", envs[i]); fprintf(stderr, "Expected: %s\n", expected[i]); fprintf(stderr, "Got: %s\n", env); - return 1; + exit(1); } } + // Check that it's not possible to access other app ENVs + const char *env_specific = getenv("SPECIFIC"); + if (env_specific != NULL) { + fprintf(stderr, "\n[ERROR] Env. variable SPECIFIC is accessible from the app.\n"); + fprintf(stderr, "Expected: NULL\n"); + fprintf(stderr, "Got %s\n", env_specific); + exit(1); + } + + // Check if key with dot was rejected + const char *env_dot = getenv("DOT"); + if (env_dot != NULL) { + fprintf(stderr, "\n[ERROR] Received Env. variable DOT with wrong key subset.\n"); + fprintf(stderr, "Expected: NULL\n"); + fprintf(stderr, "Got %s\n", env_dot); + exit(1); + } + + // Precedence check: Testing if the order of the envs is correct + const char *env_order = getenv("ORDER"); + + if (env_order == NULL){ + fprintf(stderr, "\n[ERROR] Env. variable ORDER is not set.\n"); + exit(1); + } + + if (strcmp(env_order, "from app-specific") != 0) { + fprintf(stderr, "\n[ERROR] Precedence error: app-specific envs should override global envs.\n"); + exit(1); + } + return 0; } diff --git a/tests/spread/extensions/snaps/env-injector-hello/hello2.c b/tests/spread/extensions/snaps/env-injector-hello/hello2.c new file mode 100644 index 0000000000..516445d298 --- /dev/null +++ b/tests/spread/extensions/snaps/env-injector-hello/hello2.c @@ -0,0 +1,46 @@ +#include +#include +#include + +int main(int argc, char const *argv[]) { + const char *envs[] = { + "GLOBAL", // This is set globally + "SPECIFIC", // This is set for the app only + "HELLO_WORLD", // This is set from global env file + }; + + const char *expected[] = { + "World", + "City", + "Hello World", + }; + + int num_envs = sizeof(envs) / sizeof(envs[0]); + + for (int i = 0; i < num_envs; ++i) { + const char *env = getenv(envs[i]); + + if (env == NULL) { + fprintf(stderr, "\n[ERROR] Env. variable %s is not set.\n", envs[i]); + exit(1); + } + + if (strcmp(env, expected[i]) != 0) { + fprintf(stderr, "\n[ERROR] Env. variable %s isn't set to the expected value.\n", envs[i]); + fprintf(stderr, "Expected: %s\n", expected[i]); + fprintf(stderr, "Got: %s\n", env); + exit(1); + } + } + + // Check that it's not possible to access other app ENVs + const char *env_hello = getenv("HELLO"); + if (env_hello != NULL) { + fprintf(stderr, "\n[ERROR] Env. variable SPECIFIC is accessible from the app.\n"); + fprintf(stderr, "Expected: NULL\n"); + fprintf(stderr, "Got %s\n", env_hello); + exit(1); + } + + return 0; +} diff --git a/tests/spread/extensions/snaps/env-injector-hello/snap/snapcraft.yaml b/tests/spread/extensions/snaps/env-injector-hello/snap/snapcraft.yaml index 1fd271d2d9..e9c3073083 100644 --- a/tests/spread/extensions/snaps/env-injector-hello/snap/snapcraft.yaml +++ b/tests/spread/extensions/snaps/env-injector-hello/snap/snapcraft.yaml @@ -8,8 +8,18 @@ base: core24 confinement: strict apps: - env-hello: + hello1: command: usr/local/bin/hello + extensions: [ env-injector ] + + hello2: + command: usr/local/bin/hello2 + extensions: [ env-injector ] + + hello-demo: + command: usr/local/bin/hello2 + environment: + app_alias: hello-demo extensions: [ env-injector ] parts: From 0a280d908aa92695a0a891a55c620388858c3693 Mon Sep 17 00:00:00 2001 From: Lincoln Wallace Date: Fri, 2 Aug 2024 17:34:53 -0300 Subject: [PATCH 06/41] many: Address reviews - doc: Add line break on Pydoc - rust exporter: Fix error messages - spread: Add case to test app-specific envfile - spread: Fix test for verify alias overrite behavior Signed-off-by: Lincoln Wallace --- extensions/env-injector/src/main.rs | 27 ++++--------------- snapcraft/extensions/env_injector.py | 1 + .../spread/extensions/env-injector/task.yaml | 10 +++++-- .../snaps/env-injector-hello/hello.c | 13 ++++++++- .../snaps/env-injector-hello/hello2.c | 8 ++++++ .../env-injector-hello/snap/snapcraft.yaml | 3 ++- 6 files changed, 36 insertions(+), 26 deletions(-) diff --git a/extensions/env-injector/src/main.rs b/extensions/env-injector/src/main.rs index 7c3a35bcd6..a9f73c0120 100644 --- a/extensions/env-injector/src/main.rs +++ b/extensions/env-injector/src/main.rs @@ -51,31 +51,14 @@ fn process_env(env: &serde_json::Value) -> HashMap { for (k, v) in obj { if v.is_object() || v.is_array() { eprintln!( - "ERROR: Invalid value detected.\n\ - Key: {}\n\ - Reason: Environment variable names must not contain dots.\n\ - Action: Skipped\n", + "Skipped invalid key containing dots: {}", v ); continue; } let key = k.to_uppercase().replace("-", "_"); - let value = match v { - serde_json::Value::String(s) => s.clone(), - serde_json::Value::Number(n) => n.to_string(), - serde_json::Value::Bool(b) => b.to_string(), - _ => { - eprintln!( - "ERROR: Invalid type for environment variable value detected.\n\ - val: {}\n\ - Reason: It must be a string, number or boolean\n\ - Action: Skipped\n", - k - ); - continue; - }, - }; + let value = v.to_string(); map.insert(key, value); } map @@ -172,7 +155,7 @@ async fn run() -> Result<(), Box> { Ok(()) } -fn main()-> Result<(), Box> { +fn main() -> Result<(), Box> { let args: Vec = std::env::args().collect(); @@ -186,7 +169,7 @@ fn main()-> Result<(), Box> { run()?; - Command::new(command).args(args).status()?; + let status = Command::new(command).args(args).status()?; - Ok(()) + std::process::exit(status.code().unwrap_or(1)); } diff --git a/snapcraft/extensions/env_injector.py b/snapcraft/extensions/env_injector.py index 252ad6d13b..6001ad932e 100644 --- a/snapcraft/extensions/env_injector.py +++ b/snapcraft/extensions/env_injector.py @@ -47,6 +47,7 @@ class EnvInjectorExtension(Extension): .. code-block:: shell sudo snap set env-file= - To set environment file for a specific app: + .. code-block:: shell sudo snap set apps..envfile= diff --git a/tests/spread/extensions/env-injector/task.yaml b/tests/spread/extensions/env-injector/task.yaml index c2ee87d0de..a95cb6c1d3 100644 --- a/tests/spread/extensions/env-injector/task.yaml +++ b/tests/spread/extensions/env-injector/task.yaml @@ -40,9 +40,12 @@ execute: | snap install "${SNAP}"_1.0_*.snap --dangerous - # Create envfile + # Create global envfile echo 'HELLO_WORLD=Hello World' >> envfile.env + # Create app-specific envfile + echo 'SCOPED=Scoped' >> appenv.env + # Set env vars: Global snap set env-injector-hello env.global="World" @@ -56,7 +59,10 @@ execute: | # To test order of env vars echo 'ORDER="From envfile"' >> envfile.env snap set env-injector-hello apps.hello1.env.order="from app-specific" + + # Load global and app-specific envfiles snap set env-injector-hello envfile="${SNAP_DIR}"/envfile.env + snap set env-injector-hello apps.hello1.envfile="${SNAP_DIR}"/appenv.env # Run the hello command # Those will fail if the env vars are not set as expected @@ -64,5 +70,5 @@ execute: | env-injector-hello.hello2 # Test if the alias it's rewrite - snap set env-injector-hello apps.hello-demo.env.specific="City" + snap set env-injector-hello apps.myapp.env.specific="City" env-injector-hello.hello-demo diff --git a/tests/spread/extensions/snaps/env-injector-hello/hello.c b/tests/spread/extensions/snaps/env-injector-hello/hello.c index 3aee1818f7..8cbff82973 100644 --- a/tests/spread/extensions/snaps/env-injector-hello/hello.c +++ b/tests/spread/extensions/snaps/env-injector-hello/hello.c @@ -59,11 +59,22 @@ int main(int argc, char const *argv[]) { fprintf(stderr, "\n[ERROR] Env. variable ORDER is not set.\n"); exit(1); } - if (strcmp(env_order, "from app-specific") != 0) { fprintf(stderr, "\n[ERROR] Precedence error: app-specific envs should override global envs.\n"); exit(1); } + // Scope specific envfile + const char *env_scope = getenv("SCOPED"); + + if (env_scope == NULL){ + fprintf(stderr, "\n[ERROR] Env. variable SCOPED is not set.\n"); + exit(-1); + } + if (strcmp(env_scope, "Scoped") != 0) { + fprintf(stderr, "\n[ERROR] app envfile error: unexpected value.\n"); + exit(-1); + } + return 0; } diff --git a/tests/spread/extensions/snaps/env-injector-hello/hello2.c b/tests/spread/extensions/snaps/env-injector-hello/hello2.c index 516445d298..2c2f2429b2 100644 --- a/tests/spread/extensions/snaps/env-injector-hello/hello2.c +++ b/tests/spread/extensions/snaps/env-injector-hello/hello2.c @@ -42,5 +42,13 @@ int main(int argc, char const *argv[]) { exit(1); } + // Testing Scope specific envfile + const char *env_scope = getenv("SCOPE"); + + if (env_scope != NULL){ + fprintf(stderr, "\n[ERROR] Env. variable SCOPE should not be captured by this app.\n"); + exit(-1); + } + return 0; } diff --git a/tests/spread/extensions/snaps/env-injector-hello/snap/snapcraft.yaml b/tests/spread/extensions/snaps/env-injector-hello/snap/snapcraft.yaml index e9c3073083..cdc973676f 100644 --- a/tests/spread/extensions/snaps/env-injector-hello/snap/snapcraft.yaml +++ b/tests/spread/extensions/snaps/env-injector-hello/snap/snapcraft.yaml @@ -19,7 +19,8 @@ apps: hello-demo: command: usr/local/bin/hello2 environment: - app_alias: hello-demo + # user-defined alias + env_alias: myapp extensions: [ env-injector ] parts: From 2d276b8f79c5ec3a1d182de0f6f102bfe86fb0a0 Mon Sep 17 00:00:00 2001 From: Lincoln Wallace Date: Fri, 2 Aug 2024 17:42:06 -0300 Subject: [PATCH 07/41] feat: improve sentence. Co-authored-by: Farshid Tavakolizadeh --- tests/spread/extensions/env-injector/task.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/spread/extensions/env-injector/task.yaml b/tests/spread/extensions/env-injector/task.yaml index a95cb6c1d3..8e4a44ccd8 100644 --- a/tests/spread/extensions/env-injector/task.yaml +++ b/tests/spread/extensions/env-injector/task.yaml @@ -69,6 +69,6 @@ execute: | env-injector-hello.hello1 env-injector-hello.hello2 - # Test if the alias it's rewrite + # Make sure that alias is NOT rewritten snap set env-injector-hello apps.myapp.env.specific="City" env-injector-hello.hello-demo From d0629a74e35b8ae053ab02e3a53e807258e6e891 Mon Sep 17 00:00:00 2001 From: Lincoln Wallace Date: Fri, 2 Aug 2024 17:42:45 -0300 Subject: [PATCH 08/41] fix: typo error Co-authored-by: Farshid Tavakolizadeh --- tests/spread/extensions/env-injector/task.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/spread/extensions/env-injector/task.yaml b/tests/spread/extensions/env-injector/task.yaml index 8e4a44ccd8..74319eb87d 100644 --- a/tests/spread/extensions/env-injector/task.yaml +++ b/tests/spread/extensions/env-injector/task.yaml @@ -65,7 +65,7 @@ execute: | snap set env-injector-hello apps.hello1.envfile="${SNAP_DIR}"/appenv.env # Run the hello command - # Those will fail if the env vars are not set as expected + # These will fail if the env vars are not set as expected env-injector-hello.hello1 env-injector-hello.hello2 From 8bd2300f9362e207e740820c519df39e5e5e3539 Mon Sep 17 00:00:00 2001 From: Lincoln Wallace Date: Fri, 2 Aug 2024 17:43:08 -0300 Subject: [PATCH 09/41] fix: improve sentence Co-authored-by: Farshid Tavakolizadeh --- tests/spread/extensions/env-injector/task.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/spread/extensions/env-injector/task.yaml b/tests/spread/extensions/env-injector/task.yaml index 74319eb87d..ba30398ede 100644 --- a/tests/spread/extensions/env-injector/task.yaml +++ b/tests/spread/extensions/env-injector/task.yaml @@ -53,7 +53,7 @@ execute: | snap set env-injector-hello apps.hello1.env.hello="Hello" snap set env-injector-hello apps.hello2.env.specific="City" - # To Check if key with dot will be rejected + # To check if key with dot will be rejected snap set env-injector-hello env.word.dot="wrong" # To test order of env vars From d8f9e6864f3b66862e1d237408df2b8e8f1aaa30 Mon Sep 17 00:00:00 2001 From: Lincoln Wallace Date: Sun, 4 Aug 2024 20:04:39 -0300 Subject: [PATCH 10/41] fix: commentaries not being discarted in envfiles Signed-off-by: Lincoln Wallace --- extensions/env-injector/Cargo.toml | 1 + extensions/env-injector/src/main.rs | 37 +++++++---------------------- 2 files changed, 10 insertions(+), 28 deletions(-) diff --git a/extensions/env-injector/Cargo.toml b/extensions/env-injector/Cargo.toml index fe2b664a4c..f9c06ace3f 100644 --- a/extensions/env-injector/Cargo.toml +++ b/extensions/env-injector/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] +dotenv = "0.15.0" http-body-util = "0.1.2" hyper = {git = "https://github.com/hyperium/hyper", tag = "v1.4.1"} hyper-util = "0.1.6" diff --git a/extensions/env-injector/src/main.rs b/extensions/env-injector/src/main.rs index a9f73c0120..f027a26343 100644 --- a/extensions/env-injector/src/main.rs +++ b/extensions/env-injector/src/main.rs @@ -7,7 +7,7 @@ use std::error::Error; use std::process::Command; use std::collections::HashMap; use std::path::Path; -use std::io::BufRead; +use dotenv; const SNAPD_SOCKET: &str = "/run/snapd-snap.socket"; @@ -64,9 +64,6 @@ fn process_env(env: &serde_json::Value) -> HashMap { map } - - - fn set_env_vars(app: &str, json: &serde_json::Value) -> Result<(), Box> { let stdout_str = json["result"]["stdout"].as_str().ok_or("Invalid stdout")?; let stdout_json: serde_json::Value = serde_json::from_str(stdout_str)?; @@ -87,7 +84,7 @@ fn set_env_vars(app: &str, json: &serde_json::Value) -> Result<(), Box std::io::Result> { +fn source_env_file(file_path: &str) -> std::io::Result<()> { let path = Path::new(file_path); if !path.exists() { @@ -100,20 +97,12 @@ fn source_env_file(file_path: &str) -> std::io::Result> return Err(std::io::Error::new(std::io::ErrorKind::PermissionDenied, "File is not readable")); } - let file = std::fs::File::open(file_path)?; - let reader = std::io::BufReader::new(file); + dotenv::from_path(path).map_err(|e| { + eprintln!("Failed to load environment file: {}", e); + std::io::Error::new(std::io::ErrorKind::InvalidData, "Failed to load environment file") + })?; - let mut env_vars = HashMap::new(); - for line in reader.lines() { - let line = line?; - if !line.trim().is_empty() && !line.starts_with('#') { - if let Some((key, value)) = line.split_once('=') { - env_vars.insert(key.to_string(), value.to_string()); - } - } - } - - Ok(env_vars) + Ok(()) } fn set_env_vars_from_file(app: &str, json: &serde_json::Value) -> Result<(), Box> { @@ -123,20 +112,12 @@ fn set_env_vars_from_file(app: &str, json: &serde_json::Value) -> Result<(), Bo // Source the global envfile first if let Some(global_envfile) = stdout_json["envfile"].as_str() { - if let Ok(env_vars) = source_env_file(global_envfile) { - for (key, value) in env_vars { - std::env::set_var(key, value); - } - } + source_env_file(global_envfile)?; } // Source the app-specific envfile if let Some(app_envfile) = stdout_json["apps"][app]["envfile"].as_str() { - if let Ok(env_vars) = source_env_file(app_envfile) { - for (key, value) in env_vars { - std::env::set_var(key, value); - } - } + source_env_file(app_envfile)?; } Ok(()) From 3e17cecf2230ad5da31869938b29d04e63e1a506 Mon Sep 17 00:00:00 2001 From: Lincoln Wallace Date: Mon, 5 Aug 2024 18:54:08 -0300 Subject: [PATCH 11/41] feat: add logs to check test execution Signed-off-by: Lincoln Wallace --- tests/spread/extensions/env-injector/task.yaml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/spread/extensions/env-injector/task.yaml b/tests/spread/extensions/env-injector/task.yaml index ba30398ede..7e02a4903d 100644 --- a/tests/spread/extensions/env-injector/task.yaml +++ b/tests/spread/extensions/env-injector/task.yaml @@ -40,35 +40,45 @@ execute: | snap install "${SNAP}"_1.0_*.snap --dangerous + echo "[env-injector] Creating global envfile" # Create global envfile echo 'HELLO_WORLD=Hello World' >> envfile.env + echo "[env-injector] Creating app-specific envfile" # Create app-specific envfile echo 'SCOPED=Scoped' >> appenv.env + echo "[env-injector] Setting global env variable" # Set env vars: Global snap set env-injector-hello env.global="World" + echo "[env-injector] Setting app-specific env variable" # Set env vars: specific to each app snap set env-injector-hello apps.hello1.env.hello="Hello" snap set env-injector-hello apps.hello2.env.specific="City" + echo "[env-injector] Rejecting invalid keys" + # Set env vars: specific to each app # To check if key with dot will be rejected snap set env-injector-hello env.word.dot="wrong" + echo "[env-injector] Testing order of env vars" # To test order of env vars echo 'ORDER="From envfile"' >> envfile.env snap set env-injector-hello apps.hello1.env.order="from app-specific" + echo "[env-injector] Load envfiles" # Load global and app-specific envfiles snap set env-injector-hello envfile="${SNAP_DIR}"/envfile.env snap set env-injector-hello apps.hello1.envfile="${SNAP_DIR}"/appenv.env + echo "[env-injector] Run hello1 and hello2 apps" # Run the hello command # These will fail if the env vars are not set as expected env-injector-hello.hello1 env-injector-hello.hello2 + echo "[env-injector] Run hello-demo app" # Make sure that alias is NOT rewritten snap set env-injector-hello apps.myapp.env.specific="City" env-injector-hello.hello-demo From b65a0c6a908e539615e45df7079d0481fb97dd35 Mon Sep 17 00:00:00 2001 From: Lincoln Wallace Date: Thu, 8 Aug 2024 14:29:15 -0300 Subject: [PATCH 12/41] fix: add missing configure hook Signed-off-by: Lincoln Wallace --- .../extensions/snaps/env-injector-hello/snap/hooks/configure | 1 + 1 file changed, 1 insertion(+) create mode 100755 tests/spread/extensions/snaps/env-injector-hello/snap/hooks/configure diff --git a/tests/spread/extensions/snaps/env-injector-hello/snap/hooks/configure b/tests/spread/extensions/snaps/env-injector-hello/snap/hooks/configure new file mode 100755 index 0000000000..a9bf588e2f --- /dev/null +++ b/tests/spread/extensions/snaps/env-injector-hello/snap/hooks/configure @@ -0,0 +1 @@ +#!/bin/bash From 6ab0b23bd61a5c3072057613db239b2497e738a1 Mon Sep 17 00:00:00 2001 From: Lincoln Wallace Date: Thu, 8 Aug 2024 14:42:08 -0300 Subject: [PATCH 13/41] Update tests/spread/extensions/env-injector/task.yaml Co-authored-by: Callahan --- tests/spread/extensions/env-injector/task.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/spread/extensions/env-injector/task.yaml b/tests/spread/extensions/env-injector/task.yaml index 7e02a4903d..34f7e955ce 100644 --- a/tests/spread/extensions/env-injector/task.yaml +++ b/tests/spread/extensions/env-injector/task.yaml @@ -1,7 +1,7 @@ summary: Build and run a basic hello-world snap using extensions systems: - - ubuntu-24.04 + - ubuntu-24.04* environment: From 09a883ad3910b9453603aa556d4084c620d0a39c Mon Sep 17 00:00:00 2001 From: Lincoln Wallace Date: Mon, 12 Aug 2024 10:33:21 -0300 Subject: [PATCH 14/41] feat: improve tests, drop C code Signed-off-by: Lincoln Wallace --- .../spread/extensions/env-injector/task.yaml | 78 +++++++++++++----- .../snaps/env-injector-hello/CMakeLists.txt | 10 --- .../snaps/env-injector-hello/hello.c | 80 ------------------- .../snaps/env-injector-hello/hello2.c | 54 ------------- .../env-injector-hello/snap/snapcraft.yaml | 13 +-- .../snaps/env-injector-hello/usr/bin/exec-env | 3 + 6 files changed, 69 insertions(+), 169 deletions(-) delete mode 100644 tests/spread/extensions/snaps/env-injector-hello/CMakeLists.txt delete mode 100644 tests/spread/extensions/snaps/env-injector-hello/hello.c delete mode 100644 tests/spread/extensions/snaps/env-injector-hello/hello2.c create mode 100755 tests/spread/extensions/snaps/env-injector-hello/usr/bin/exec-env diff --git a/tests/spread/extensions/env-injector/task.yaml b/tests/spread/extensions/env-injector/task.yaml index 34f7e955ce..abefebe75f 100644 --- a/tests/spread/extensions/env-injector/task.yaml +++ b/tests/spread/extensions/env-injector/task.yaml @@ -18,6 +18,7 @@ prepare: | restore: | cd "$SNAP_DIR" + rm *.env snapcraft clean rm -f ./*.snap rm -rf ./squashfs-root @@ -27,11 +28,35 @@ restore: | execute: | + check_env_var() { + local snap_app="$1" + local env_name="$2" + local exp_value="$3" + + local actual_value + + if ! eval "$snap_app" | grep -q "^${env_name}="; then + echo "Environment variable '$env_name' is not set." + return 1 + fi + + actual_value=$( "$snap_app" | grep "^${env_name}=" | cut -d'=' -f2-) + + if [ "$actual_value" != "$exp_value" ]; then + echo "Environment variable '$env_name' does not match the expected value." + echo "Expected: '$env_name=$exp_value', but got: '$env_name=$actual_value'" + return 1 + fi + + return 0 + } + cd "$SNAP_DIR" - snapcraft + SNAPCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS=1 snapcraft unsquashfs "${SNAP}"_1.0_*.snap + # Check that the env-exporter program is present [ -f squashfs-root/bin/command-chain/env-exporter ] @@ -41,44 +66,59 @@ execute: | snap install "${SNAP}"_1.0_*.snap --dangerous echo "[env-injector] Creating global envfile" - # Create global envfile - echo 'HELLO_WORLD=Hello World' >> envfile.env + echo 'HELLO_WORLD="Hello World"' >> global.env + # Load global envfile + snap set env-injector-hello envfile=global.env + # [TEST] - Check if the global envfile is loaded for all apps + check_env_var "env-injector-hello.hello1" "HELLO_WORLD" "Hello World" || exit 1 + check_env_var "env-injector-hello.hello2" "HELLO_WORLD" "Hello World" || exit 1 + check_env_var "env-injector-hello.hello-demo" "HELLO_WORLD" "Hello World" || exit 1 echo "[env-injector] Creating app-specific envfile" - # Create app-specific envfile echo 'SCOPED=Scoped' >> appenv.env + # Load app-specific envfile + snap set env-injector-hello apps.hello1.envfile=appenv.env + # [TEST] - Check if the app-specific envfile is loaded for the app + check_env_var "env-injector-hello.hello1" "SCOPED" "Scoped" || exit 1 echo "[env-injector] Setting global env variable" # Set env vars: Global snap set env-injector-hello env.global="World" + # [TEST] - Check if the global env var is set for all apps + check_env_var "env-injector-hello.hello1" "GLOBAL" "World" || exit 1 + check_env_var "env-injector-hello.hello2" "GLOBAL" "World" || exit 1 + check_env_var "env-injector-hello.hello-demo" "GLOBAL" "World" || exit 1 echo "[env-injector] Setting app-specific env variable" # Set env vars: specific to each app snap set env-injector-hello apps.hello1.env.hello="Hello" snap set env-injector-hello apps.hello2.env.specific="City" + # [TEST] - Check if the app-specific env var IS SET for the app hello1 + check_env_var "env-injector-hello.hello1" "HELLO" "Hello" || exit 1 + # [TEST] - Check if the app-specific env var IS NOT SET for the app hello2 + ! check_env_var "env-injector-hello.hello2" "HELLO" "Hello" || exit 1 + + # [TEST] - Check if the app-specific env var IS SET for the app hello2 + check_env_var "env-injector-hello.hello2" "SPECIFIC" "City" || exit 1 + # [TEST] - Check if the app-specific env var IS NOT SET for the app hello1 + ! check_env_var "env-injector-hello.hello1" "SPECIFIC" "City" || exit 1 + echo "[env-injector] Rejecting invalid keys" - # Set env vars: specific to each app # To check if key with dot will be rejected snap set env-injector-hello env.word.dot="wrong" + # [TEST] - Check if the key with dot was rejected + ! check_env_var "env-injector-hello.hello1" "DOT" "wrong" || exit 1 echo "[env-injector] Testing order of env vars" # To test order of env vars - echo 'ORDER="From envfile"' >> envfile.env + echo 'ORDER="From envfile"' >> local.env snap set env-injector-hello apps.hello1.env.order="from app-specific" - - echo "[env-injector] Load envfiles" - # Load global and app-specific envfiles - snap set env-injector-hello envfile="${SNAP_DIR}"/envfile.env - snap set env-injector-hello apps.hello1.envfile="${SNAP_DIR}"/appenv.env - - echo "[env-injector] Run hello1 and hello2 apps" - # Run the hello command - # These will fail if the env vars are not set as expected - env-injector-hello.hello1 - env-injector-hello.hello2 + snap set env-injector-hello apps.hello1.envfile=envfile.env + # [TEST] - Check if local overrites global + check_env_var "env-injector-hello.hello1" "ORDER" "from app-specific" || exit 1 echo "[env-injector] Run hello-demo app" - # Make sure that alias is NOT rewritten snap set env-injector-hello apps.myapp.env.specific="City" - env-injector-hello.hello-demo + # [TEST] Make sure that alias is NOT rewritten + check_env_var "env-injector-hello.hello-demo" "SPECIFIC" "City" || exit 1 diff --git a/tests/spread/extensions/snaps/env-injector-hello/CMakeLists.txt b/tests/spread/extensions/snaps/env-injector-hello/CMakeLists.txt deleted file mode 100644 index b4d8ba4757..0000000000 --- a/tests/spread/extensions/snaps/env-injector-hello/CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ -cmake_minimum_required(VERSION 3.5) -project(hello C) - -# Add executable for hello -add_executable(hello hello.c) -install(TARGETS hello RUNTIME DESTINATION bin) - -# Add executable for hello2 -add_executable(hello2 hello2.c) -install(TARGETS hello2 RUNTIME DESTINATION bin) \ No newline at end of file diff --git a/tests/spread/extensions/snaps/env-injector-hello/hello.c b/tests/spread/extensions/snaps/env-injector-hello/hello.c deleted file mode 100644 index 8cbff82973..0000000000 --- a/tests/spread/extensions/snaps/env-injector-hello/hello.c +++ /dev/null @@ -1,80 +0,0 @@ -#include -#include -#include - -int main(int argc, char const *argv[]) { - const char *envs[] = { - "GLOBAL", // This is set globally - "HELLO", // This is set for the app - "HELLO_WORLD", // This is set from global env file - }; - - const char *expected[] = { - "World", - "Hello", - "Hello World", - }; - - int num_envs = sizeof(envs) / sizeof(envs[0]); - - // Check Global, App specific and global env file - for (int i = 0; i < num_envs; ++i) { - const char *env = getenv(envs[i]); - - if (env == NULL) { - fprintf(stderr, "\n[ERROR] Env. variable %s is not set.\n", envs[i]); - exit(1); - } - - if (strcmp(env, expected[i]) != 0) { - fprintf(stderr, "\n[ERROR] Env. variable %s isn't set to the expected value.\n", envs[i]); - fprintf(stderr, "Expected: %s\n", expected[i]); - fprintf(stderr, "Got: %s\n", env); - exit(1); - } - } - - // Check that it's not possible to access other app ENVs - const char *env_specific = getenv("SPECIFIC"); - if (env_specific != NULL) { - fprintf(stderr, "\n[ERROR] Env. variable SPECIFIC is accessible from the app.\n"); - fprintf(stderr, "Expected: NULL\n"); - fprintf(stderr, "Got %s\n", env_specific); - exit(1); - } - - // Check if key with dot was rejected - const char *env_dot = getenv("DOT"); - if (env_dot != NULL) { - fprintf(stderr, "\n[ERROR] Received Env. variable DOT with wrong key subset.\n"); - fprintf(stderr, "Expected: NULL\n"); - fprintf(stderr, "Got %s\n", env_dot); - exit(1); - } - - // Precedence check: Testing if the order of the envs is correct - const char *env_order = getenv("ORDER"); - - if (env_order == NULL){ - fprintf(stderr, "\n[ERROR] Env. variable ORDER is not set.\n"); - exit(1); - } - if (strcmp(env_order, "from app-specific") != 0) { - fprintf(stderr, "\n[ERROR] Precedence error: app-specific envs should override global envs.\n"); - exit(1); - } - - // Scope specific envfile - const char *env_scope = getenv("SCOPED"); - - if (env_scope == NULL){ - fprintf(stderr, "\n[ERROR] Env. variable SCOPED is not set.\n"); - exit(-1); - } - if (strcmp(env_scope, "Scoped") != 0) { - fprintf(stderr, "\n[ERROR] app envfile error: unexpected value.\n"); - exit(-1); - } - - return 0; -} diff --git a/tests/spread/extensions/snaps/env-injector-hello/hello2.c b/tests/spread/extensions/snaps/env-injector-hello/hello2.c deleted file mode 100644 index 2c2f2429b2..0000000000 --- a/tests/spread/extensions/snaps/env-injector-hello/hello2.c +++ /dev/null @@ -1,54 +0,0 @@ -#include -#include -#include - -int main(int argc, char const *argv[]) { - const char *envs[] = { - "GLOBAL", // This is set globally - "SPECIFIC", // This is set for the app only - "HELLO_WORLD", // This is set from global env file - }; - - const char *expected[] = { - "World", - "City", - "Hello World", - }; - - int num_envs = sizeof(envs) / sizeof(envs[0]); - - for (int i = 0; i < num_envs; ++i) { - const char *env = getenv(envs[i]); - - if (env == NULL) { - fprintf(stderr, "\n[ERROR] Env. variable %s is not set.\n", envs[i]); - exit(1); - } - - if (strcmp(env, expected[i]) != 0) { - fprintf(stderr, "\n[ERROR] Env. variable %s isn't set to the expected value.\n", envs[i]); - fprintf(stderr, "Expected: %s\n", expected[i]); - fprintf(stderr, "Got: %s\n", env); - exit(1); - } - } - - // Check that it's not possible to access other app ENVs - const char *env_hello = getenv("HELLO"); - if (env_hello != NULL) { - fprintf(stderr, "\n[ERROR] Env. variable SPECIFIC is accessible from the app.\n"); - fprintf(stderr, "Expected: NULL\n"); - fprintf(stderr, "Got %s\n", env_hello); - exit(1); - } - - // Testing Scope specific envfile - const char *env_scope = getenv("SCOPE"); - - if (env_scope != NULL){ - fprintf(stderr, "\n[ERROR] Env. variable SCOPE should not be captured by this app.\n"); - exit(-1); - } - - return 0; -} diff --git a/tests/spread/extensions/snaps/env-injector-hello/snap/snapcraft.yaml b/tests/spread/extensions/snaps/env-injector-hello/snap/snapcraft.yaml index cdc973676f..fc925f609f 100644 --- a/tests/spread/extensions/snaps/env-injector-hello/snap/snapcraft.yaml +++ b/tests/spread/extensions/snaps/env-injector-hello/snap/snapcraft.yaml @@ -9,21 +9,22 @@ confinement: strict apps: hello1: - command: usr/local/bin/hello - extensions: [ env-injector ] + command: usr/bin/exec-env + extensions: [ env-injector ] hello2: - command: usr/local/bin/hello2 + command: usr/bin/exec-env extensions: [ env-injector ] hello-demo: - command: usr/local/bin/hello2 + command: usr/bin/exec-env environment: # user-defined alias env_alias: myapp extensions: [ env-injector ] parts: - hello: - plugin: cmake + env: + plugin: dump source: . + diff --git a/tests/spread/extensions/snaps/env-injector-hello/usr/bin/exec-env b/tests/spread/extensions/snaps/env-injector-hello/usr/bin/exec-env new file mode 100755 index 0000000000..b802721fcb --- /dev/null +++ b/tests/spread/extensions/snaps/env-injector-hello/usr/bin/exec-env @@ -0,0 +1,3 @@ +#!/usr/bin/bash + +env From 84ce9102c7758e9586619f44908c65befe151d6e Mon Sep 17 00:00:00 2001 From: Lincoln Wallace Date: Mon, 12 Aug 2024 10:39:26 -0300 Subject: [PATCH 15/41] fix: shellchek error Signed-off-by: Lincoln Wallace --- tests/spread/extensions/env-injector/task.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/spread/extensions/env-injector/task.yaml b/tests/spread/extensions/env-injector/task.yaml index abefebe75f..c538355f93 100644 --- a/tests/spread/extensions/env-injector/task.yaml +++ b/tests/spread/extensions/env-injector/task.yaml @@ -18,7 +18,7 @@ prepare: | restore: | cd "$SNAP_DIR" - rm *.env + rm ./*.env snapcraft clean rm -f ./*.snap rm -rf ./squashfs-root From 984dd5b8dfc9a640e199a8ecdde564c859b8d07b Mon Sep 17 00:00:00 2001 From: Lincoln Wallace Date: Mon, 12 Aug 2024 20:33:00 -0300 Subject: [PATCH 16/41] fix(ci): fix spread test Signed-off-by: Lincoln Wallace --- tests/spread/extensions/env-injector/task.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/spread/extensions/env-injector/task.yaml b/tests/spread/extensions/env-injector/task.yaml index c538355f93..65a8165d7b 100644 --- a/tests/spread/extensions/env-injector/task.yaml +++ b/tests/spread/extensions/env-injector/task.yaml @@ -18,8 +18,8 @@ prepare: | restore: | cd "$SNAP_DIR" - rm ./*.env snapcraft clean + rm -f ./*.env rm -f ./*.snap rm -rf ./squashfs-root #shellcheck source=tests/spread/tools/snapcraft-yaml.sh From 5406f551452f6fdfb1088eeab2dd2bf135bc5b36 Mon Sep 17 00:00:00 2001 From: Lincoln Wallace Date: Tue, 13 Aug 2024 15:43:28 -0300 Subject: [PATCH 17/41] fix: outdated test Signed-off-by: Lincoln Wallace --- tests/spread/extensions/env-injector/task.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/spread/extensions/env-injector/task.yaml b/tests/spread/extensions/env-injector/task.yaml index 65a8165d7b..37d7cf0873 100644 --- a/tests/spread/extensions/env-injector/task.yaml +++ b/tests/spread/extensions/env-injector/task.yaml @@ -60,8 +60,8 @@ execute: | # Check that the env-exporter program is present [ -f squashfs-root/bin/command-chain/env-exporter ] - # Check that the hello command is present - [ -f squashfs-root/usr/local/bin/hello ] + # Check that the exec-env script is present + [ -f squashfs-root/usr/bin/exec-env ] snap install "${SNAP}"_1.0_*.snap --dangerous From 946637765815342ff7e9b2583764199bcd6c652b Mon Sep 17 00:00:00 2001 From: Lincoln Wallace Date: Tue, 13 Aug 2024 18:15:19 -0300 Subject: [PATCH 18/41] fix: use absolute path for envfiles Signed-off-by: Lincoln Wallace --- tests/spread/extensions/env-injector/task.yaml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/spread/extensions/env-injector/task.yaml b/tests/spread/extensions/env-injector/task.yaml index 37d7cf0873..623e94e3c9 100644 --- a/tests/spread/extensions/env-injector/task.yaml +++ b/tests/spread/extensions/env-injector/task.yaml @@ -66,18 +66,18 @@ execute: | snap install "${SNAP}"_1.0_*.snap --dangerous echo "[env-injector] Creating global envfile" - echo 'HELLO_WORLD="Hello World"' >> global.env + echo 'HELLO_WORLD="Hello World"' >> "$SNAP_DIR"/global.env # Load global envfile - snap set env-injector-hello envfile=global.env + snap set env-injector-hello envfile="$SNAP_DIR"/global.env # [TEST] - Check if the global envfile is loaded for all apps check_env_var "env-injector-hello.hello1" "HELLO_WORLD" "Hello World" || exit 1 check_env_var "env-injector-hello.hello2" "HELLO_WORLD" "Hello World" || exit 1 check_env_var "env-injector-hello.hello-demo" "HELLO_WORLD" "Hello World" || exit 1 echo "[env-injector] Creating app-specific envfile" - echo 'SCOPED=Scoped' >> appenv.env + echo 'SCOPED=Scoped' >> "$SNAP_DIR"/appenv.env # Load app-specific envfile - snap set env-injector-hello apps.hello1.envfile=appenv.env + snap set env-injector-hello apps.hello1.envfile="$SNAP_DIR"/appenv.env # [TEST] - Check if the app-specific envfile is loaded for the app check_env_var "env-injector-hello.hello1" "SCOPED" "Scoped" || exit 1 @@ -105,16 +105,16 @@ execute: | ! check_env_var "env-injector-hello.hello1" "SPECIFIC" "City" || exit 1 echo "[env-injector] Rejecting invalid keys" - # To check if key with dot will be rejected + # To check if key with dot will be rejected snap set env-injector-hello env.word.dot="wrong" # [TEST] - Check if the key with dot was rejected ! check_env_var "env-injector-hello.hello1" "DOT" "wrong" || exit 1 echo "[env-injector] Testing order of env vars" # To test order of env vars - echo 'ORDER="From envfile"' >> local.env + echo 'ORDER="From envfile"' >> "$SNAP_DIR"/local.env snap set env-injector-hello apps.hello1.env.order="from app-specific" - snap set env-injector-hello apps.hello1.envfile=envfile.env + snap set env-injector-hello apps.hello1.envfile="$SNAP_DIR"/envfile.env # [TEST] - Check if local overrites global check_env_var "env-injector-hello.hello1" "ORDER" "from app-specific" || exit 1 From bd60e487867afe5b40c624439fbf4b6e1f8c73fd Mon Sep 17 00:00:00 2001 From: Lincoln Wallace Date: Tue, 13 Aug 2024 19:48:22 -0300 Subject: [PATCH 19/41] feat: change test snap to classic confinement Permission problems are happening because the test snap doesn't have acess to the envfiles. As the confinement isn't something related to test, I'm changing this to classic Signed-off-by: Lincoln Wallace --- tests/spread/extensions/env-injector/task.yaml | 2 +- .../extensions/snaps/env-injector-hello/snap/snapcraft.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/spread/extensions/env-injector/task.yaml b/tests/spread/extensions/env-injector/task.yaml index 623e94e3c9..c87f0ee0ad 100644 --- a/tests/spread/extensions/env-injector/task.yaml +++ b/tests/spread/extensions/env-injector/task.yaml @@ -63,7 +63,7 @@ execute: | # Check that the exec-env script is present [ -f squashfs-root/usr/bin/exec-env ] - snap install "${SNAP}"_1.0_*.snap --dangerous + snap install "${SNAP}"_1.0_*.snap --dangerous --classic echo "[env-injector] Creating global envfile" echo 'HELLO_WORLD="Hello World"' >> "$SNAP_DIR"/global.env diff --git a/tests/spread/extensions/snaps/env-injector-hello/snap/snapcraft.yaml b/tests/spread/extensions/snaps/env-injector-hello/snap/snapcraft.yaml index fc925f609f..cca2341242 100644 --- a/tests/spread/extensions/snaps/env-injector-hello/snap/snapcraft.yaml +++ b/tests/spread/extensions/snaps/env-injector-hello/snap/snapcraft.yaml @@ -5,7 +5,7 @@ description: This is a basic snap for testing env-injector extension grade: devel base: core24 -confinement: strict +confinement: classic apps: hello1: From 03a188da80f6d052ab26352854e5c9be0f1cf6c5 Mon Sep 17 00:00:00 2001 From: Lincoln Wallace Date: Tue, 13 Aug 2024 23:03:19 -0300 Subject: [PATCH 20/41] fix: (try to) fix envfile path location problem Signed-off-by: Lincoln Wallace --- .../spread/extensions/env-injector/task.yaml | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/tests/spread/extensions/env-injector/task.yaml b/tests/spread/extensions/env-injector/task.yaml index c87f0ee0ad..a8989c101e 100644 --- a/tests/spread/extensions/env-injector/task.yaml +++ b/tests/spread/extensions/env-injector/task.yaml @@ -66,18 +66,24 @@ execute: | snap install "${SNAP}"_1.0_*.snap --dangerous --classic echo "[env-injector] Creating global envfile" - echo 'HELLO_WORLD="Hello World"' >> "$SNAP_DIR"/global.env + touch global.env + echo 'HELLO_WORLD="Hello World"' >> global.env + global_envpath=$(realpath global.env) + # Load global envfile - snap set env-injector-hello envfile="$SNAP_DIR"/global.env + snap set env-injector-hello envfile="$global_envpath" # [TEST] - Check if the global envfile is loaded for all apps check_env_var "env-injector-hello.hello1" "HELLO_WORLD" "Hello World" || exit 1 check_env_var "env-injector-hello.hello2" "HELLO_WORLD" "Hello World" || exit 1 check_env_var "env-injector-hello.hello-demo" "HELLO_WORLD" "Hello World" || exit 1 echo "[env-injector] Creating app-specific envfile" - echo 'SCOPED=Scoped' >> "$SNAP_DIR"/appenv.env + touch appenv.env + echo 'SCOPED=Scoped' >> appenv.env + app_envpath=$(realpath appenv.env) + # Load app-specific envfile - snap set env-injector-hello apps.hello1.envfile="$SNAP_DIR"/appenv.env + snap set env-injector-hello apps.hello1.envfile="$app_envpath" # [TEST] - Check if the app-specific envfile is loaded for the app check_env_var "env-injector-hello.hello1" "SCOPED" "Scoped" || exit 1 @@ -112,9 +118,11 @@ execute: | echo "[env-injector] Testing order of env vars" # To test order of env vars - echo 'ORDER="From envfile"' >> "$SNAP_DIR"/local.env + touch local.env + echo 'ORDER="From envfile"' >> local.env + local_envpath=$(realpath local.env) snap set env-injector-hello apps.hello1.env.order="from app-specific" - snap set env-injector-hello apps.hello1.envfile="$SNAP_DIR"/envfile.env + snap set env-injector-hello apps.hello1.envfile="$local_envpath" # [TEST] - Check if local overrites global check_env_var "env-injector-hello.hello1" "ORDER" "from app-specific" || exit 1 From 0d09a1580d4307c9dbde03841ffdb6b526303b7c Mon Sep 17 00:00:00 2001 From: Lincoln Wallace Date: Wed, 14 Aug 2024 08:26:48 -0300 Subject: [PATCH 21/41] test|refact: Add unit tests and rename class Signed-off-by: Lincoln Wallace --- snapcraft/extensions/env_injector.py | 2 +- snapcraft/extensions/registry.py | 4 +- tests/unit/extensions/test_env_injector.py | 94 ++++++++++++++++++++++ 3 files changed, 97 insertions(+), 3 deletions(-) create mode 100644 tests/unit/extensions/test_env_injector.py diff --git a/snapcraft/extensions/env_injector.py b/snapcraft/extensions/env_injector.py index 6001ad932e..7ac8696479 100644 --- a/snapcraft/extensions/env_injector.py +++ b/snapcraft/extensions/env_injector.py @@ -23,7 +23,7 @@ from .extension import Extension, get_extensions_data_dir -class EnvInjectorExtension(Extension): +class EnvInjector(Extension): """Extension to automatically set environment variables on snaps. This extension allows you to transform snap options into environment diff --git a/snapcraft/extensions/registry.py b/snapcraft/extensions/registry.py index 125bec4bef..e3ed00d23d 100644 --- a/snapcraft/extensions/registry.py +++ b/snapcraft/extensions/registry.py @@ -20,7 +20,7 @@ from snapcraft import errors -from .env_injector import EnvInjectorExtension +from .env_injector import EnvInjector from .gnome import GNOME from .kde_neon import KDENeon from .kde_neon_6 import KDENeon6 @@ -39,7 +39,7 @@ ExtensionType = Type[Extension] _EXTENSIONS: Dict[str, "ExtensionType"] = { - "env-injector": EnvInjectorExtension, + "env-injector": EnvInjector, "gnome": GNOME, "ros2-humble": ROS2HumbleExtension, "ros2-humble-ros-core": ROS2HumbleRosCoreExtension, diff --git a/tests/unit/extensions/test_env_injector.py b/tests/unit/extensions/test_env_injector.py new file mode 100644 index 0000000000..6019f1e70d --- /dev/null +++ b/tests/unit/extensions/test_env_injector.py @@ -0,0 +1,94 @@ +# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- +# +# Copyright 2024 Canonical Ltd. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 3 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import pytest + +from snapcraft.extensions import env_injector +from snapcraft.extensions.extension import get_extensions_data_dir + +############ +# Fixtures # +############ + + +@pytest.fixture +def envinjector_extension(): + return env_injector.EnvInjector( + yaml_data={"base": "core24", "parts": {}}, arch="amd64", target_arch="amd64" + ) + + +######################### +# EnvInjector Extension # +######################### + + +def test_get_supported_bases(envinjector_extension): + assert envinjector_extension.get_supported_bases() == ("core24",) + + +def test_get_supported_confinement(envinjector_extension): + assert envinjector_extension.get_supported_confinement() == ("strict", "devmode", "classic") + +def test_is_experimental(): + assert env_injector.EnvInjector.is_experimental() is True + +def test_get_root_snippet(): + assert env_injector.EnvInjector.get_root_snippet() == {} + + +def test_get_app_snippet(envinjector_extension): + assert envinjector_extension.get_app_snippet(app_name="test") == { + "command-chain": ["bin/command-chain/env-exporter"], + "environment": { + "env_alias": "test", + }, + } + +def test_get_part_snippet(): + assert env_injector.EnvInjector.get_part_snippet() == {} + +def test_get_part_snippet(envinjector_extension): + toolchain = "x86_64-unknown-linux-gnu" + assert envinjector_extension.get_part_snippet() == { + "env-injector/env-injector": { + "source": f"{get_extensions_data_dir()}/env-injector", + "plugin": "nil", + "build-snaps": [ + "rustup", + ], + "build-packages": [ + "upx-ucl", # for binary compression + ], + "override-build": f""" + + rustup default stable + rustup target add {toolchain} + + cargo build --target {toolchain} --release + mkdir -p $SNAPCRAFT_PART_INSTALL/bin/command-chain + + cp target/{toolchain}/release/env-exporter $SNAPCRAFT_PART_INSTALL/bin/command-chain + + # compress the binary + upx --best --lzma target/{toolchain}/release/env-exporter + cp target/{toolchain}/release/env-exporter $SNAPCRAFT_PART_INSTALL/bin/command-chain/env-exporter-upx + + """, + } + } + + From 18d6c5c8c4c3cc9fff8badb75a5540a978855d6b Mon Sep 17 00:00:00 2001 From: Lincoln Wallace Date: Wed, 14 Aug 2024 08:58:27 -0300 Subject: [PATCH 22/41] refact: formating python code Signed-off-by: Lincoln Wallace --- tests/unit/extensions/test_env_injector.py | 34 +++++++++++++--------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/tests/unit/extensions/test_env_injector.py b/tests/unit/extensions/test_env_injector.py index 6019f1e70d..c477547237 100644 --- a/tests/unit/extensions/test_env_injector.py +++ b/tests/unit/extensions/test_env_injector.py @@ -41,11 +41,17 @@ def test_get_supported_bases(envinjector_extension): def test_get_supported_confinement(envinjector_extension): - assert envinjector_extension.get_supported_confinement() == ("strict", "devmode", "classic") + assert envinjector_extension.get_supported_confinement() == ( + "strict", + "devmode", + "classic", + ) + def test_is_experimental(): assert env_injector.EnvInjector.is_experimental() is True + def test_get_root_snippet(): assert env_injector.EnvInjector.get_root_snippet() == {} @@ -58,22 +64,24 @@ def test_get_app_snippet(envinjector_extension): }, } + def test_get_part_snippet(): assert env_injector.EnvInjector.get_part_snippet() == {} + def test_get_part_snippet(envinjector_extension): toolchain = "x86_64-unknown-linux-gnu" assert envinjector_extension.get_part_snippet() == { - "env-injector/env-injector": { - "source": f"{get_extensions_data_dir()}/env-injector", - "plugin": "nil", - "build-snaps": [ - "rustup", - ], - "build-packages": [ - "upx-ucl", # for binary compression - ], - "override-build": f""" + "env-injector/env-injector": { + "source": f"{get_extensions_data_dir()}/env-injector", + "plugin": "nil", + "build-snaps": [ + "rustup", + ], + "build-packages": [ + "upx-ucl", # for binary compression + ], + "override-build": f""" rustup default stable rustup target add {toolchain} @@ -88,7 +96,5 @@ def test_get_part_snippet(envinjector_extension): cp target/{toolchain}/release/env-exporter $SNAPCRAFT_PART_INSTALL/bin/command-chain/env-exporter-upx """, - } } - - + } From 7bc6f24d443263ada720a05b01d46ee36030a6cf Mon Sep 17 00:00:00 2001 From: Lincoln Wallace Date: Wed, 14 Aug 2024 11:23:26 -0300 Subject: [PATCH 23/41] test: add parts to dedicated class Signed-off-by: Lincoln Wallace --- tests/unit/extensions/test_env_injector.py | 70 ++++++++++++---------- 1 file changed, 38 insertions(+), 32 deletions(-) diff --git a/tests/unit/extensions/test_env_injector.py b/tests/unit/extensions/test_env_injector.py index c477547237..f31b955ee5 100644 --- a/tests/unit/extensions/test_env_injector.py +++ b/tests/unit/extensions/test_env_injector.py @@ -65,36 +65,42 @@ def test_get_app_snippet(envinjector_extension): } -def test_get_part_snippet(): - assert env_injector.EnvInjector.get_part_snippet() == {} - - -def test_get_part_snippet(envinjector_extension): - toolchain = "x86_64-unknown-linux-gnu" - assert envinjector_extension.get_part_snippet() == { - "env-injector/env-injector": { - "source": f"{get_extensions_data_dir()}/env-injector", - "plugin": "nil", - "build-snaps": [ - "rustup", - ], - "build-packages": [ - "upx-ucl", # for binary compression - ], - "override-build": f""" - - rustup default stable - rustup target add {toolchain} - - cargo build --target {toolchain} --release - mkdir -p $SNAPCRAFT_PART_INSTALL/bin/command-chain - - cp target/{toolchain}/release/env-exporter $SNAPCRAFT_PART_INSTALL/bin/command-chain - - # compress the binary - upx --best --lzma target/{toolchain}/release/env-exporter - cp target/{toolchain}/release/env-exporter $SNAPCRAFT_PART_INSTALL/bin/command-chain/env-exporter-upx - - """, +class TestGetPartSnippet: + """Tests for EnvInjector.get_part_snippet when using the default sdk snap name.""" + + def test_get_part_snippet(self, envinjector_extension): + self.assert_get_part_snippet(envinjector_extension) + + @staticmethod + def assert_get_part_snippet(): + assert env_injector.EnvInjector.get_part_snippet() == {} + + def test_get_parts_snippet(envinjector_extension): + toolchain = "x86_64-unknown-linux-gnu" + assert envinjector_extension.get_part_snippet() == { + "env-injector/env-injector": { + "source": f"{get_extensions_data_dir()}/env-injector", + "plugin": "nil", + "build-snaps": [ + "rustup", + ], + "build-packages": [ + "upx-ucl", # for binary compression + ], + "override-build": f""" + + rustup default stable + rustup target add {toolchain} + + cargo build --target {toolchain} --release + mkdir -p $SNAPCRAFT_PART_INSTALL/bin/command-chain + + cp target/{toolchain}/release/env-exporter $SNAPCRAFT_PART_INSTALL/bin/command-chain + + # compress the binary + upx --best --lzma target/{toolchain}/release/env-exporter + cp target/{toolchain}/release/env-exporter $SNAPCRAFT_PART_INSTALL/bin/command-chain/env-exporter-upx + + """, + } } - } From 0c578142fd3abf380237b4ddc0219de07fbbc2c9 Mon Sep 17 00:00:00 2001 From: Lincoln Wallace Date: Wed, 14 Aug 2024 13:11:08 -0300 Subject: [PATCH 24/41] fix: address lint errors Signed-off-by: Lincoln Wallace --- tests/unit/extensions/test_env_injector.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/unit/extensions/test_env_injector.py b/tests/unit/extensions/test_env_injector.py index f31b955ee5..06974b6d4d 100644 --- a/tests/unit/extensions/test_env_injector.py +++ b/tests/unit/extensions/test_env_injector.py @@ -49,11 +49,11 @@ def test_get_supported_confinement(envinjector_extension): def test_is_experimental(): - assert env_injector.EnvInjector.is_experimental() is True + assert env_injector.EnvInjector.is_experimental(base="core24") is True -def test_get_root_snippet(): - assert env_injector.EnvInjector.get_root_snippet() == {} +def test_get_root_snippet(envinjector_extension): + assert envinjector_extension.get_root_snippet() == {} def test_get_app_snippet(envinjector_extension): @@ -72,10 +72,10 @@ def test_get_part_snippet(self, envinjector_extension): self.assert_get_part_snippet(envinjector_extension) @staticmethod - def assert_get_part_snippet(): - assert env_injector.EnvInjector.get_part_snippet() == {} + def assert_get_part_snippet(envinjector_extension): + assert envinjector_extension.get_part_snippet() == {} - def test_get_parts_snippet(envinjector_extension): + def test_get_parts_snippet(self,envinjector_extension): toolchain = "x86_64-unknown-linux-gnu" assert envinjector_extension.get_part_snippet() == { "env-injector/env-injector": { From cb72589030e2dd7a2ad3795b5e69e0b9cfd6b612 Mon Sep 17 00:00:00 2001 From: Lincoln Wallace Date: Wed, 14 Aug 2024 14:11:28 -0300 Subject: [PATCH 25/41] fix: missing fields Signed-off-by: Lincoln Wallace --- tests/unit/extensions/test_env_injector.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/extensions/test_env_injector.py b/tests/unit/extensions/test_env_injector.py index 06974b6d4d..b70d433b7a 100644 --- a/tests/unit/extensions/test_env_injector.py +++ b/tests/unit/extensions/test_env_injector.py @@ -73,11 +73,11 @@ def test_get_part_snippet(self, envinjector_extension): @staticmethod def assert_get_part_snippet(envinjector_extension): - assert envinjector_extension.get_part_snippet() == {} + assert envinjector_extension.get_part_snippet(plugin_name="nil") == {} def test_get_parts_snippet(self,envinjector_extension): toolchain = "x86_64-unknown-linux-gnu" - assert envinjector_extension.get_part_snippet() == { + assert envinjector_extension.get_parts_snippet() == { "env-injector/env-injector": { "source": f"{get_extensions_data_dir()}/env-injector", "plugin": "nil", From 297de3a65daf78befd2090156eea4bf88dfd5563 Mon Sep 17 00:00:00 2001 From: Lincoln Wallace Date: Wed, 14 Aug 2024 14:17:26 -0300 Subject: [PATCH 26/41] fix: PEP8 formating Signed-off-by: Lincoln Wallace --- tests/unit/extensions/test_env_injector.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/extensions/test_env_injector.py b/tests/unit/extensions/test_env_injector.py index b70d433b7a..ee55910e27 100644 --- a/tests/unit/extensions/test_env_injector.py +++ b/tests/unit/extensions/test_env_injector.py @@ -75,7 +75,7 @@ def test_get_part_snippet(self, envinjector_extension): def assert_get_part_snippet(envinjector_extension): assert envinjector_extension.get_part_snippet(plugin_name="nil") == {} - def test_get_parts_snippet(self,envinjector_extension): + def test_get_parts_snippet(self, envinjector_extension): toolchain = "x86_64-unknown-linux-gnu" assert envinjector_extension.get_parts_snippet() == { "env-injector/env-injector": { From 6b1dafe2332cf66d3ba167afeaf7a44fc671b49b Mon Sep 17 00:00:00 2001 From: Lincoln Wallace Date: Wed, 14 Aug 2024 15:07:01 -0300 Subject: [PATCH 27/41] refact: formating code Signed-off-by: Lincoln Wallace --- snapcraft/extensions/env_injector.py | 27 ++++++++----------- tests/unit/extensions/test_env_injector.py | 30 +++++++++------------- 2 files changed, 23 insertions(+), 34 deletions(-) diff --git a/snapcraft/extensions/env_injector.py b/snapcraft/extensions/env_injector.py index 7ac8696479..4138fedd64 100644 --- a/snapcraft/extensions/env_injector.py +++ b/snapcraft/extensions/env_injector.py @@ -93,30 +93,25 @@ def get_parts_snippet(self) -> Dict[str, Any]: raise ValueError( f"Unsupported architecture for env-injector extension: {self.arch}" ) + return { "env-injector/env-injector": { "source": f"{get_extensions_data_dir()}/env-injector", "plugin": "nil", - "build-snaps": [ - "rustup", - ], - "build-packages": [ - "upx-ucl", # for binary compression - ], + "build-snaps": ["rustup"], + "build-packages": ["upx-ucl"], # for binary compression "override-build": f""" + rustup default stable + rustup target add {toolchain} - rustup default stable - rustup target add {toolchain} + cargo build --target {toolchain} --release + mkdir -p $SNAPCRAFT_PART_INSTALL/bin/command-chain - cargo build --target {toolchain} --release - mkdir -p $SNAPCRAFT_PART_INSTALL/bin/command-chain - - cp target/{toolchain}/release/env-exporter $SNAPCRAFT_PART_INSTALL/bin/command-chain - - # compress the binary - upx --best --lzma target/{toolchain}/release/env-exporter - cp target/{toolchain}/release/env-exporter $SNAPCRAFT_PART_INSTALL/bin/command-chain/env-exporter-upx + cp target/{toolchain}/release/env-exporter $SNAPCRAFT_PART_INSTALL/bin/command-chain + # compress the binary + upx --best --lzma target/{toolchain}/release/env-exporter + cp target/{toolchain}/release/env-exporter $SNAPCRAFT_PART_INSTALL/bin/command-chain/env-exporter-upx """, } } diff --git a/tests/unit/extensions/test_env_injector.py b/tests/unit/extensions/test_env_injector.py index ee55910e27..2022cb6fe4 100644 --- a/tests/unit/extensions/test_env_injector.py +++ b/tests/unit/extensions/test_env_injector.py @@ -81,26 +81,20 @@ def test_get_parts_snippet(self, envinjector_extension): "env-injector/env-injector": { "source": f"{get_extensions_data_dir()}/env-injector", "plugin": "nil", - "build-snaps": [ - "rustup", - ], - "build-packages": [ - "upx-ucl", # for binary compression - ], + "build-snaps": ["rustup"], + "build-packages": ["upx-ucl"], # for binary compression "override-build": f""" + rustup default stable + rustup target add {toolchain} - rustup default stable - rustup target add {toolchain} + cargo build --target {toolchain} --release + mkdir -p $SNAPCRAFT_PART_INSTALL/bin/command-chain - cargo build --target {toolchain} --release - mkdir -p $SNAPCRAFT_PART_INSTALL/bin/command-chain + cp target/{toolchain}/release/env-exporter $SNAPCRAFT_PART_INSTALL/bin/command-chain - cp target/{toolchain}/release/env-exporter $SNAPCRAFT_PART_INSTALL/bin/command-chain - - # compress the binary - upx --best --lzma target/{toolchain}/release/env-exporter - cp target/{toolchain}/release/env-exporter $SNAPCRAFT_PART_INSTALL/bin/command-chain/env-exporter-upx - - """, + # compress the binary + upx --best --lzma target/{toolchain}/release/env-exporter + cp target/{toolchain}/release/env-exporter $SNAPCRAFT_PART_INSTALL/bin/command-chain/env-exporter-upx + """, } - } + } \ No newline at end of file From 498ad201508c95e8c3d58f7584b0432af9bf989c Mon Sep 17 00:00:00 2001 From: Lincoln Wallace Date: Wed, 14 Aug 2024 19:15:25 -0300 Subject: [PATCH 28/41] fix: solving last(I hope) TOX/linter complaining Signed-off-by: Lincoln Wallace --- snapcraft/extensions/env_injector.py | 2 +- tests/unit/extensions/test_env_injector.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/snapcraft/extensions/env_injector.py b/snapcraft/extensions/env_injector.py index 4138fedd64..4d4be0fb9d 100644 --- a/snapcraft/extensions/env_injector.py +++ b/snapcraft/extensions/env_injector.py @@ -93,7 +93,7 @@ def get_parts_snippet(self) -> Dict[str, Any]: raise ValueError( f"Unsupported architecture for env-injector extension: {self.arch}" ) - + return { "env-injector/env-injector": { "source": f"{get_extensions_data_dir()}/env-injector", diff --git a/tests/unit/extensions/test_env_injector.py b/tests/unit/extensions/test_env_injector.py index 2022cb6fe4..4eb1c9e601 100644 --- a/tests/unit/extensions/test_env_injector.py +++ b/tests/unit/extensions/test_env_injector.py @@ -97,4 +97,4 @@ def test_get_parts_snippet(self, envinjector_extension): cp target/{toolchain}/release/env-exporter $SNAPCRAFT_PART_INSTALL/bin/command-chain/env-exporter-upx """, } - } \ No newline at end of file + } From 5e155980cbf77949df9533641b049085effffecc Mon Sep 17 00:00:00 2001 From: Lincoln Wallace Date: Thu, 15 Aug 2024 10:26:44 -0300 Subject: [PATCH 29/41] cleanup: remove comment Signed-off-by: Lincoln Wallace --- snapcraft/extensions/env_injector.py | 1 - 1 file changed, 1 deletion(-) diff --git a/snapcraft/extensions/env_injector.py b/snapcraft/extensions/env_injector.py index 4d4be0fb9d..ec27b71ffd 100644 --- a/snapcraft/extensions/env_injector.py +++ b/snapcraft/extensions/env_injector.py @@ -118,7 +118,6 @@ def get_parts_snippet(self) -> Dict[str, Any]: def get_toolchain(self): """Get the Rust toolchain for the current architecture.""" - # Dictionary mapping architecture names toolchain = { "amd64": "x86_64-unknown-linux-gnu", "arm64": "aarch64-unknown-linux-gnu", From c20cac81cd1db80f3bd108b0e7d90858e1966f7b Mon Sep 17 00:00:00 2001 From: Lincoln Wallace Date: Fri, 16 Aug 2024 13:30:59 -0300 Subject: [PATCH 30/41] many: reviews addressed - refact: Change comments to log messages - refact: Changed install confinement to devmode - refact: error messages Signed-off-by: Lincoln Wallace --- spread.yaml | 2 +- .../spread/extensions/env-injector/task.yaml | 68 +++++++++---------- .../env-injector-hello/snap/snapcraft.yaml | 2 +- 3 files changed, 35 insertions(+), 37 deletions(-) diff --git a/spread.yaml b/spread.yaml index 039ef727b3..ef2b4e1747 100644 --- a/spread.yaml +++ b/spread.yaml @@ -82,7 +82,7 @@ backends: FATAL "$SPREAD_SYSTEM is not supported!" fi - multipass launch --disk 20G --memory 2G --name "$instance_name" "$image" + multipass launch --cpus 8 --disk 30G --memory 8G --name "$instance_name" "$image" # Get the IP from the instance ip=$(multipass info --format csv "$instance_name" | tail -1 | cut -d\, -f3) diff --git a/tests/spread/extensions/env-injector/task.yaml b/tests/spread/extensions/env-injector/task.yaml index a8989c101e..9b4fe2f164 100644 --- a/tests/spread/extensions/env-injector/task.yaml +++ b/tests/spread/extensions/env-injector/task.yaml @@ -28,7 +28,7 @@ restore: | execute: | - check_env_var() { + assert_env() { local snap_app="$1" local env_name="$2" local exp_value="$3" @@ -40,6 +40,11 @@ execute: | return 1 fi + if [ -z "$env_name" ]; then + empty=$( "$snap_app" | grep "=${exp_value}") + [ -z "$empty" ] || return 1 + fi + actual_value=$( "$snap_app" | grep "^${env_name}=" | cut -d'=' -f2-) if [ "$actual_value" != "$exp_value" ]; then @@ -48,7 +53,7 @@ execute: | return 1 fi - return 0 + return 0 } cd "$SNAP_DIR" @@ -56,77 +61,70 @@ execute: | unsquashfs "${SNAP}"_1.0_*.snap - # Check that the env-exporter program is present [ -f squashfs-root/bin/command-chain/env-exporter ] - + # Check that the exec-env script is present [ -f squashfs-root/usr/bin/exec-env ] - snap install "${SNAP}"_1.0_*.snap --dangerous --classic + snap install "${SNAP}"_1.0_*.snap --dangerous --devmode echo "[env-injector] Creating global envfile" - touch global.env echo 'HELLO_WORLD="Hello World"' >> global.env global_envpath=$(realpath global.env) # Load global envfile snap set env-injector-hello envfile="$global_envpath" - # [TEST] - Check if the global envfile is loaded for all apps - check_env_var "env-injector-hello.hello1" "HELLO_WORLD" "Hello World" || exit 1 - check_env_var "env-injector-hello.hello2" "HELLO_WORLD" "Hello World" || exit 1 - check_env_var "env-injector-hello.hello-demo" "HELLO_WORLD" "Hello World" || exit 1 + echo "[TEST] - Check if the global envfile is loaded for all apps" + assert_env "env-injector-hello.hello1" "HELLO_WORLD" "Hello World" || exit 1 + assert_env "env-injector-hello.hello2" "HELLO_WORLD" "Hello World" || exit 1 + assert_env "env-injector-hello.hello-demo" "HELLO_WORLD" "Hello World" || exit 1 echo "[env-injector] Creating app-specific envfile" - touch appenv.env echo 'SCOPED=Scoped' >> appenv.env app_envpath=$(realpath appenv.env) # Load app-specific envfile snap set env-injector-hello apps.hello1.envfile="$app_envpath" - # [TEST] - Check if the app-specific envfile is loaded for the app - check_env_var "env-injector-hello.hello1" "SCOPED" "Scoped" || exit 1 + echo "[TEST] - Check if the app-specific envfile is loaded for the app" + assert_env "env-injector-hello.hello1" "SCOPED" "Scoped" || exit 1 echo "[env-injector] Setting global env variable" # Set env vars: Global snap set env-injector-hello env.global="World" - # [TEST] - Check if the global env var is set for all apps - check_env_var "env-injector-hello.hello1" "GLOBAL" "World" || exit 1 - check_env_var "env-injector-hello.hello2" "GLOBAL" "World" || exit 1 - check_env_var "env-injector-hello.hello-demo" "GLOBAL" "World" || exit 1 + echo "[TEST] - Check if the global env var is set for all apps" + assert_env "env-injector-hello.hello1" "GLOBAL" "World" || exit 1 + assert_env "env-injector-hello.hello2" "GLOBAL" "World" || exit 1 + assert_env "env-injector-hello.hello-demo" "GLOBAL" "World" || exit 1 echo "[env-injector] Setting app-specific env variable" # Set env vars: specific to each app snap set env-injector-hello apps.hello1.env.hello="Hello" snap set env-injector-hello apps.hello2.env.specific="City" - # [TEST] - Check if the app-specific env var IS SET for the app hello1 - check_env_var "env-injector-hello.hello1" "HELLO" "Hello" || exit 1 - # [TEST] - Check if the app-specific env var IS NOT SET for the app hello2 - ! check_env_var "env-injector-hello.hello2" "HELLO" "Hello" || exit 1 + echo "[TEST] - Check if the app-specific env var IS SET for the app hello1" + assert_env "env-injector-hello.hello1" "HELLO" "Hello" || exit 1 + echo "[TEST] - Check if the app-specific env var IS NOT SET for the app hello2" + ! assert_env "env-injector-hello.hello2" "HELLO" "Hello" || exit 1 - # [TEST] - Check if the app-specific env var IS SET for the app hello2 - check_env_var "env-injector-hello.hello2" "SPECIFIC" "City" || exit 1 - # [TEST] - Check if the app-specific env var IS NOT SET for the app hello1 - ! check_env_var "env-injector-hello.hello1" "SPECIFIC" "City" || exit 1 + echo "[TEST] - Check if the app-specific env var IS SET for the app hello2" + assert_env "env-injector-hello.hello2" "SPECIFIC" "City" || exit 1 + echo "[TEST] - Check if the app-specific env var IS NOT SET for the app hello1" + ! assert_env "env-injector-hello.hello1" "SPECIFIC" "City" || exit 1 - echo "[env-injector] Rejecting invalid keys" - # To check if key with dot will be rejected snap set env-injector-hello env.word.dot="wrong" - # [TEST] - Check if the key with dot was rejected - ! check_env_var "env-injector-hello.hello1" "DOT" "wrong" || exit 1 + echo "[TEST] - Check if the key with dot was ignored" + ! assert_env "env-injector-hello.hello1" "" "wrong" || exit 1 echo "[env-injector] Testing order of env vars" - # To test order of env vars - touch local.env echo 'ORDER="From envfile"' >> local.env local_envpath=$(realpath local.env) snap set env-injector-hello apps.hello1.env.order="from app-specific" snap set env-injector-hello apps.hello1.envfile="$local_envpath" - # [TEST] - Check if local overrites global - check_env_var "env-injector-hello.hello1" "ORDER" "from app-specific" || exit 1 + echo "[TEST] - Check if local overrites global" + assert_env "env-injector-hello.hello1" "ORDER" "from app-specific" || exit 1 echo "[env-injector] Run hello-demo app" snap set env-injector-hello apps.myapp.env.specific="City" - # [TEST] Make sure that alias is NOT rewritten - check_env_var "env-injector-hello.hello-demo" "SPECIFIC" "City" || exit 1 + echo "[TEST] Make sure that alias is NOT rewritten" + assert_env "env-injector-hello.hello-demo" "SPECIFIC" "City" || exit 1 diff --git a/tests/spread/extensions/snaps/env-injector-hello/snap/snapcraft.yaml b/tests/spread/extensions/snaps/env-injector-hello/snap/snapcraft.yaml index cca2341242..fc925f609f 100644 --- a/tests/spread/extensions/snaps/env-injector-hello/snap/snapcraft.yaml +++ b/tests/spread/extensions/snaps/env-injector-hello/snap/snapcraft.yaml @@ -5,7 +5,7 @@ description: This is a basic snap for testing env-injector extension grade: devel base: core24 -confinement: classic +confinement: strict apps: hello1: From e30ed8996c95be263997f8a3e4155a4dc520d5fb Mon Sep 17 00:00:00 2001 From: Lincoln Wallace Date: Fri, 16 Aug 2024 14:22:52 -0300 Subject: [PATCH 31/41] refact: using common directory Signed-off-by: Lincoln Wallace --- .../spread/extensions/env-injector/task.yaml | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/tests/spread/extensions/env-injector/task.yaml b/tests/spread/extensions/env-injector/task.yaml index 9b4fe2f164..f50bdcd9da 100644 --- a/tests/spread/extensions/env-injector/task.yaml +++ b/tests/spread/extensions/env-injector/task.yaml @@ -19,7 +19,7 @@ restore: | cd "$SNAP_DIR" snapcraft clean - rm -f ./*.env + rm -f /var/snap/"${SNAP}"/common/*.env rm -f ./*.snap rm -rf ./squashfs-root #shellcheck source=tests/spread/tools/snapcraft-yaml.sh @@ -67,25 +67,23 @@ execute: | # Check that the exec-env script is present [ -f squashfs-root/usr/bin/exec-env ] - snap install "${SNAP}"_1.0_*.snap --dangerous --devmode + snap install "${SNAP}"_1.0_*.snap --dangerous echo "[env-injector] Creating global envfile" - echo 'HELLO_WORLD="Hello World"' >> global.env - global_envpath=$(realpath global.env) + echo 'HELLO_WORLD="Hello World"' >> /var/snap/"${SNAP}"/common/global.env # Load global envfile - snap set env-injector-hello envfile="$global_envpath" + snap set env-injector-hello envfile=/var/snap/"${SNAP}"/common/global.env echo "[TEST] - Check if the global envfile is loaded for all apps" assert_env "env-injector-hello.hello1" "HELLO_WORLD" "Hello World" || exit 1 assert_env "env-injector-hello.hello2" "HELLO_WORLD" "Hello World" || exit 1 assert_env "env-injector-hello.hello-demo" "HELLO_WORLD" "Hello World" || exit 1 echo "[env-injector] Creating app-specific envfile" - echo 'SCOPED=Scoped' >> appenv.env - app_envpath=$(realpath appenv.env) + echo 'SCOPED=Scoped' >> /var/snap/"${SNAP}"/common/appenv.env # Load app-specific envfile - snap set env-injector-hello apps.hello1.envfile="$app_envpath" + snap set env-injector-hello apps.hello1.envfile=/var/snap/"${SNAP}"/common/appenv.env echo "[TEST] - Check if the app-specific envfile is loaded for the app" assert_env "env-injector-hello.hello1" "SCOPED" "Scoped" || exit 1 @@ -117,10 +115,9 @@ execute: | ! assert_env "env-injector-hello.hello1" "" "wrong" || exit 1 echo "[env-injector] Testing order of env vars" - echo 'ORDER="From envfile"' >> local.env - local_envpath=$(realpath local.env) + echo 'ORDER="From envfile"' >> /var/snap/"${SNAP}"/common/local.env snap set env-injector-hello apps.hello1.env.order="from app-specific" - snap set env-injector-hello apps.hello1.envfile="$local_envpath" + snap set env-injector-hello apps.hello1.envfile=/var/snap/"${SNAP}"/common/ echo "[TEST] - Check if local overrites global" assert_env "env-injector-hello.hello1" "ORDER" "from app-specific" || exit 1 From 645fb355f25e8145b460ff3693e20a253959d12d Mon Sep 17 00:00:00 2001 From: Lincoln Wallace Date: Fri, 16 Aug 2024 22:33:28 -0300 Subject: [PATCH 32/41] fix: missing filename Signed-off-by: Lincoln Wallace --- tests/spread/extensions/env-injector/task.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/spread/extensions/env-injector/task.yaml b/tests/spread/extensions/env-injector/task.yaml index f50bdcd9da..69731ef048 100644 --- a/tests/spread/extensions/env-injector/task.yaml +++ b/tests/spread/extensions/env-injector/task.yaml @@ -117,7 +117,7 @@ execute: | echo "[env-injector] Testing order of env vars" echo 'ORDER="From envfile"' >> /var/snap/"${SNAP}"/common/local.env snap set env-injector-hello apps.hello1.env.order="from app-specific" - snap set env-injector-hello apps.hello1.envfile=/var/snap/"${SNAP}"/common/ + snap set env-injector-hello apps.hello1.envfile=/var/snap/"${SNAP}"/common/local.env echo "[TEST] - Check if local overrites global" assert_env "env-injector-hello.hello1" "ORDER" "from app-specific" || exit 1 From d839fe20ce77c41163eeae51724a7d94f4f1e5c1 Mon Sep 17 00:00:00 2001 From: Lincoln Wallace Date: Sat, 17 Aug 2024 09:41:19 -0300 Subject: [PATCH 33/41] tests: add tests to toolchain fetch method Signed-off-by: Lincoln Wallace --- snapcraft/extensions/env_injector.py | 6 +++--- tests/unit/extensions/test_env_injector.py | 8 ++++++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/snapcraft/extensions/env_injector.py b/snapcraft/extensions/env_injector.py index ec27b71ffd..51e8f6f9e2 100644 --- a/snapcraft/extensions/env_injector.py +++ b/snapcraft/extensions/env_injector.py @@ -88,7 +88,7 @@ def get_part_snippet(self, *, plugin_name: str) -> Dict[str, Any]: @overrides def get_parts_snippet(self) -> Dict[str, Any]: - toolchain = self.get_toolchain() + toolchain = self.get_toolchain(self.arch) if toolchain is None: raise ValueError( f"Unsupported architecture for env-injector extension: {self.arch}" @@ -116,7 +116,7 @@ def get_parts_snippet(self) -> Dict[str, Any]: } } - def get_toolchain(self): + def get_toolchain(self, arch: str): """Get the Rust toolchain for the current architecture.""" toolchain = { "amd64": "x86_64-unknown-linux-gnu", @@ -126,4 +126,4 @@ def get_toolchain(self): # 'ppc64el': 'powerpc64-unknown-linux-gnu', # Tier 2 toolchain # 's390x': 's390x-unknown-linux-gnu', # Tier 2 toolchain } - return toolchain.get(self.arch) + return toolchain.get(arch) diff --git a/tests/unit/extensions/test_env_injector.py b/tests/unit/extensions/test_env_injector.py index 4eb1c9e601..77c0e576d4 100644 --- a/tests/unit/extensions/test_env_injector.py +++ b/tests/unit/extensions/test_env_injector.py @@ -65,6 +65,14 @@ def test_get_app_snippet(envinjector_extension): } +def test_get_toolchain_amd64(envinjector_extension): + assert envinjector_extension.get_toolchain("amd64") == "x86_64-unknown-linux-gnu" + + +def test_get_toolchain_arm64(envinjector_extension): + assert envinjector_extension.get_toolchain("arm64") == "aarch64-unknown-linux-gnu" + + class TestGetPartSnippet: """Tests for EnvInjector.get_part_snippet when using the default sdk snap name.""" From 76e51e6a4efdd649df34ef9b4a2695f1401cf6ea Mon Sep 17 00:00:00 2001 From: Lincoln Wallace Date: Tue, 3 Sep 2024 13:33:11 -0300 Subject: [PATCH 34/41] fix: don't copy compressed bin to snap Co-authored-by: Farshid Tavakolizadeh --- snapcraft/extensions/env_injector.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/snapcraft/extensions/env_injector.py b/snapcraft/extensions/env_injector.py index 51e8f6f9e2..92d6784230 100644 --- a/snapcraft/extensions/env_injector.py +++ b/snapcraft/extensions/env_injector.py @@ -107,11 +107,10 @@ def get_parts_snippet(self) -> Dict[str, Any]: cargo build --target {toolchain} --release mkdir -p $SNAPCRAFT_PART_INSTALL/bin/command-chain - cp target/{toolchain}/release/env-exporter $SNAPCRAFT_PART_INSTALL/bin/command-chain - # compress the binary upx --best --lzma target/{toolchain}/release/env-exporter - cp target/{toolchain}/release/env-exporter $SNAPCRAFT_PART_INSTALL/bin/command-chain/env-exporter-upx + + cp target/{toolchain}/release/env-exporter $SNAPCRAFT_PART_INSTALL/bin/command-chain """, } } From ecbde85b6b6cc5188765d491b997e68b0aac8a42 Mon Sep 17 00:00:00 2001 From: Lincoln Wallace Date: Tue, 3 Sep 2024 19:58:18 -0300 Subject: [PATCH 35/41] refact: move exporter program to separate repo - exporter program moved to: https://github.com/canonical/snappy-env Signed-off-by: Lincoln Wallace --- extensions/env-injector/Cargo.toml | 21 --- extensions/env-injector/src/main.rs | 156 --------------------- snapcraft/extensions/env_injector.py | 2 +- tests/unit/extensions/test_env_injector.py | 5 +- 4 files changed, 3 insertions(+), 181 deletions(-) delete mode 100644 extensions/env-injector/Cargo.toml delete mode 100644 extensions/env-injector/src/main.rs diff --git a/extensions/env-injector/Cargo.toml b/extensions/env-injector/Cargo.toml deleted file mode 100644 index f9c06ace3f..0000000000 --- a/extensions/env-injector/Cargo.toml +++ /dev/null @@ -1,21 +0,0 @@ -[package] -name = "env-exporter" -version = "0.1.0" -edition = "2021" - -[dependencies] -dotenv = "0.15.0" -http-body-util = "0.1.2" -hyper = {git = "https://github.com/hyperium/hyper", tag = "v1.4.1"} -hyper-util = "0.1.6" -hyperlocal = {git = "https://github.com/softprops/hyperlocal", tag = "v0.9.1"} -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" -tokio = { version = "1.35", features = ["full"] } - -[profile.release] -panic = "abort" -strip = true -opt-level = "z" -lto = true -codegen-units = 1 diff --git a/extensions/env-injector/src/main.rs b/extensions/env-injector/src/main.rs deleted file mode 100644 index f027a26343..0000000000 --- a/extensions/env-injector/src/main.rs +++ /dev/null @@ -1,156 +0,0 @@ -use http_body_util::{BodyExt, Full}; -use hyper::body::Bytes; -use hyper::{Method, Request}; -use hyper_util::client::legacy::Client; -use hyperlocal::{UnixClientExt, UnixConnector, Uri}; -use std::error::Error; -use std::process::Command; -use std::collections::HashMap; -use std::path::Path; -use dotenv; - -const SNAPD_SOCKET: &str = "/run/snapd-snap.socket"; - -async fn snapdapi_req() -> Result> { - let url: hyperlocal::Uri = Uri::new(SNAPD_SOCKET, "/v2/snapctl").into(); - - let client: Client> = Client::unix(); - - let snap_context = std::env::var("SNAP_CONTEXT")?; - - let request_body = format!( - r#"{{"context-id":"{}","args":["get", "env", "envfile", "apps"]}}"#, - snap_context - ); - - let req: Request> = Request::builder() - .method(Method::POST) - .uri(url) - .body(Full::from(request_body))?; - - let mut res = client.request(req).await?; - - let mut body: Vec = Vec::new(); - - while let Some(frame_result) = res.frame().await { - let frame = frame_result?; - - if let Some(segment) = frame.data_ref() { - body.extend_from_slice(segment); - } - } - - Ok(serde_json::from_slice(&body)?) -} - -fn process_env(env: &serde_json::Value) -> HashMap { - let obj = env.as_object() - .ok_or("Expected an object (JSON input)").unwrap(); - let mut map = HashMap::new(); - - for (k, v) in obj { - if v.is_object() || v.is_array() { - eprintln!( - "Skipped invalid key containing dots: {}", - v - ); - continue; - } - - let key = k.to_uppercase().replace("-", "_"); - let value = v.to_string(); - map.insert(key, value); - } - map -} - -fn set_env_vars(app: &str, json: &serde_json::Value) -> Result<(), Box> { - let stdout_str = json["result"]["stdout"].as_str().ok_or("Invalid stdout")?; - let stdout_json: serde_json::Value = serde_json::from_str(stdout_str)?; - - - if let Some(global_env) = stdout_json["env"].as_object() { - for (key, value) in process_env(&serde_json::Value::Object(global_env.clone())) { - std::env::set_var(key, value.trim_matches('"')); - } - } - - if let Some(app_env) = stdout_json["apps"][app]["env"].as_object() { - for (key, value) in process_env(&serde_json::Value::Object(app_env.clone())) { - std::env::set_var(key, value.trim_matches('"')); - } - } - - Ok(()) -} - -fn source_env_file(file_path: &str) -> std::io::Result<()> { - let path = Path::new(file_path); - - if !path.exists() { - eprintln!("File does not exist: {}", file_path); - return Err(std::io::Error::new(std::io::ErrorKind::NotFound, "File does not exist")); - } - - if !path.is_file() { - eprintln!("File is not readable: {}", file_path); - return Err(std::io::Error::new(std::io::ErrorKind::PermissionDenied, "File is not readable")); - } - - dotenv::from_path(path).map_err(|e| { - eprintln!("Failed to load environment file: {}", e); - std::io::Error::new(std::io::ErrorKind::InvalidData, "Failed to load environment file") - })?; - - Ok(()) -} - -fn set_env_vars_from_file(app: &str, json: &serde_json::Value) -> Result<(), Box> { - // Extract the stdout JSON string and parse it - let stdout_str = json["result"]["stdout"].as_str().ok_or("Invalid stdout")?; - let stdout_json: serde_json::Value = serde_json::from_str(stdout_str)?; - - // Source the global envfile first - if let Some(global_envfile) = stdout_json["envfile"].as_str() { - source_env_file(global_envfile)?; - } - - // Source the app-specific envfile - if let Some(app_envfile) = stdout_json["apps"][app]["envfile"].as_str() { - source_env_file(app_envfile)?; - } - - Ok(()) -} - -#[tokio::main] -async fn run() -> Result<(), Box> { - - let json = snapdapi_req().await?; - - let app = std::env::var("env_alias")?; - - set_env_vars_from_file(&app, &json)?; - set_env_vars(&app, &json)?; - - Ok(()) -} - -fn main() -> Result<(), Box> { - - let args: Vec = std::env::args().collect(); - - if args.len() < 2 { - eprintln!("Usage: {} ", args[0]); - std::process::exit(1); - } - - let command = args[1].clone(); - let args = args[2..].to_vec(); - - run()?; - - let status = Command::new(command).args(args).status()?; - - std::process::exit(status.code().unwrap_or(1)); -} diff --git a/snapcraft/extensions/env_injector.py b/snapcraft/extensions/env_injector.py index 92d6784230..6aedd5128d 100644 --- a/snapcraft/extensions/env_injector.py +++ b/snapcraft/extensions/env_injector.py @@ -96,7 +96,7 @@ def get_parts_snippet(self) -> Dict[str, Any]: return { "env-injector/env-injector": { - "source": f"{get_extensions_data_dir()}/env-injector", + "source": f"https://github.com/canonical/snappy-env.git", "plugin": "nil", "build-snaps": ["rustup"], "build-packages": ["upx-ucl"], # for binary compression diff --git a/tests/unit/extensions/test_env_injector.py b/tests/unit/extensions/test_env_injector.py index 77c0e576d4..b34cd2c8e5 100644 --- a/tests/unit/extensions/test_env_injector.py +++ b/tests/unit/extensions/test_env_injector.py @@ -98,11 +98,10 @@ def test_get_parts_snippet(self, envinjector_extension): cargo build --target {toolchain} --release mkdir -p $SNAPCRAFT_PART_INSTALL/bin/command-chain - cp target/{toolchain}/release/env-exporter $SNAPCRAFT_PART_INSTALL/bin/command-chain - # compress the binary upx --best --lzma target/{toolchain}/release/env-exporter - cp target/{toolchain}/release/env-exporter $SNAPCRAFT_PART_INSTALL/bin/command-chain/env-exporter-upx + + cp target/{toolchain}/release/env-exporter $SNAPCRAFT_PART_INSTALL/bin/command-chain """, } } From 48378caffbf9de943bce94e56f9b56e4e10e5d84 Mon Sep 17 00:00:00 2001 From: Lincoln Wallace Date: Tue, 3 Sep 2024 20:03:46 -0300 Subject: [PATCH 36/41] fix: lint error Signed-off-by: Lincoln Wallace --- snapcraft/extensions/env_injector.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snapcraft/extensions/env_injector.py b/snapcraft/extensions/env_injector.py index 6aedd5128d..dadf074fa8 100644 --- a/snapcraft/extensions/env_injector.py +++ b/snapcraft/extensions/env_injector.py @@ -96,7 +96,7 @@ def get_parts_snippet(self) -> Dict[str, Any]: return { "env-injector/env-injector": { - "source": f"https://github.com/canonical/snappy-env.git", + "source": "https://github.com/canonical/snappy-env.git", "plugin": "nil", "build-snaps": ["rustup"], "build-packages": ["upx-ucl"], # for binary compression From 0ce514a8bccf88c43366819584071af262eccd83 Mon Sep 17 00:00:00 2001 From: Lincoln Wallace Date: Tue, 3 Sep 2024 21:10:46 -0300 Subject: [PATCH 37/41] fix: lint include error Signed-off-by: Lincoln Wallace --- snapcraft/extensions/env_injector.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snapcraft/extensions/env_injector.py b/snapcraft/extensions/env_injector.py index dadf074fa8..c6f2829b29 100644 --- a/snapcraft/extensions/env_injector.py +++ b/snapcraft/extensions/env_injector.py @@ -20,7 +20,7 @@ from overrides import overrides -from .extension import Extension, get_extensions_data_dir +from .extension import Extension class EnvInjector(Extension): From a97df2dd86dd00238ae5e68d49b87a0899392951 Mon Sep 17 00:00:00 2001 From: Lincoln Wallace Date: Thu, 26 Sep 2024 13:08:32 -0300 Subject: [PATCH 38/41] feat|fix: set tagged version of snappy-env and fix unit test Signed-off-by: Lincoln Wallace --- snapcraft/extensions/env_injector.py | 1 + tests/unit/extensions/test_env_injector.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/snapcraft/extensions/env_injector.py b/snapcraft/extensions/env_injector.py index c6f2829b29..32a1b34a36 100644 --- a/snapcraft/extensions/env_injector.py +++ b/snapcraft/extensions/env_injector.py @@ -97,6 +97,7 @@ def get_parts_snippet(self) -> Dict[str, Any]: return { "env-injector/env-injector": { "source": "https://github.com/canonical/snappy-env.git", + "source-tag": "v1.0.0-beta", "plugin": "nil", "build-snaps": ["rustup"], "build-packages": ["upx-ucl"], # for binary compression diff --git a/tests/unit/extensions/test_env_injector.py b/tests/unit/extensions/test_env_injector.py index b34cd2c8e5..40b8f406cf 100644 --- a/tests/unit/extensions/test_env_injector.py +++ b/tests/unit/extensions/test_env_injector.py @@ -17,7 +17,6 @@ import pytest from snapcraft.extensions import env_injector -from snapcraft.extensions.extension import get_extensions_data_dir ############ # Fixtures # @@ -87,7 +86,8 @@ def test_get_parts_snippet(self, envinjector_extension): toolchain = "x86_64-unknown-linux-gnu" assert envinjector_extension.get_parts_snippet() == { "env-injector/env-injector": { - "source": f"{get_extensions_data_dir()}/env-injector", + "source": "https://github.com/canonical/snappy-env.git", + "source-tag": "v1.0.0-beta", "plugin": "nil", "build-snaps": ["rustup"], "build-packages": ["upx-ucl"], # for binary compression From b5f6370b8c383fef97302a5ad425d494c2513517 Mon Sep 17 00:00:00 2001 From: Lincoln Wallace Date: Thu, 26 Sep 2024 13:53:16 -0300 Subject: [PATCH 39/41] test: check error value for toolchain selection Signed-off-by: Lincoln Wallace --- tests/unit/extensions/test_env_injector.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/unit/extensions/test_env_injector.py b/tests/unit/extensions/test_env_injector.py index 40b8f406cf..da70e293f9 100644 --- a/tests/unit/extensions/test_env_injector.py +++ b/tests/unit/extensions/test_env_injector.py @@ -82,7 +82,8 @@ def test_get_part_snippet(self, envinjector_extension): def assert_get_part_snippet(envinjector_extension): assert envinjector_extension.get_part_snippet(plugin_name="nil") == {} - def test_get_parts_snippet(self, envinjector_extension): + @pytest.mark.parametrize("unsupported_arch", ["armhf", "riscv64", "ppc64el", "s390x"]) + def test_get_parts_snippet(self, envinjector_extension, unsupported_arch): toolchain = "x86_64-unknown-linux-gnu" assert envinjector_extension.get_parts_snippet() == { "env-injector/env-injector": { @@ -105,3 +106,7 @@ def test_get_parts_snippet(self, envinjector_extension): """, } } + + envinjector_extension.arch = unsupported_arch + with pytest.raises(ValueError, match="Unsupported architecture for env-injector extension"): + envinjector_extension.get_parts_snippet() From fb432b8cc424c8a06b34c8d4aab1a8f12183d9e3 Mon Sep 17 00:00:00 2001 From: Lincoln Wallace Date: Thu, 26 Sep 2024 14:35:38 -0300 Subject: [PATCH 40/41] refact: formating Signed-off-by: Lincoln Wallace --- tests/unit/extensions/test_env_injector.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/unit/extensions/test_env_injector.py b/tests/unit/extensions/test_env_injector.py index da70e293f9..6f4faeba89 100644 --- a/tests/unit/extensions/test_env_injector.py +++ b/tests/unit/extensions/test_env_injector.py @@ -82,7 +82,9 @@ def test_get_part_snippet(self, envinjector_extension): def assert_get_part_snippet(envinjector_extension): assert envinjector_extension.get_part_snippet(plugin_name="nil") == {} - @pytest.mark.parametrize("unsupported_arch", ["armhf", "riscv64", "ppc64el", "s390x"]) + @pytest.mark.parametrize( + "unsupported_arch", ["armhf", "riscv64", "ppc64el", "s390x"] + ) def test_get_parts_snippet(self, envinjector_extension, unsupported_arch): toolchain = "x86_64-unknown-linux-gnu" assert envinjector_extension.get_parts_snippet() == { @@ -108,5 +110,7 @@ def test_get_parts_snippet(self, envinjector_extension, unsupported_arch): } envinjector_extension.arch = unsupported_arch - with pytest.raises(ValueError, match="Unsupported architecture for env-injector extension"): + with pytest.raises( + ValueError, match="Unsupported architecture for env-injector extension" + ): envinjector_extension.get_parts_snippet() From 01c5c251f98a0cdee3154872683b1d4d14ed06ea Mon Sep 17 00:00:00 2001 From: Lincoln Wallace Date: Fri, 27 Sep 2024 09:16:52 -0300 Subject: [PATCH 41/41] fix: revert change to spread Signed-off-by: Lincoln Wallace --- spread.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spread.yaml b/spread.yaml index ef2b4e1747..039ef727b3 100644 --- a/spread.yaml +++ b/spread.yaml @@ -82,7 +82,7 @@ backends: FATAL "$SPREAD_SYSTEM is not supported!" fi - multipass launch --cpus 8 --disk 30G --memory 8G --name "$instance_name" "$image" + multipass launch --disk 20G --memory 2G --name "$instance_name" "$image" # Get the IP from the instance ip=$(multipass info --format csv "$instance_name" | tail -1 | cut -d\, -f3)