def test_get_snap_project_no_base(snapcraft_yaml, new_dir): with pytest.raises(errors.ProjectValidationError) as raised: Project.unmarshal(snapcraft_yaml(base=None)) assert str(raised.value) == ( "Bad snapcraft.yaml content:\n" "- Snap base must be declared when type is not base, kernel or snapd")
def run(self, parsed_args): snap_project = get_snap_project() yaml_data = process_yaml(snap_project.project_file) expanded_yaml_data = extensions.apply_extensions( yaml_data, arch=get_host_architecture(), target_arch=get_host_architecture(), ) Project.unmarshal(expanded_yaml_data) emit.message( yaml.safe_dump(expanded_yaml_data, indent=4, sort_keys=False))
def test_lifecycle_clean_part_names(snapcraft_yaml, project_vars, new_dir, mocker): """Clean project inside provider if called with part names.""" project = Project.unmarshal(snapcraft_yaml(base="core22")) run_in_provider_mock = mocker.patch("snapcraft.parts.lifecycle._run_in_provider") parts_lifecycle._run_command( "clean", project=project, parse_info={}, assets_dir=Path(), start_time=datetime.now(), parallel_build_count=8, parsed_args=argparse.Namespace( directory=None, output=None, destructive_mode=False, use_lxd=False, parts=["part1"], ), ) assert run_in_provider_mock.mock_calls == [ call( project, "clean", argparse.Namespace( directory=None, output=None, destructive_mode=False, use_lxd=False, parts=["part1"], ), ) ]
def test_icon(new_dir): content = dedent("""\ name: project-name base: core22 version: "1.0" summary: sanity checks description: sanity checks grade: stable confinement: strict icon: foo.png parts: nil: plugin: nil """) yaml_data = yaml.safe_load(content) project = Project.unmarshal(yaml_data) # Test without icon raises error with pytest.raises(errors.SnapcraftError) as raised: run_project_checks(project, assets_dir=Path("snap")) assert str(raised.value) == "Specified icon 'foo.png' does not exist." # Test with icon passes. (new_dir / "foo.png").touch() run_project_checks(project, assets_dir=Path("snap"))
def test_lifecycle_debug_shell(snapcraft_yaml, cmd, new_dir, mocker): """Adoptable fields shouldn't be empty after adoption.""" mocker.patch("craft_parts.executor.Executor.execute", side_effect=Exception) mock_shell = mocker.patch("subprocess.run") project = Project.unmarshal(snapcraft_yaml(base="core22")) with pytest.raises(errors.PartsLifecycleError): parts_lifecycle._run_command( cmd, project=project, parse_info={}, assets_dir=Path(), start_time=datetime.now(), parallel_build_count=8, parsed_args=argparse.Namespace( directory=None, output=None, debug=True, destructive_mode=True, shell=False, shell_after=False, use_lxd=False, parts=["part1"], ), ) assert mock_shell.mock_calls == [call(["bash"], check=False, cwd=None)]
def test_lifecycle_run_command_clean(snapcraft_yaml, project_vars, new_dir, mocker): """Clean provider project when called without parts.""" project = Project.unmarshal(snapcraft_yaml(base="core22")) clean_mock = mocker.patch( "snapcraft.providers.LXDProvider.clean_project_environments", return_value=["instance-name"], ) parts_lifecycle._run_command( "clean", project=project, parse_info={}, assets_dir=Path(), start_time=datetime.now(), parallel_build_count=8, parsed_args=argparse.Namespace( directory=None, output=None, destructive_mode=False, use_lxd=False, parts=None, ), ) assert clean_mock.mock_calls == [ call( project_name="mytest", project_path=new_dir, build_on=get_host_architecture(), build_for=get_host_architecture(), ) ]
def test_lifecycle_clean_managed(snapcraft_yaml, project_vars, new_dir, mocker): project = Project.unmarshal(snapcraft_yaml(base="core22")) run_in_provider_mock = mocker.patch( "snapcraft.parts.lifecycle._run_in_provider") clean_mock = mocker.patch("snapcraft.parts.PartsLifecycle.clean") mocker.patch("snapcraft.utils.is_managed_mode", return_value=True) mocker.patch( "snapcraft.utils.get_managed_environment_home_path", return_value=new_dir / "home", ) parts_lifecycle._run_command( "clean", project=project, parse_info={}, assets_dir=Path(), start_time=datetime.now(), parallel_build_count=8, parsed_args=argparse.Namespace( directory=None, output=None, destructive_mode=False, use_lxd=False, parts=["part1"], ), ) assert run_in_provider_mock.mock_calls == [] assert clean_mock.mock_calls == [call(part_names=["part1"])]
def test_accepted_artifacts(new_dir, emitter, snapcraft_yaml): project = Project.unmarshal(snapcraft_yaml) assets_dir = Path("snap") file_assets = [ ".snapcraft/state", "gui/icon.png", "gui/other-icon.png", "plugins/plugin1.py", "plugins/data-file", "hooks/configure", "hooks/new-hook", "keys/key1.asc", "keys/key2.asc", "local/file", "local/dir/file", ] for file_asset in file_assets: asset_path = assets_dir / file_asset asset_path.parent.mkdir(parents=True, exist_ok=True) asset_path.touch() run_project_checks(project, assets_dir=Path("snap")) assert emitter.interactions == []
def test_unexpected_things(new_dir, emitter, snapcraft_yaml): project = Project.unmarshal(snapcraft_yaml) assets_dir = Path("snap") file_assets = [ "dir1/foo", "dir1/keys/key1.asc", "dir1/keys/key2.asc", "dir1/local/dir/file", "dir1/local/file", "dir1/plugins/data-file", "dir1/plugins/plugin1.py", "dir2/foo", "dir2/hooks/configure", "dir2/hooks/new-hook", "gui/icon.jpg", ] for file_asset in file_assets: asset_path = assets_dir / file_asset asset_path.parent.mkdir(parents=True, exist_ok=True) asset_path.touch() run_project_checks(project, assets_dir=Path("snap")) assert emitter.assert_progress( "The 'snap' directory is meant specifically for snapcraft, but it contains\n" "the following non-snapcraft-related paths:\n" "- dir1\n" "- dir1/foo\n" "- dir1/keys\n" "- dir1/keys/key1.asc\n" "- dir1/keys/key2.asc\n" "- dir1/local\n" "- dir1/local/dir\n" "- dir1/local/dir/file\n" "- dir1/local/file\n" "- dir1/plugins\n" "- dir1/plugins/data-file\n" "- dir1/plugins/plugin1.py\n" "- dir2\n" "- dir2/foo\n" "- dir2/hooks\n" "- dir2/hooks/configure\n" "- dir2/hooks/new-hook\n" "- gui/icon.jpg\n" "\n" "This is unsupported and may cause unexpected behavior. If you must store\n" "these files within the 'snap' directory, move them to 'snap/local'\n" "which is ignored by snapcraft.", permanent=True, )
def test_lifecycle_pack_managed(cmd, snapcraft_yaml, project_vars, new_dir, mocker): project = Project.unmarshal(snapcraft_yaml(base="core22")) run_in_provider_mock = mocker.patch( "snapcraft.parts.lifecycle._run_in_provider") run_mock = mocker.patch("snapcraft.parts.PartsLifecycle.run") pack_mock = mocker.patch("snapcraft.pack.pack_snap") mocker.patch("snapcraft.meta.snap_yaml.write") mocker.patch("snapcraft.utils.is_managed_mode", return_value=True) mocker.patch( "snapcraft.utils.get_managed_environment_home_path", return_value=new_dir / "home", ) parts_lifecycle._run_command( cmd, project=project, parse_info={}, assets_dir=Path(), start_time=datetime.now(), parallel_build_count=8, parsed_args=argparse.Namespace( directory=None, output=None, debug=False, bind_ssh=False, enable_manifest=False, manifest_image_information=None, destructive_mode=False, shell=False, shell_after=False, use_lxd=False, parts=[], ), ) assert run_in_provider_mock.mock_calls == [] assert run_mock.mock_calls == [ call("prime", debug=False, shell=False, shell_after=False) ] assert pack_mock.mock_calls[:1] == [ call( new_dir / "home/prime", output=None, compression="xz", name="mytest", version="0.1", target_arch=get_host_architecture(), ) ]
def test_setup_assets_remote_icon(self, desktop_file, yaml_data, new_dir): # create primed tree (no icon) desktop_file("prime/test.desktop") # define project # pylint: disable=line-too-long project = Project.unmarshal( yaml_data( { "adopt-info": "part", "icon": "https://dashboard.snapcraft.io/site_media/appmedia/2018/04/Snapcraft-logo-bird.png", "apps": { "app1": { "command": "test.sh", "common-id": "my-test", "desktop": "test.desktop", }, }, }, )) # pylint: enable=line-too-long setup_assets( project, assets_dir=Path("snap"), project_dir=Path.cwd(), prime_dir=Path("prime"), ) # desktop file should be in meta/gui and named after app desktop_path = Path("prime/meta/gui/app1.desktop") assert desktop_path.is_file() # desktop file content should make icon relative to ${SNAP} content = desktop_path.read_text() assert content == textwrap.dedent("""\ [Desktop Entry] Name=appstream-desktop Exec=test-project.app1 Type=Application Icon=${SNAP}/meta/gui/icon.png """) # icon was downloaded icon_path = Path("prime/meta/gui/icon.png") assert icon_path.is_file() assert icon_path.stat().st_size > 0
def run(command_name: str, parsed_args: "argparse.Namespace") -> None: """Run the parts lifecycle. :raises SnapcraftError: if the step name is invalid, or the project yaml file cannot be loaded. :raises LegacyFallback: if the project's base is not core22. """ emit.debug(f"command: {command_name}, arguments: {parsed_args}") snap_project = get_snap_project() yaml_data = process_yaml(snap_project.project_file) start_time = datetime.now() build_plan = get_build_plan(yaml_data, parsed_args) if parsed_args.provider: raise errors.SnapcraftError("Option --provider is not supported.") # Register our own plugins and callbacks plugins.register() callbacks.register_prologue(_set_global_environment) callbacks.register_pre_step(_set_step_environment) build_count = utils.get_parallel_build_count() for build_on, build_for in build_plan: emit.verbose(f"Running on {build_on} for {build_for}") yaml_data_for_arch = apply_yaml(yaml_data, build_on, build_for) parse_info = _extract_parse_info(yaml_data_for_arch) _expand_environment( yaml_data_for_arch, parallel_build_count=build_count, target_arch=build_for, ) project = Project.unmarshal(yaml_data_for_arch) try: _run_command( command_name, project=project, parse_info=parse_info, parallel_build_count=build_count, assets_dir=snap_project.assets_dir, start_time=start_time, parsed_args=parsed_args, ) except PermissionError as err: raise errors.FilePermissionError(err.filename, reason=err.strerror)
def test_snapcraft_yaml_load(new_dir, snapcraft_yaml, filename, mocker): """Snapcraft.yaml should be parsed as a valid yaml file.""" yaml_data = snapcraft_yaml(base="core22", filename=filename) run_command_mock = mocker.patch("snapcraft.parts.lifecycle._run_command") mocker.patch("snapcraft.utils.get_parallel_build_count", return_value=5) parts_lifecycle.run( "pull", argparse.Namespace( parts=["part1"], destructive_mode=True, use_lxd=False, provider=None, enable_manifest=False, manifest_image_information=None, bind_ssh=False, build_for=None, ), ) project = Project.unmarshal(yaml_data) if filename == "build-aux/snap/snapcraft.yaml": assets_dir = Path("build-aux/snap") else: assets_dir = Path("snap") assert run_command_mock.mock_calls == [ call( "pull", project=project, parse_info={}, assets_dir=assets_dir, parallel_build_count=5, start_time=mocker.ANY, parsed_args=argparse.Namespace( parts=["part1"], destructive_mode=True, use_lxd=False, provider=None, enable_manifest=False, manifest_image_information=None, bind_ssh=False, build_for=None, ), ), ]
def test_lifecycle_metadata_empty(field, snapcraft_yaml, new_dir): """Adoptable fields shouldn't be empty after adoption.""" yaml_data = snapcraft_yaml(base="core22") yaml_data.pop(field) yaml_data["adopt-info"] = "part" project = Project.unmarshal(yaml_data) with pytest.raises(errors.SnapcraftError) as raised: update_project_metadata( project, project_vars={"version": "", "grade": ""}, metadata_list=[], assets_dir=new_dir, prime_dir=new_dir, ) assert str(raised.value) == f"Field {field!r} was not adopted from metadata"
def test_lifecycle_pack_metadata_error(cmd, snapcraft_yaml, new_dir, mocker): project = Project.unmarshal(snapcraft_yaml(base="core22")) run_mock = mocker.patch("snapcraft.parts.PartsLifecycle.run") mocker.patch("snapcraft.utils.is_managed_mode", return_value=True) mocker.patch( "snapcraft.utils.get_managed_environment_home_path", return_value=new_dir / "home", ) mocker.patch( "snapcraft.parts.PartsLifecycle.project_vars", new_callable=PropertyMock, return_value={ "version": "0.1", "grade": "invalid" }, # invalid value ) pack_mock = mocker.patch("snapcraft.pack.pack_snap") mocker.patch("snapcraft.meta.snap_yaml.write") with pytest.raises(errors.SnapcraftError) as raised: parts_lifecycle._run_command( cmd, project=project, assets_dir=Path(), start_time=datetime.now(), parse_info={}, parallel_build_count=8, parsed_args=argparse.Namespace( directory=None, output=None, debug=False, destructive_mode=False, shell=False, shell_after=False, use_lxd=False, parts=[], ), ) assert str(raised.value) == ( "error setting grade: unexpected value; permitted: 'stable', 'devel'") assert run_mock.mock_calls == [ call("prime", debug=False, shell=False, shell_after=False) ] assert pack_mock.mock_calls == []
def test_setup_assets_no_apps(self, desktop_file, yaml_data, new_dir): desktop_file("prime/test.desktop") Path("prime/usr/share/icons").mkdir(parents=True) Path("prime/usr/share/icons/icon.svg").touch() Path("snap/gui").mkdir() # define project project = Project.unmarshal(yaml_data({"adopt-info": "part"})) # setting up assets does not crash setup_assets( project, assets_dir=Path("snap"), project_dir=Path.cwd(), prime_dir=Path("prime"), ) assert os.listdir("prime/meta/gui") == []
def test_gadget_missing(yaml_data, new_dir): project = Project.unmarshal( yaml_data({ "type": "gadget", "version": "1.0", "summary": "summary", "description": "description", })) with pytest.raises(errors.SnapcraftError) as raised: setup_assets( project, assets_dir=Path("snap"), project_dir=Path.cwd(), prime_dir=Path("prime"), ) assert str(raised.value) == "gadget.yaml is required for gadget snaps"
def test_lifecycle_adopt_project_vars(snapcraft_yaml, new_dir): """Adoptable fields shouldn't be empty after adoption.""" yaml_data = snapcraft_yaml(base="core22") yaml_data.pop("version") yaml_data.pop("grade") yaml_data["adopt-info"] = "part" project = Project.unmarshal(yaml_data) update_project_metadata( project, project_vars={"version": "42", "grade": "devel"}, metadata_list=[], assets_dir=new_dir, prime_dir=new_dir, ) assert project.version == "42" assert project.grade == "devel"
def test_setup_assets_icon_in_assets_dir(self, desktop_file, yaml_data, new_dir): desktop_file("prime/test.desktop") Path("snap/gui").mkdir(parents=True) Path("snap/gui/icon.svg").touch() # define project project = Project.unmarshal( yaml_data( { "adopt-info": "part", "apps": { "app1": { "command": "test.sh", "common-id": "my-test", "desktop": "test.desktop", }, }, }, )) setup_assets( project, assets_dir=Path("snap"), project_dir=Path.cwd(), prime_dir=Path("prime"), ) # desktop file should be in meta/gui and named after app desktop_path = Path("prime/meta/gui/app1.desktop") assert desktop_path.is_file() # desktop file content should make icon relative to ${SNAP} content = desktop_path.read_text() assert content == textwrap.dedent("""\ [Desktop Entry] Name=appstream-desktop Exec=test-project.app1 Type=Application Icon=${SNAP}/snap/gui/icon.svg """) # icon file exists Path("prime/snap/gui/icon.svg").is_file()
def test_gadget(yaml_data, gadget_yaml_file, new_dir): project = Project.unmarshal( yaml_data({ "type": "gadget", "version": "1.0", "summary": "summary", "description": "description", })) setup_assets( project, assets_dir=Path("snap"), project_dir=Path.cwd(), prime_dir=Path("prime"), ) # gadget file should be in meta/ gadget_path = Path("prime/meta/gadget.yaml") assert gadget_path.is_file()
def test_update_project_metadata(project_yaml_data, appstream_file, new_dir): project = Project.unmarshal(project_yaml_data({"adopt-info": "part"})) metadata = ExtractedMetadata( common_id="common.id", title="title", summary="summary", description="description", version="1.2.3", icon="assets/icon.png", desktop_file_paths=["assets/file.desktop"], ) assets_dir = Path("assets") prime_dir = Path("prime") # set up project apps project.apps = { "app1": _project_app({"command": "bin/app1"}), "app2": _project_app({"command": "bin/app2", "common_id": "other.id"}), "app3": _project_app({"command": "bin/app3", "common_id": "common.id"}), } prime_dir.mkdir() (prime_dir / "assets").mkdir() (prime_dir / "assets/icon.png").touch() (prime_dir / "assets/file.desktop").touch() prj_vars = {"version": "0.1", "grade": "stable"} update_project_metadata( project, project_vars=prj_vars, metadata_list=[metadata], assets_dir=assets_dir, prime_dir=prime_dir, ) assert project.title == "title" assert project.summary == "summary" # already set in project assert project.description == "description" # already set in project assert project.version == "0.1" # already set in project assert project.icon == "assets/icon.png" assert project.apps["app3"].desktop == "assets/file.desktop"
def test_lifecycle_shell_after(snapcraft_yaml, cmd, new_dir, mocker): """Adoptable fields shouldn't be empty after adoption.""" last_step = None def _fake_execute(_, action: Action, **kwargs): # pylint: disable=unused-argument nonlocal last_step last_step = action.step mocker.patch("craft_parts.executor.Executor.execute", new=_fake_execute) mock_shell = mocker.patch("subprocess.run") project = Project.unmarshal(snapcraft_yaml(base="core22")) parts_lifecycle._run_command( cmd, project=project, parse_info={}, assets_dir=Path(), start_time=datetime.now(), parallel_build_count=8, parsed_args=argparse.Namespace( directory=None, output=None, debug=False, destructive_mode=True, shell=False, shell_after=True, use_lxd=False, parts=["part1"], ), ) expected_last_step = Step.PULL if cmd == "build": expected_last_step = Step.BUILD if cmd == "stage": expected_last_step = Step.STAGE if cmd == "prime": expected_last_step = Step.PRIME assert last_step == expected_last_step assert mock_shell.mock_calls == [call(["bash"], check=False, cwd=None)]
def _simple_project(**kwargs): snapcraft_config = { "name": "mytest", "version": "1.29.3", "base": "core22", "summary": "Single-line elevator pitch for your amazing snap", "description": "test-description", "confinement": "strict", "parts": { "part1": { "plugin": "nil", }, }, "apps": { "app1": { "command": "bin/mytest", }, }, **kwargs, } return Project.unmarshal(snapcraft_config)
def test_kernel_missing(yaml_data, new_dir): project = Project.unmarshal({ "name": "custom-kernel", "type": "kernel", "confinement": "strict", "version": "1.0", "summary": "summary", "description": "description", "parts": {}, }) setup_assets( project, assets_dir=Path("snap"), project_dir=Path.cwd(), prime_dir=Path("prime"), ) # kernel file should not be in meta/ kernel_path = Path("prime/meta/kernel.yaml") assert not kernel_path.is_file()
def test_lifecycle_run_command_pack(cmd, snapcraft_yaml, project_vars, new_dir, mocker): project = Project.unmarshal(snapcraft_yaml(base="core22")) run_mock = mocker.patch("snapcraft.parts.PartsLifecycle.run") pack_mock = mocker.patch("snapcraft.pack.pack_snap") parts_lifecycle._run_command( cmd, project=project, parse_info={}, assets_dir=Path(), start_time=datetime.now(), parallel_build_count=8, parsed_args=argparse.Namespace( directory=None, output=None, debug=False, destructive_mode=True, enable_manifest=False, shell=False, shell_after=False, use_lxd=False, parts=[], ), ) assert run_mock.mock_calls == [ call("prime", debug=False, shell=False, shell_after=False) ] assert pack_mock.mock_calls[:1] == [ call( new_dir / "prime", output=None, compression="xz", name="mytest", version="0.1", target_arch=get_host_architecture(), ) ]
def test_lifecycle_clean_destructive_mode(snapcraft_yaml, project_vars, new_dir, mocker): """Clean local project if called in destructive mode.""" project = Project.unmarshal(snapcraft_yaml(base="core22")) clean_mock = mocker.patch("snapcraft.parts.PartsLifecycle.clean") parts_lifecycle._run_command( "clean", project=project, parse_info={}, assets_dir=Path(), start_time=datetime.now(), parallel_build_count=8, parsed_args=argparse.Namespace( directory=None, output=None, destructive_mode=True, use_lxd=False, parts=None, ), ) assert clean_mock.mock_calls == [call(part_names=None)]
def test_lifecycle_run_command_step( cmd, step, debug_shell, snapcraft_yaml, project_vars, new_dir, mocker ): project = Project.unmarshal(snapcraft_yaml(base="core22")) run_mock = mocker.patch("snapcraft.parts.PartsLifecycle.run") mocker.patch("snapcraft.meta.snap_yaml.write") pack_mock = mocker.patch("snapcraft.pack.pack_snap") parsed_args = argparse.Namespace( debug=False, destructive_mode=True, enable_manifest=False, shell=False, shell_after=False, use_lxd=False, parts=[], ) if debug_shell: setattr(parsed_args, debug_shell, True) parts_lifecycle._run_command( cmd, project=project, parse_info={}, assets_dir=Path(), start_time=datetime.now(), parallel_build_count=8, parsed_args=parsed_args, ) call_args = {"debug": False, "shell": False, "shell_after": False} if debug_shell: call_args[debug_shell] = True assert run_mock.mock_calls == [call(step, **call_args)] assert pack_mock.mock_calls == []
def test_lifecycle_pack_not_managed(cmd, snapcraft_yaml, new_dir, mocker): project = Project.unmarshal(snapcraft_yaml(base="core22")) run_in_provider_mock = mocker.patch( "snapcraft.parts.lifecycle._run_in_provider") run_mock = mocker.patch("snapcraft.parts.PartsLifecycle.run") mocker.patch("snapcraft.utils.is_managed_mode", return_value=False) parts_lifecycle._run_command( cmd, project=project, parse_info={}, assets_dir=Path(), start_time=datetime.now(), parallel_build_count=8, parsed_args=argparse.Namespace( directory=None, output=None, destructive_mode=False, use_lxd=False, parts=[], ), ) assert run_mock.mock_calls == [] assert run_in_provider_mock.mock_calls == [ call( project, cmd, argparse.Namespace( directory=None, output=None, destructive_mode=False, use_lxd=False, parts=[], ), ) ]
def test_setup_assets_hook_command_chain_error(self, yaml_data, new_dir): # define project project = Project.unmarshal( yaml_data( { "adopt-info": "part1", "hooks": { "hook1": { "command-chain": ["does-not-exist"] }, }, }, )) with pytest.raises(errors.SnapcraftError) as raised: setup_assets( project, assets_dir=Path("snap"), project_dir=Path.cwd(), prime_dir=new_dir, ) assert str(raised.value) == ( "Failed to generate snap metadata: The command-chain item 'does-not-exist' " "defined in hook 'hook1' does not exist or is not executable.")
def complex_project(): snapcraft_yaml = textwrap.dedent("""\ name: mytest version: 1.29.3 base: core22 type: app summary: Single-line elevator pitch for your amazing snap description: | This is my-snap's description. You have a paragraph or two to tell the most important story about your snap. Keep it under 100 words though, we live in tweetspace and your description wants to look good in the snap store. grade: devel confinement: strict environment: GLOBAL_VARIABLE: test-global-variable parts: part1: plugin: nil apps: app1: command: bin/mytest autostart: test-app.desktop common-id: test-common-id bus-name: test-bus-name completer: test-completer stop-command: test-stop-command post-stop-command: test-post-stop-command start-timeout: 1s stop-timeout: 2s watchdog-timeout: 3s reload-command: test-reload-command restart-delay: 4s timer: test-timer daemon: simple after: [test-after-1, test-after-2] before: [test-before-1, test-before-2] refresh-mode: endure stop-mode: sigterm restart-condition: on-success install-mode: enable aliases: [test-alias-1, test-alias-2] environment: APP_VARIABLE: test-app-variable command-chain: - cc-test1 - cc-test2 sockets: test-socket-1: listen-stream: /tmp/test-socket.sock socket-mode: 0 test-socket-2: listen-stream: 100 socket-mode: 1 plugs: empty-plug: string-plug: home dict-plug: string-parameter: foo bool-parameter: True content-interface: interface: content target: test-target content: test-content default-provider: test-provider slots: empty-slot: string-slot: slot dict-slot: string-parameter: foo bool-parameter: True content-interface: interface: content read: - / hooks: configure: command-chain: ["test"] environment: test-variable-1: "test" test-variable-2: "test" plugs: - home - network install: environment: environment-var-1: "test" system-usernames: snap_daemon: scope: shared snap_microk8s: shared layout: /usr/share/libdrm: bind: $SNAP/gnome-platform/usr/share/libdrm /usr/lib/x86_64-linux-gnu/webkit2gtk-4.0: bind: $SNAP/gnome-platform/usr/lib/x86_64-linux-gnu/webkit2gtk-4.0 /usr/share/xml/iso-codes: bind: $SNAP/gnome-platform/usr/share/xml/iso-codes """) data = yaml.safe_load(snapcraft_yaml) yield Project.unmarshal(data)