From bb37b67166a8c80efca92e608f397e4cd820eb5e Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Tue, 8 Jun 2021 08:46:20 +0200 Subject: [PATCH] flatpak: add tests in CI, add no_dependencies parameter (#2751) * Similar version restrictions than flatpak_remote tests. * ... * Try to work around missing dependencies. * Revert "Try to work around missing dependencies." This reverts commit 66a4e385668d0212e1150dcfb743478cf5aa042e. * Add changelog. * App8 -> App2; make sure that there are two apps App1 and App2. * Fix forgotten variabe. * Remove test notices. * Seems like flatpak no longer supports file:// URLs. The tests would need to be rewritten to offer the URL via http:// instead. * Try local HTTP server for URL tests. * ... * Lint, add status check. * Add boilerplate. * Add 'ps aux'. * Surrender to -f. * Work around apparent flatpak bug. * Fix YAML. * Improve condition. * Make sure test reruns behave better. --- .../2751-flatpak-no_dependencies.yml | 2 + plugins/modules/packaging/os/flatpak.py | 55 ++++++------- .../modules/packaging/os/flatpak_remote.py | 21 ----- tests/integration/targets/flatpak/aliases | 3 +- .../targets/flatpak/files/serve.py | 65 +++++++++++++++ .../integration/targets/flatpak/meta/main.yml | 1 + .../targets/flatpak/tasks/check_mode.yml | 39 +++++---- .../targets/flatpak/tasks/main.yml | 21 ++++- .../targets/flatpak/tasks/setup.yml | 44 +++++++--- .../targets/flatpak/tasks/test.yml | 76 ++++++++++++------ .../setup_flatpak_remote/create-repo.sh | 68 +++++++++------- .../setup_flatpak_remote/files/repo.tar.xz | Bin 5524 -> 6436 bytes 12 files changed, 255 insertions(+), 140 deletions(-) create mode 100644 changelogs/fragments/2751-flatpak-no_dependencies.yml create mode 100644 tests/integration/targets/flatpak/files/serve.py diff --git a/changelogs/fragments/2751-flatpak-no_dependencies.yml b/changelogs/fragments/2751-flatpak-no_dependencies.yml new file mode 100644 index 0000000000..a07ead96da --- /dev/null +++ b/changelogs/fragments/2751-flatpak-no_dependencies.yml @@ -0,0 +1,2 @@ +minor_changes: +- "flatpak - add ``no_dependencies`` parameter (https://github.com/ansible/ansible/pull/55452, https://github.com/ansible-collections/community.general/pull/2751)." diff --git a/plugins/modules/packaging/os/flatpak.py b/plugins/modules/packaging/os/flatpak.py index 1be1a72243..4a9e214fde 100644 --- a/plugins/modules/packaging/os/flatpak.py +++ b/plugins/modules/packaging/os/flatpak.py @@ -6,27 +6,6 @@ # Copyright: (c) 2017 Ansible Project # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -# ATTENTION CONTRIBUTORS! -# -# TL;DR: Run this module's integration tests manually before opening a pull request -# -# Long explanation: -# The integration tests for this module are currently NOT run on the Ansible project's continuous -# delivery pipeline. So please: When you make changes to this module, make sure that you run the -# included integration tests manually for both Python 2 and Python 3: -# -# Python 2: -# ansible-test integration -v --docker fedora28 --docker-privileged --allow-unsupported --python 2.7 flatpak -# Python 3: -# ansible-test integration -v --docker fedora28 --docker-privileged --allow-unsupported --python 3.6 flatpak -# -# Because of external dependencies, the current integration tests are somewhat too slow and brittle -# to be included right now. I have plans to rewrite the integration tests based on a local flatpak -# repository so that they can be included into the normal CI pipeline. -# //oolongbrothers - - from __future__ import (absolute_import, division, print_function) __metaclass__ = type @@ -60,18 +39,28 @@ options: name: description: - The name of the flatpak to manage. - - When used with I(state=present), I(name) can be specified as an C(http(s)) URL to a + - When used with I(state=present), I(name) can be specified as a URL to a C(flatpakref) file or the unique reverse DNS name that identifies a flatpak. + - Both C(https://) and C(http://) URLs are supported. - When supplying a reverse DNS name, you can use the I(remote) option to specify on what remote to look for the flatpak. An example for a reverse DNS name is C(org.gnome.gedit). - When used with I(state=absent), it is recommended to specify the name in the reverse DNS format. - - When supplying an C(http(s)) URL with I(state=absent), the module will try to match the + - When supplying a URL with I(state=absent), the module will try to match the installed flatpak based on the name of the flatpakref to remove it. However, there is no guarantee that the names of the flatpakref file and the reverse DNS name of the installed flatpak do match. type: str required: true + no_dependencies: + description: + - If installing runtime dependencies should be omitted or not + - This parameter is primarily implemented for integration testing this module. + There might however be some use cases where you would want to have this, like when you are + packaging your own flatpaks. + type: bool + default: false + version_added: 3.2.0 remote: description: - The flatpak remote (repository) to install the flatpak from. @@ -94,10 +83,11 @@ EXAMPLES = r''' name: https://s3.amazonaws.com/alexlarsson/spotify-repo/spotify.flatpakref state: present -- name: Install the gedit flatpak package +- name: Install the gedit flatpak package without dependencies (not recommended) community.general.flatpak: name: https://git.gnome.org/browse/gnome-apps-nightly/plain/gedit.flatpakref state: present + no_dependencies: true - name: Install the gedit package from flathub for current user community.general.flatpak: @@ -153,18 +143,21 @@ from ansible.module_utils.basic import AnsibleModule OUTDATED_FLATPAK_VERSION_ERROR_MESSAGE = "Unknown option --columns=application" -def install_flat(module, binary, remote, name, method): +def install_flat(module, binary, remote, name, method, no_dependencies): """Add a new flatpak.""" global result flatpak_version = _flatpak_version(module, binary) + command = [binary, "install", "--{0}".format(method)] if StrictVersion(flatpak_version) < StrictVersion('1.1.3'): - noninteractive_arg = "-y" + command += ["-y"] else: - noninteractive_arg = "--noninteractive" + command += ["--noninteractive"] + if no_dependencies: + command += ["--no-deps"] if name.startswith('http://') or name.startswith('https://'): - command = [binary, "install", "--{0}".format(method), noninteractive_arg, name] + command += [name] else: - command = [binary, "install", "--{0}".format(method), noninteractive_arg, remote, name] + command += [remote, name] _flatpak_command(module, module.check_mode, command) result['changed'] = True @@ -279,6 +272,7 @@ def main(): choices=['user', 'system']), state=dict(type='str', default='present', choices=['absent', 'present']), + no_dependencies=dict(type='bool', default=False), executable=dict(type='path', default='flatpak') ), supports_check_mode=True, @@ -287,6 +281,7 @@ def main(): name = module.params['name'] state = module.params['state'] remote = module.params['remote'] + no_dependencies = module.params['no_dependencies'] method = module.params['method'] executable = module.params['executable'] binary = module.get_bin_path(executable, None) @@ -301,7 +296,7 @@ def main(): module.fail_json(msg="Executable '%s' was not found on the system." % executable, **result) if state == 'present' and not flatpak_exists(module, binary, name, method): - install_flat(module, binary, remote, name, method) + install_flat(module, binary, remote, name, method, no_dependencies) elif state == 'absent' and flatpak_exists(module, binary, name, method): uninstall_flat(module, binary, name, method) diff --git a/plugins/modules/packaging/os/flatpak_remote.py b/plugins/modules/packaging/os/flatpak_remote.py index dbb211c2fb..a7767621d7 100644 --- a/plugins/modules/packaging/os/flatpak_remote.py +++ b/plugins/modules/packaging/os/flatpak_remote.py @@ -6,27 +6,6 @@ # Copyright: (c) 2017 Ansible Project # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -# ATTENTION CONTRIBUTORS! -# -# TL;DR: Run this module's integration tests manually before opening a pull request -# -# Long explanation: -# The integration tests for this module are currently NOT run on the Ansible project's continuous -# delivery pipeline. So please: When you make changes to this module, make sure that you run the -# included integration tests manually for both Python 2 and Python 3: -# -# Python 2: -# ansible-test integration -v --docker fedora28 --docker-privileged --allow-unsupported --python 2.7 flatpak_remote -# Python 3: -# ansible-test integration -v --docker fedora28 --docker-privileged --allow-unsupported --python 3.6 flatpak_remote -# -# Because of external dependencies, the current integration tests are somewhat too slow and brittle -# to be included right now. I have plans to rewrite the integration tests based on a local flatpak -# repository so that they can be included into the normal CI pipeline. -# //oolongbrothers - - from __future__ import (absolute_import, division, print_function) __metaclass__ = type diff --git a/tests/integration/targets/flatpak/aliases b/tests/integration/targets/flatpak/aliases index 59e306f8b4..39291d435b 100644 --- a/tests/integration/targets/flatpak/aliases +++ b/tests/integration/targets/flatpak/aliases @@ -1,4 +1,4 @@ -unsupported +shippable/posix/group3 destructive skip/aix skip/freebsd @@ -6,4 +6,3 @@ skip/osx skip/macos skip/rhel needs/root -needs/privileged diff --git a/tests/integration/targets/flatpak/files/serve.py b/tests/integration/targets/flatpak/files/serve.py new file mode 100644 index 0000000000..d9ca2d17a5 --- /dev/null +++ b/tests/integration/targets/flatpak/files/serve.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import os +import posixpath +import sys + +try: + from http.server import SimpleHTTPRequestHandler, HTTPServer + from urllib.parse import unquote +except ImportError: + from SimpleHTTPServer import SimpleHTTPRequestHandler + from BaseHTTPServer import HTTPServer + from urllib import unquote + + +# Argument parsing +if len(sys.argv) != 4: + print('Syntax: {0} '.format(sys.argv[0])) + sys.exit(-1) + +HOST, PORT, PATH = sys.argv[1:4] +PORT = int(PORT) + + +# The HTTP request handler +class Handler(SimpleHTTPRequestHandler): + def translate_path(self, path): + # Modified from Python 3.6's version of SimpleHTTPRequestHandler + # to support using another base directory than CWD. + + # abandon query parameters + path = path.split('?', 1)[0] + path = path.split('#', 1)[0] + # Don't forget explicit trailing slash when normalizing. Issue17324 + trailing_slash = path.rstrip().endswith('/') + try: + path = unquote(path, errors='surrogatepass') + except (UnicodeDecodeError, TypeError) as exc: + path = unquote(path) + path = posixpath.normpath(path) + words = path.split('/') + words = filter(None, words) + path = PATH + for word in words: + if os.path.dirname(word) or word in (os.curdir, os.pardir): + # Ignore components that are not a simple file/directory name + continue + path = os.path.join(path, word) + if trailing_slash: + path += '/' + return path + + +# Run simple HTTP server +httpd = HTTPServer((HOST, PORT), Handler) + +try: + httpd.serve_forever() +except KeyboardInterrupt: + pass + +httpd.server_close() diff --git a/tests/integration/targets/flatpak/meta/main.yml b/tests/integration/targets/flatpak/meta/main.yml index 07faa21776..314f77eba9 100644 --- a/tests/integration/targets/flatpak/meta/main.yml +++ b/tests/integration/targets/flatpak/meta/main.yml @@ -1,2 +1,3 @@ dependencies: - prepare_tests + - setup_flatpak_remote diff --git a/tests/integration/targets/flatpak/tasks/check_mode.yml b/tests/integration/targets/flatpak/tasks/check_mode.yml index 3186fd2830..2270e0a9be 100644 --- a/tests/integration/targets/flatpak/tasks/check_mode.yml +++ b/tests/integration/targets/flatpak/tasks/check_mode.yml @@ -4,8 +4,8 @@ - name: Test addition of absent flatpak (check mode) flatpak: - name: org.gnome.Characters - remote: flathub + name: com.dummy.App1 + remote: dummy-remote state: present register: addition_result check_mode: true @@ -18,8 +18,8 @@ - name: Test non-existent idempotency of addition of absent flatpak (check mode) flatpak: - name: org.gnome.Characters - remote: flathub + name: com.dummy.App1 + remote: dummy-remote state: present register: double_addition_result check_mode: true @@ -36,7 +36,7 @@ - name: Test removal of absent flatpak check mode flatpak: - name: org.gnome.Characters + name: com.dummy.App1 state: absent register: removal_result check_mode: true @@ -51,8 +51,8 @@ - name: Test addition of absent flatpak with url (check mode) flatpak: - name: https://flathub.org/repo/appstream/org.gnome.Characters.flatpakref - remote: flathub + name: http://127.0.0.1:8000/repo/com.dummy.App1.flatpakref + remote: dummy-remote state: present register: url_addition_result check_mode: true @@ -65,8 +65,8 @@ - name: Test non-existent idempotency of addition of absent flatpak with url (check mode) flatpak: - name: https://flathub.org/repo/appstream/org.gnome.Characters.flatpakref - remote: flathub + name: http://127.0.0.1:8000/repo/com.dummy.App1.flatpakref + remote: dummy-remote state: present register: double_url_addition_result check_mode: true @@ -85,7 +85,7 @@ - name: Test removal of absent flatpak with url not doing anything (check mode) flatpak: - name: https://flathub.org/repo/appstream/org.gnome.Characters.flatpakref + name: http://127.0.0.1:8000/repo/com.dummy.App1.flatpakref state: absent register: url_removal_result check_mode: true @@ -96,15 +96,14 @@ - url_removal_result is not changed msg: "Removing an absent flatpak shall mark module execution as not changed" - # - Tests with present flatpak ------------------------------------------------- # state=present on present flatpak - name: Test addition of present flatpak (check mode) flatpak: - name: org.gnome.Calculator - remote: flathub + name: com.dummy.App2 + remote: dummy-remote state: present register: addition_present_result check_mode: true @@ -119,7 +118,7 @@ - name: Test removal of present flatpak (check mode) flatpak: - name: org.gnome.Calculator + name: com.dummy.App2 state: absent register: removal_present_result check_mode: true @@ -132,7 +131,7 @@ - name: Test non-existent idempotency of removal (check mode) flatpak: - name: org.gnome.Calculator + name: com.dummy.App2 state: absent register: double_removal_present_result check_mode: true @@ -149,8 +148,8 @@ - name: Test addition with url of present flatpak (check mode) flatpak: - name: https://flathub.org/repo/appstream/org.gnome.Calculator.flatpakref - remote: flathub + name: http://127.0.0.1:8000/repo/com.dummy.App2.flatpakref + remote: dummy-remote state: present register: url_addition_present_result check_mode: true @@ -165,7 +164,7 @@ - name: Test removal with url of present flatpak (check mode) flatpak: - name: https://flathub.org/repo/appstream/org.gnome.Calculator.flatpakref + name: http://127.0.0.1:8000/repo/com.dummy.App2.flatpakref state: absent register: url_removal_present_result check_mode: true @@ -178,8 +177,8 @@ - name: Test non-existent idempotency of removal with url of present flatpak (check mode) flatpak: - name: https://flathub.org/repo/appstream/org.gnome.Calculator.flatpakref - remote: flathub + name: http://127.0.0.1:8000/repo/com.dummy.App2.flatpakref + remote: dummy-remote state: absent register: double_url_removal_present_result check_mode: true diff --git a/tests/integration/targets/flatpak/tasks/main.yml b/tests/integration/targets/flatpak/tasks/main.yml index a1d1bda8a4..68d41d2efe 100644 --- a/tests/integration/targets/flatpak/tasks/main.yml +++ b/tests/integration/targets/flatpak/tasks/main.yml @@ -30,8 +30,8 @@ - name: Test executable override flatpak: - name: org.gnome.Characters - remote: flathub + name: com.dummy.App1 + remote: dummy-remote state: present executable: nothing-that-exists ignore_errors: true @@ -57,5 +57,20 @@ vars: method: system + always: + + - name: Check HTTP server status + async_status: + jid: "{{ webserver_status.ansible_job_id }}" + ignore_errors: true + + - name: List processes + command: ps aux + + - name: Stop HTTP server + command: >- + pkill -f -- '{{ remote_tmp_dir }}/serve.py' + when: | - ansible_distribution in ('Fedora', 'Ubuntu') + ansible_distribution == 'Fedora' or + ansible_distribution == 'Ubuntu' and not ansible_distribution_major_version | int < 16 diff --git a/tests/integration/targets/flatpak/tasks/setup.yml b/tests/integration/targets/flatpak/tasks/setup.yml index 2dfa33a0b1..98b07cd480 100644 --- a/tests/integration/targets/flatpak/tasks/setup.yml +++ b/tests/integration/targets/flatpak/tasks/setup.yml @@ -4,32 +4,58 @@ state: present become: true when: ansible_distribution == 'Fedora' + - block: - name: Activate flatpak ppa on Ubuntu apt_repository: repo: ppa:alexlarsson/flatpak state: present mode: '0644' + when: ansible_lsb.major_release | int < 18 + - name: Install flatpak package on Ubuntu apt: name: flatpak state: present - become: true + when: ansible_distribution == 'Ubuntu' -- name: Enable flathub for user + +- name: Install dummy remote for user flatpak_remote: - name: flathub + name: dummy-remote state: present - flatpakrepo_url: https://dl.flathub.org/repo/flathub.flatpakrepo + flatpakrepo_url: /tmp/flatpak/repo/dummy-repo.flatpakrepo method: user -- name: Enable flathub for system + +- name: Install dummy remote for system flatpak_remote: - name: flathub + name: dummy-remote state: present - flatpakrepo_url: https://dl.flathub.org/repo/flathub.flatpakrepo + flatpakrepo_url: /tmp/flatpak/repo/dummy-repo.flatpakrepo method: system + +- name: Remove (if necessary) flatpak for testing check mode on absent flatpak + flatpak: + name: com.dummy.App1 + remote: dummy-remote + state: absent + no_dependencies: true + - name: Add flatpak for testing check mode on present flatpak flatpak: - name: org.gnome.Calculator - remote: flathub + name: com.dummy.App2 + remote: dummy-remote state: present + no_dependencies: true + +- name: Copy HTTP server + copy: + src: serve.py + dest: '{{ remote_tmp_dir }}/serve.py' + mode: '0755' + +- name: Start HTTP server + command: '{{ remote_tmp_dir }}/serve.py 127.0.0.1 8000 /tmp/flatpak/' + async: 120 + poll: 0 + register: webserver_status diff --git a/tests/integration/targets/flatpak/tasks/test.yml b/tests/integration/targets/flatpak/tasks/test.yml index 1e7d888bb5..7442e4b468 100644 --- a/tests/integration/targets/flatpak/tasks/test.yml +++ b/tests/integration/targets/flatpak/tasks/test.yml @@ -2,10 +2,11 @@ - name: Test addition - {{ method }} flatpak: - name: org.gnome.Characters - remote: flathub + name: com.dummy.App1 + remote: dummy-remote state: present method: "{{ method }}" + no_dependencies: true register: addition_result - name: Verify addition test result - {{ method }} @@ -16,10 +17,11 @@ - name: Test idempotency of addition - {{ method }} flatpak: - name: org.gnome.Characters - remote: flathub + name: com.dummy.App1 + remote: dummy-remote state: present method: "{{ method }}" + no_dependencies: true register: double_addition_result - name: Verify idempotency of addition test result - {{ method }} @@ -32,9 +34,10 @@ - name: Test removal - {{ method }} flatpak: - name: org.gnome.Characters + name: com.dummy.App1 state: absent method: "{{ method }}" + no_dependencies: true register: removal_result - name: Verify removal test result - {{ method }} @@ -45,9 +48,10 @@ - name: Test idempotency of removal - {{ method }} flatpak: - name: org.gnome.Characters + name: com.dummy.App1 state: absent method: "{{ method }}" + no_dependencies: true register: double_removal_result - name: Verify idempotency of removal test result - {{ method }} @@ -60,10 +64,11 @@ - name: Test addition with url - {{ method }} flatpak: - name: https://flathub.org/repo/appstream/org.gnome.Characters.flatpakref - remote: flathub + name: http://127.0.0.1:8000/repo/com.dummy.App1.flatpakref + remote: dummy-remote state: present method: "{{ method }}" + no_dependencies: true register: url_addition_result - name: Verify addition test result - {{ method }} @@ -74,10 +79,11 @@ - name: Test idempotency of addition with url - {{ method }} flatpak: - name: https://flathub.org/repo/appstream/org.gnome.Characters.flatpakref - remote: flathub + name: http://127.0.0.1:8000/repo/com.dummy.App1.flatpakref + remote: dummy-remote state: present method: "{{ method }}" + no_dependencies: true register: double_url_addition_result - name: Verify idempotency of addition with url test result - {{ method }} @@ -90,26 +96,46 @@ - name: Test removal with url - {{ method }} flatpak: - name: https://flathub.org/repo/appstream/org.gnome.Characters.flatpakref + name: http://127.0.0.1:8000/repo/com.dummy.App1.flatpakref state: absent method: "{{ method }}" + no_dependencies: true register: url_removal_result + ignore_errors: true -- name: Verify removal test result - {{ method }} +- name: Verify removal test result failed - {{ method }} + # It looks like flatpak has a bug when the hostname contains a port. If this is the case, it emits + # the following message, which we check for. If another error happens, we fail. + # Upstream issue: https://github.com/flatpak/flatpak/issues/4307 + # (The second message happens with Ubuntu 18.04.) assert: that: - - url_removal_result is changed - msg: "state=absent with url as name shall remove flatpak when present" + - >- + url_removal_result.msg in [ + "error: Invalid branch 127.0.0.1:8000: Branch can't contain :", + "error: Invalid id http:: Name can't contain :", + ] + when: url_removal_result is failed -- name: Test idempotency of removal with url - {{ method }} - flatpak: - name: https://flathub.org/repo/appstream/org.gnome.Characters.flatpakref - state: absent - method: "{{ method }}" - register: double_url_removal_result +- when: url_removal_result is not failed + block: -- name: Verify idempotency of removal with url test result - {{ method }} - assert: - that: - - double_url_removal_result is not changed - msg: "state=absent with url as name shall not do anything when flatpak is not present" + - name: Verify removal test result - {{ method }} + assert: + that: + - url_removal_result is changed + msg: "state=absent with url as name shall remove flatpak when present" + + - name: Test idempotency of removal with url - {{ method }} + flatpak: + name: http://127.0.0.1:8000/repo/com.dummy.App1.flatpakref + state: absent + method: "{{ method }}" + no_dependencies: true + register: double_url_removal_result + + - name: Verify idempotency of removal with url test result - {{ method }} + assert: + that: + - double_url_removal_result is not changed + msg: "state=absent with url as name shall not do anything when flatpak is not present" diff --git a/tests/integration/targets/setup_flatpak_remote/create-repo.sh b/tests/integration/targets/setup_flatpak_remote/create-repo.sh index 1b09bb7956..4ece76ccfc 100755 --- a/tests/integration/targets/setup_flatpak_remote/create-repo.sh +++ b/tests/integration/targets/setup_flatpak_remote/create-repo.sh @@ -1,51 +1,59 @@ #!/usr/bin/env bash set -eux -flatpak install -y --system flathub org.freedesktop.Platform//1.6 org.freedesktop.Sdk//1.6 - -echo $'#!/bin/sh\necho hello world' > hello.sh - -export NUM=1 -flatpak build-init appdir$NUM com.dummy.App$NUM org.freedesktop.Sdk org.freedesktop.Platform 1.6; -flatpak build appdir$NUM mkdir /app/bin; -flatpak build appdir$NUM install --mode=750 hello.sh /app/bin; -flatpak build-finish --command=hello.sh appdir$NUM - -flatpak build-export repo appdir$NUM stable +# Delete traces from last run +rm -rf appdir* dummy-repo.gpg gpg hello.sh repo +# Create GPG key mkdir -p gpg chmod 0700 gpg gpg --homedir gpg --batch --passphrase '' --quick-gen-key test@dummy.com future-default default 10y - KEY_ID=$(gpg --homedir=gpg --list-keys --with-colons test@dummy.com | grep fpr: | head -1 | cut -d ':' -f 10) - gpg --homedir=gpg --export "${KEY_ID}" > dummy-repo.gpg - BASE64_PUBLIC_KEY=$(base64 dummy-repo.gpg | tr -d '\n') -cat > repo/com.dummy.App1.flatpakref < hello.sh + +for NUM in 1 2; do + flatpak build-init appdir${NUM} com.dummy.App${NUM} org.freedesktop.Sdk org.freedesktop.Platform 1.6; + flatpak build appdir${NUM} mkdir /app/bin; + flatpak build appdir${NUM} install --mode=750 hello.sh /app/bin; + flatpak build-finish --command=hello.sh appdir${NUM} + + flatpak build-export repo appdir${NUM} stable + + cat > repo/com.dummy.App${NUM}.flatpakref < repo/dummy-repo.flatpakrepo <v>5N@un}Hi1L#y~)Z- z{84!W3f)Esb7jq8xj(H~uV-ILw_hEK-tS2ml%=3W(9ZbK%*zGuyzw% zx|CRvE$)Z8hP(q}r5_|!pL6c&F%PC`(i85LvmX4tby$`<){6<0vLZ&gXzNHVZ&Xf~SL^u8sOHdo})6*qX{X~g@Rb%r$B zquEDB>&GQIE73m2JY`xc#+vdGzyT>afBQP{JpJd%k^#05fTyu|mQGF%YV6zMjsMHtkdFZgCC3Hu1hT#!3sP zlX%{M1-qoEJrl#7R+_{(He}QOx3|*uAuy=7BSbaUQ8?+uC3n_im4s73NG;MPAh=aV zsr!5fZ9@geCLM}Jn>G|r+q1urn&NgkY4kCkgSRk&Nj)-6mJ}r({(ADFv?Rj1Qi%V= z#?W3PY}yyhh)TAJTmea{WQz!$!kLBs#hrb!Oe&|_EO+32=p~I7Sqw@dyF6|0q@M8hmxVyd6gGOW|A*fgO zCjkQ-W<>L7srkC{ZDbazsy?xXWtl6Tn^-^s5r^=XB~c~PZoyai;ogn=Y?otN+g6?B zWT2F(AKl^h>@zuadxsdm1EXN(hxfSJYH8;e zz;C?|(!_t_gVJQO-YhxZ#DIbf^JItaMJEvG3=GNsV|Pda>WptsL5Ak0>x>89MY&QR zvxJw5)nrELLCY}g=4zkb23M+VcZJ$kRf>*B)7NH^5*~YC1&?h)!NDqJ#4h)y^7lSF zi0y|>Mb(4Bi+hVEtJNPzDGi5ePhs41$l@VJ6rX;uYD1>|^@C@*E|^CE22`*L6)9O( z?oddJ%D4`7)w*+JE5dme9)yRz6QUBv7seZjaaPl+=5^@LXrU2)SW!puqXN*k~< zxJ=^x0|xbva8VGz2sP|0OQ49+NDlq!uPRo^kf`w}9bO}}YYnnZfrnFS6OoqH0DR0Z z)13cHH?2TM5eIPv98c!~DoZUl=tf4~Ez+DZpFz9!hD8{t@ygq-u-`2F@8>dIx~3Pp z_~>w^QGN4mH_|yzkiIl29x4}JQ`?SIiDy8OIc$Rthg#B&sK;_kF(Hm(+xpjo$9{3} zf@XWNwLx*Vd_dj*OANsrRiQ-b!<{tKhioy&{7kMaBAL9ZYqlDm`$jG_&t~4py9Ya*Ge+y~Pv>HtJcFY8 zihplQ2s3yH>rf+Gfp*T`P}GI@BX&K=2Culc>vvXu$qsx^^i!;(p07mgthO5Vt3XwW z-S*i?CrwF}cE4bC?{sxVTM0iKA}878XK!_j}n zlK6Acn`k-ISyDh<>V?YujFd0=*QcORor3$IxiL^c^H>cZN#Ti~LF>Z*guR2b@FiEP zi%z<6o<4R@1mf4(89Y@fr2_iLU2H;6^ScvM6>{p5qy#U3#`+2b5+>I~Z1{Qcp68UB>zR!!-prXQw5y4( zgDKV1lMkGqQl!qMQYo**XhY34KJT4ex%fejcv zZtoQ=*~j`gYdvg;tQHVS^TBB^88ATLx!w{}b>CjXr6-Gk!0=mAp!C5o2jJHp%{A6Q z);jVS6UGe2k_eN$vMlN9BEzj$P+lss#h9gtB0|Slj-IbZAjy0vp+wvw`|l=$>$Db{ z-zX7JIZcLr+t_(b6|;jMi!T zolTrC91#&H^S!VZL5FS!4n!;Ov1TVn?=$9+44O#ga`2q`7k`0GT9BdFrCf(5QkIcBh1g_v`9#z=X}^u zEX3sA`gUnR{hP2ok2#U)b{&^yKRK|m35I1K2YzRc@F<7ISA)5&68BU@T`2SaIh0{Q zR+4P&P?>nXe-5Tu4ADqcQ8IXzze|bQdAZbeumC-v_asSQn; zcsa{!jf~)!@ApoXJ|eoTn#@sI%n_!eZYFJ?wwYK}&b4lJ3xGVH*ed!&CYPC&HO*{2 zx!&?sSwm+%<^c6=(?~CB>I7PY3&9n2}!JRmQS?&N4(`>5bCe#G>_!l-U zV=&BmtaM!@pCI6<7{y&03<(BUBPfg=}#=V1QjNh9r^D3ZQ~$ z-+d4(XSuVNC#N0n>`4rUY=>51W00YMceM5OJr_HgTTfSLEz{8iti&SNn>(EV$o9JW z!m(K@?NUx=&_SExn*qX~{m)clg3E3uI!yy-DPLdxN1DU*tpvKk!8glk*Hf;dQar=R z7on>L@+5L?e(v{&A8(jHcO7kAloZWS&3%0pW#C-Auw_GzDtBS?LYD*}^H6l(nT4DYJ9ve?9;u&7=<;D*FhwV`{oy33F!QH)y+h(Q_{^S__4O*`fbWV(A7 zWjUxL4a#hZr!puK*C~5l45>P9E67TlKz$7iUb0YsC|Yb zCnipl&)YZ90KN)Bxo#d<(iou(K{&J$GWS^<<=I+@__=Xt(A)>w+Yi}dKp8?N2KRwN z^gr}Vwq&VE4@VV%9@O>z>ERVLH36<>+mQ?%=aXkN*odx#Kq2}t^_A!}w#ln1zKPps zedEUYxuo@>=iu>96C9}yB zCbMidGlvUKENqLxrW2cg``-*3sZf3a$US)1zpgLentlC+dxuXcb#e0pH6eYbJF<(` z_(Kj?UXp$c-l`|EQjCV;n|z?PW2`l{DIOv3&U`!(tFpw!5fx^g(=@fBZq$tCk;a}L z?6uR0bKlS#MZXT{8{XUTd)j)acoP7%fg`4QNBm-F=w;VM(4%AF&^DR|Guo7@3417* z1~o&vueD=BWCKjHpR(`EZpGc4HGP_xU-J_pCsD>lqJCrA#QFIhAjjF}&De*Ukji|& zwq=!tF1#YjM2Dln$(9PGgQxvK<_3#XaRKjv3FymIvD{+O|B~>3*5T`aNVtD;A^}9v zPqTh15!MoN?6?KmUg5QY4u|}W6V^A$t!@H*8YtK*JFgf5r2_F4+dd-;oDv7lRl?)A zjlAgqldfV%)8HzC&Ph+uhd^-yLWe^l=?8a&12-Ume96LIJV&0ayQ zEn?2vlqGF3$U=z(-(fZ$tm|>X99kReTLQ8t)u5ltKiMhYq8cL7?Ik@{Sl;Br$a^m& zFD{ZgAv<5POi51>jj>s4sY#-jfDD-Va9FS>^It5_)V8d1)gZs&3zY4)KNF@3d2tn_ zbmXJ*`qQy!S2LEe(bB>G+Y1`&U>rh%dhH3#{MJds9$SwyNYE0n8;s&C=F3DAF85K` zyz~>{k4>dfFy$ks6~q}?sE$8PqQ%BnWg}@qS&AEVvnB5oYxlSxFU|)8D}Re8y3`ur zUlS`Jr|{LMxcP;BwzbI=?!2m`=czdR!Gm*bZEG<;C*@u4)R_SOdzt}!#;0QU?GrWx z(Lv@(8um}*&n6Nas;|`H<~RFk{-`kFbxRuUy$aZ&=aIgMt*6Kx-_=&v6KPfrN&YcP z(ONj_*Q`9$^OcNv!)XRPW5)@JGiz>U2@9tp%aOVi5tfG;a(IT#T5aPIfsmM*BGw{3 z&>-cf;$BH$4La$#zC^ue#8eHZR}=l#%%=4|27{Roc&molVxkR1#f3crno(y59e7uY z;wGqG6(iv~UZT5Ck`J5l8bJv_bG{!(#C5rQyMgXIyVxxf)|HK zoh#K!_$D}6sygpkNZEj5Yy29c$dn03ZPXxSymbD0d@bNhq$4rvQ7=E@=g*}lpY3Ta z6DUhSvmpHVX~4A(|18}-FcAbQq=Et>e7pA(_V9tnXWQ#I4j{VFdF+cVXmQ#RE2bhV z;BUI6f#|8(?6$dxAB!|kOk}CvDP9uw{7OPr79RE6sfkBIqxl$>HUYW|X%H*0n>Z+x z$JeHb2AYZcPy9l7m|1V1i+YKu=rfEwX#rzJfs6&|WJZ!{1m&it#Y%K{3HW3b^2BAu zDxJD3_N=RGd-#Bcw9<(A4TluQX}beZ&4r?ZQ&f~b&ajHscRaFabd#BM5-vR$G{Tnm zakJ|iprJoO7nSuf*Jz4G7Ajx?kstaIqc^RXq=3k!8`QM>WD~YZUs_(<4iGAK0Gcp1 z*FsNFE#=b)cy+7F1D$*S57pxj4-|)vDN*O@8{UaYm9oJZO+;)GzF-U_wmGNGM`)4x zVC{Cq2QVh6mp}>&c>KRZym}qc{$QmdbC7MmVj~b34u%DbOycuRMi~Sz{I$=rNQv$y zDo5s_sUl8K2jV6fnXewZ(7Yeui~8!5peW@sCh5WYXG(eA@g)mpJK1vin?qY($3{n& zdyv10S$%^5I7L8{v6v@p&bt7X@7D#zHePiI_Hp(dIRfD^cR}4cY~IRq>>^{lVl|E zUt+k`p7s9=R+ji-(kp16*(WHvE>e0Oi{E5VRphpC?mL3_t*M5M1eiCBJ%Nk%cvO@7 z-UgmF$$xoxz94TH_m)K- zW>Nq`uil4P5J{Ir2+cL1V}w&1@BzO7B4L<@9JnPDz|u@4bb1C`^-ydjrW>}Kc8trq-Suw90+K&UIaW>GLwj@XbzXWmCp zn;kGaR8DThc(5iNBB^tN$`mHytB#qUu)ktV(B}87t6C%zrvid%=;)B_+f#Bkhed0k zb@OPvh^2Yp!h@4+C9s2zGobnbcGKYp5?!OH805IoPKw>nwhg;x4NJJ0e~`qumk|irf*8I#Ao{g000001X)@i7;SF= literal 5524 zcmV;F6>IAKH+ooF000E$*0e?f03iVu0001VFXf})@BbB4T>v>5N@un}Hi1L#y~)Z- z{84!W3f)Esb7jq5>fDNTXAtbJtFLh#1{z_@XdBbYO#I_6ejX}{k|qPV5*F#tV*(KZ z&8y&v@-_@%O$~^*VQ2&(ntw~=X4{`14ln0V8gAs7B)fP}6FAt~Kb*`}cin_F`PjKM z=gDE4I1`vmQzegt`_V-)uy0DRn8>u3#F{_S@Q?#j3Pf@d^@SuYQthQu*4bx}Wxgr^ zpzQnk7-!^ooWgD}Y<{Z)25({OuU|9(2RU`Q#t(Nms*UWhQUf?8R&lYuSf-byBX0K8 z^I}P z;jsPqKwt(94>NX^omDYN6iLjF8^%Fq74AggLtsy+E4;4W#LVx|OUJ_~rkBr=I9G`S z-`lF>zkaJh8c8uPIv7!1R#=VKn4$v5FVtOAT~e-|DG}P7pu?u zg2?4<^6J^x4!h7r^PhZef;GY0o&cgx`7rh$+7fCPFWMF3s{tZp9=q<`0M;3wj#_1g zy)AF_+45$2ZeN7&sq;gF7CAoR3TC{{A3kUkG`e@pmB_i|#|gx}R%AMZmCD*tQpM$A zmFmu|SZA0^w?;w2^J#7(!b+rZ$z`H1_w}(oKbr6?5#;6I*|C;sURHR-b#HK0GgHwl zWlO4wdEbX1D|5Dcj7=Qurtcf#d@UCH2{rv6(9o2ArZcO^C#+^h0?!I+spQj?8~88n z&E4e>R8^&N7+DMiqM6PD31Wv0giL;`XN??_@^TLV4hsB_WSOq_+c>H2$Dzqflv>9< zh9V#;-2)ZRNP$CY1HLx8Cx$ODMd$i>V~Lai-_=C*H-nB$WV7w4_|_RdZxY=@`)PU9 zGU-R&Fm5PR*l=QSUedJi+q~F%qp&wRE~3tnSu#;tV>1kSB#HCAD|;^tLzOsOObfGC zxl)v7r8Kf`JlpPMyfI81Mv@JKh+8vmq_9c@{#q=-N;#Y-+4)mJ77tr#mrvm(XFmSP zG`G5J9L|fhqonikigA&LzgIIzkD0JRwG!tEUQDZ}LZ|#5Ual6Xaun6M+(HDC&+jiB zxRa*KG2+)q*k=l9wu2EZW|NN;6w^HoI>w-V*1+BB&9amec?NGnkvXq>&?V<0S79xs zE);IWKN6$<=AHej%3g*(e+_xcmwPhy$}qM#E#@F{P)$5H9+(}YyEW0ZG;nQX00+iN zlwO}I{>mXc^=^Y1-~obpGtoH%Sg8VHbMnK%bcKXdHdNlomWIl)pzNw;A8lK?zqCL$ zNtth-_+Epfbrx87EX;Qr527&3oF#4}aE}OWga@2=Ygo`4h3Y^~>!X^knst8lFS%W5 z22|-1!ABSJyfe!9gP2T9@aCFHh_E-a`8#UE-nKN8K4Lw8PgF5GHLGhpl%DwU3GWAP zb)mCUGrJ1spZEsCe>(_RS+yylRnFv0&(HZPqRJiW{T9=KHLS>_%NoQ>AjypKmy>(K zStvuAI*Zm{SABkrITglicDPv7B=GyQ>NkIV*#NS+VqdSyXmh*i{Giej9qux%*V4!v z04}f5;K6g%G(3;;FrbnT{Vlj~6@tey6YDC&(V_B1$QFNs87L<@XGuRxb z-rW@&t@xrgkZjqx%OVI!>_M>rt9_fwcs-qXQM@YqO~2H@>&AFrl^o0=9KMF&+7hz8 zm9Z0~y=WvtQFC@A4HYGmx0;`V`j zTBirBrULceJufTw9S0oQrlQkQqL4U~0>5<_BHQxuNcybgxl+KdM;RrG>B_c1s)bSoOGy?7`dzpWg+m83c_Bfe}_G zs~{p8wWo+@hwfLuI(xH@T?B%SsJ~n@;DL)@45ldz@biq=qp>{HHsq0s`InRO9bUdOPHf1S(2GBdtU}U`lXj+^OaaJ0#?cIX zz`a_1oZoGHeOnV%cz?|}Ux!?f|BWlg`mh%HY&oR|SzSM*%ToXmZz^C-H2~|!G3&`d zCzie=S>qD)_I?rZJ#|kd6V&+zn{H`5&L{S%Bod&aajrBb!)rj#Xs~5!G zp(wBu%!MMS?AkiP;grvxR|8?)kmt33Ks3^UqR*k(HGaA1apho-VQ-xRYL`*9PsVQ* zgU=Tzp-Ab0@PL%xh?tv)VOvoR%C=YDQrO`Fh~-V^Gg1Am90nf<$hXh$Mj&A`=pj>y zBXH6u+B1j{O~Vxn)sMPc8YQ5@K?48f!TheWcJnpl#Kdl+zNEIal0C0Xxx*ay&BGTt zvJv?!ox`mJ2UfORJJr84*kW2)s;aizuM35WS7&vHCw{<)N>KpncOw%vC$e2I!T>HE z;hHcWk1BUPTsSh_eym-mFuSvZaS;o`j@#x|qz6gkPwEso`$pi&OqTtHJNMOyPYOY38xFB-B}fwBm;0_?4Ml@bxI6QfTp>DJr-^F%|W_ z$-Wlx_;e(f1)dO8QR&45;N*QG-c)lB-2)7qR9vsd*c(}Yp@Jf2nV38Y3W=}J^`9Xj z4^|H>om2DJ(AfpN96L<`CEV)e(`iQ>DemWjW z?#hsDws>z8($(!8fOwYzV|Q^~@ixxn#8TOMTj;{`7Ldq?z2;shLNYtuY?zsJ`8tJ>aXJ5j0S z95)Rb9qy4F`v;^O_Y)*9M+qOq=lX`2v?C+SZkuxNydCv@w$`OWJH43g%4!lk& zNEo2_0s;9TWv_KVzRWhB2Ntq-Xo$j9DB{AwI_{ac@FK$0Qjp|iDZC6+DAFbO-q&!W z9JWT6;p*bW#@Cmiceiq%R)QY|FOBs#3++h4b*o)4u*L^zDzolJbHB(k0dE3?6_-O% z7-;9MqjJs?r_{nm`Ve6AxTvebCYENAxyCW){#(NeH}OZa`;g1*+bVLLFzAHB|D-Bn z!&(K|XeU~^-&Y{a#(j+58(5j~ zLjy#sI4fG4xQ^X_kbH@F#sn}1!;X)2ykHi9Jc;`A34k%vE14LeTJF{G!4Ji4?=wR- z)zjB5K8bajhY%D+T|1S3fN#zt-?FPFOh}pCVrr6 zLTI^gc?vM+d%#YhHQbeZxy-NXZF;C!_oRTgHKSK$Q_i#ex>VZ#>|sc?EPYD?LI#1E zcN(_#74PgUZ)dY`{>3M{@8q@Kp?k1FwB^2l;-~(+A!BfIC4r|?y~l9}juyA2H!gXc zTrJt&h767*t{P?16$BU?g@U+X4XU622arWnyi9bWh1h~F4bjM%yWc*SR0g^_L0|Cx zry_hh3|iWe>UOV_PGS-w!U{Qou~SgKg3phkt06Pe&Z(lGvJ0%rrEzd8*_N{U-7w?m zGE%LF={|x#?(XU5_u{|zRAPuBYDdOfd9~rXcTN2OtliPE$`jdGDLDNHbzXBzGh
  • hitUvXK$sVWTuHQ4`ZdY6WW&L~ zYrr|UGRP5;Nk&K=A2@&o4^{3P!YbP$DR$8*fgz^$Sh1*c?`S(CY4dUUb9om`0^=(hr*Ce&t{Yz$ihlP(i21%ghcqYblmS!V)?*#g4GUc%yqbWw{q1EfU96 zdoUIg(;!t*YXS&P+ETGQ7(}uXNHx5qNdp#Hx)B2o6bgEIb#`ISwH203$fl`Qb1*w8`e`gB{_gynce{ znS8HH0?~yJro9En)?SFWZJC*b%Fvhh%OB7QjMOL9w*(-bFv?tLYogb}Jp6=hzY5rX z?7M`G$X;7JH)FIV&1&Brw(oot0Q^U?K2zJyz&+smc9-F6oS63gQp=36sY*D2xWZ>F zg7$w^{|SQy1o?Y9o%?D!Zix2U>s==f$i}meb-24lJQC*VpKu^3`Q#_GFkN(MW!Ybh z;=JF10DQ1ofCk~}{axu`@Q7TW6SP*i3jw6T*A{*zTwokFZi`oMbVj<_)}Xk36vi|x zKnsnf&pCxuvj;dc=59GwcagEJ9;;#uXs%TpKp+N?lF=RGED=EWet5#4$fiA|z_#p* zDDs0^=ISWtn=}DH7UbDn&`)$%x{t08%}FrW$b4G@9?{mEu~b~p4^r>ZL4+O<^Q zmWlBiQw+J1h|j%WUg;L-5wgiJ=nm)iEM>W06V*1yVI7rY^~0x~&!d=3nUl{gI4{MV zz{aoZ3`dP7q7{j+#B8hwedfY5r-n7`N8l4_12d1GeXQ!~GIK_)wTC0@6?g-b`B|lq2bG-zEloEM#*Y&L z>iQ{;vHn+)Q5IkmswrfxFf>0Hcf5c;#lq`U5DK5@QZ$xL_P)KksR+k449)S}RcT`3 zJ55QKuecH|_`LmTb$1nrdBjsKYJ>X?1=~Ldqaj@_k1A{!`qcnM9DxOzRjRZ$Gue$^ zKz)<LSF(d)Q0N`7h)2otJhEf+5ejV9Whz*;U%qDS zt!lPdMwT3{&pgHhh*}cnO$VoLYDLG;dkBFazn;HN^G=pPX|&& z`V(cM@h`Phy4B$m(G{apx$P@({_U4>E8Y>2+M)c?uO87t{wifMT1UloIoWeD)hB^= zFO2o1-z8`^O=PW1lyG$kt+EBo`AjbCs_N9QhRkW7K73AO-T6XVHPpVHeb~JhW0qKr zRkaP0XpQac_8d48VgHO4$-X_c9oM4h=WMmwCp|UYRHi0LK4F8HR3BA*>6ZsKZEFaZ zI7@6sqGB1x`6Wk^m6RgWy?l5DIu#0YJL&LVd*Ek_H_O;4Duo=^hH*OpHLstavsX5} za)|yUF{mUbR2do&rEK2-YWR4}6Mrn_q{3&RJ6g4PG_!pp#H3S$7x^y%nWeZw;Mwxy z2>Wa#Q((X*uxZKtlj?bxGlD>XMSo{^(n(q=)p#hSiN?stSFuvg!%px*vF-_oWE7_& z=^fo<7l_9=OvK$LGdGZO=<4tiGJ}tGYA4JSz{JLVGcm%62fajWhb+`0Ezbw+2uDZ_ z=`6RLzWT#dX3gKoYE$EXm_JK_!mwVwBN=!?H?4++G9*&csKyne*_VwXSq9#E1~s~F zMIi85_ukPN(T+_}kR2-9k#W*;r8n|SyDzP+b{?0njVMSb!wV%(ZkHTv2`vC