We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Microk8s on armhf architecture
Hi all, 

The armhf binary is missing and not available right now, which means that some users cannot install microk8s on Ubuntu. For example, if you use the armhf image for Raspberry Pi, you cannot install microk8s: 

> ubuntu@battlecruiser:~$ sudo snap install microk8s --classic
> error: snap "microk8s" is not available on stable for this architecture (armhf)
>        but exists on other architectures (amd64, arm64, ppc64el).

It would be really good if we could also get the build compiled for this architecture and make officially available. 

Cheers,

- Calvin 

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `scripts/wrappers/common/utils.py`
Content:
```
1 import getpass
2 import json
3 import os
4 import platform
5 import subprocess
6 import sys
7 import time
8 from pathlib import Path
9 
10 import click
11 import yaml
12 
13 kubeconfig = "--kubeconfig=" + os.path.expandvars("${SNAP_DATA}/credentials/client.config")
14 
15 
16 def get_current_arch():
17     # architecture mapping
18     arch_mapping = {"aarch64": "arm64", "x86_64": "amd64"}
19 
20     return arch_mapping[platform.machine()]
21 
22 
23 def snap_data() -> Path:
24     try:
25         return Path(os.environ["SNAP_DATA"])
26     except KeyError:
27         return Path("/var/snap/microk8s/current")
28 
29 
30 def run(*args, die=True):
31     # Add wrappers to $PATH
32     env = os.environ.copy()
33     env["PATH"] += ":%s" % os.environ["SNAP"]
34     result = subprocess.run(
35         args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env
36     )
37 
38     try:
39         result.check_returncode()
40     except subprocess.CalledProcessError as err:
41         if die:
42             if result.stderr:
43                 print(result.stderr.decode("utf-8"))
44             print(err)
45             sys.exit(1)
46         else:
47             raise
48 
49     return result.stdout.decode("utf-8")
50 
51 
52 def is_cluster_ready():
53     try:
54         service_output = kubectl_get("all")
55         node_output = kubectl_get("nodes")
56         # Make sure to compare with the word " Ready " with spaces.
57         if " Ready " in node_output and "service/kubernetes" in service_output:
58             return True
59         else:
60             return False
61     except Exception:
62         return False
63 
64 
65 def is_ha_enabled():
66     ha_lock = os.path.expandvars("${SNAP_DATA}/var/lock/ha-cluster")
67     return os.path.isfile(ha_lock)
68 
69 
70 def get_dqlite_info():
71     cluster_dir = os.path.expandvars("${SNAP_DATA}/var/kubernetes/backend")
72     snap_path = os.environ.get("SNAP")
73 
74     info = []
75 
76     if not is_ha_enabled():
77         return info
78 
79     waits = 10
80     while waits > 0:
81         try:
82             with open("{}/info.yaml".format(cluster_dir), mode="r") as f:
83                 data = yaml.load(f, Loader=yaml.FullLoader)
84                 out = subprocess.check_output(
85                     "{snappath}/bin/dqlite -s file://{dbdir}/cluster.yaml -c {dbdir}/cluster.crt "
86                     "-k {dbdir}/cluster.key -f json k8s .cluster".format(
87                         snappath=snap_path, dbdir=cluster_dir
88                     ).split(),
89                     timeout=4,
90                 )
91                 if data["Address"] in out.decode():
92                     break
93                 else:
94                     time.sleep(5)
95                     waits -= 1
96         except (subprocess.CalledProcessError, subprocess.TimeoutExpired):
97             time.sleep(2)
98             waits -= 1
99 
100     if waits == 0:
101         return info
102 
103     nodes = json.loads(out.decode())
104     for n in nodes:
105         if n["Role"] == 0:
106             info.append((n["Address"], "voter"))
107         if n["Role"] == 1:
108             info.append((n["Address"], "standby"))
109         if n["Role"] == 2:
110             info.append((n["Address"], "spare"))
111     return info
112 
113 
114 def is_cluster_locked():
115     if (snap_data() / "var/lock/clustered.lock").exists():
116         click.echo("This MicroK8s deployment is acting as a node in a cluster.")
117         click.echo("Please use the master node.")
118         sys.exit(1)
119 
120 
121 def wait_for_ready(timeout):
122     start_time = time.time()
123 
124     while True:
125         if is_cluster_ready():
126             return True
127         elif timeout and time.time() > start_time + timeout:
128             return False
129         else:
130             time.sleep(2)
131 
132 
133 def exit_if_stopped():
134     stoppedLockFile = os.path.expandvars("${SNAP_DATA}/var/lock/stopped.lock")
135     if os.path.isfile(stoppedLockFile):
136         print("microk8s is not running, try microk8s start")
137         exit(0)
138 
139 
140 def exit_if_no_permission():
141     user = getpass.getuser()
142     # test if we can access the default kubeconfig
143     clientConfigFile = os.path.expandvars("${SNAP_DATA}/credentials/client.config")
144     if not os.access(clientConfigFile, os.R_OK):
145         print("Insufficient permissions to access MicroK8s.")
146         print(
147             "You can either try again with sudo or add the user {} to the 'microk8s' group:".format(
148                 user
149             )
150         )
151         print("")
152         print("    sudo usermod -a -G microk8s {}".format(user))
153         print("    sudo chown -f -R $USER ~/.kube")
154         print("")
155         print(
156             "After this, reload the user groups either via a reboot or by running 'newgrp microk8s'."
157         )
158         exit(1)
159 
160 
161 def ensure_started():
162     if (snap_data() / "var/lock/stopped.lock").exists():
163         click.echo("microk8s is not running, try microk8s start", err=True)
164         sys.exit(1)
165 
166 
167 def kubectl_get(cmd, namespace="--all-namespaces"):
168     if namespace == "--all-namespaces":
169         return run("kubectl", kubeconfig, "get", cmd, "--all-namespaces", die=False)
170     else:
171         return run("kubectl", kubeconfig, "get", cmd, "-n", namespace, die=False)
172 
173 
174 def kubectl_get_clusterroles():
175     return run(
176         "kubectl", kubeconfig, "get", "clusterroles", "--show-kind", "--no-headers", die=False
177     )
178 
179 
180 def get_available_addons(arch):
181     addon_dataset = os.path.expandvars("${SNAP}/addon-lists.yaml")
182     available = []
183     with open(addon_dataset, "r") as file:
184         # The FullLoader parameter handles the conversion from YAML
185         # scalar values to Python the dictionary format
186         addons = yaml.load(file, Loader=yaml.FullLoader)
187         for addon in addons["microk8s-addons"]["addons"]:
188             if arch in addon["supported_architectures"]:
189                 available.append(addon)
190 
191     available = sorted(available, key=lambda k: k["name"])
192     return available
193 
194 
195 def get_addon_by_name(addons, name):
196     filtered_addon = []
197     for addon in addons:
198         if name == addon["name"]:
199             filtered_addon.append(addon)
200     return filtered_addon
201 
202 
203 def is_service_expected_to_start(service):
204     """
205     Check if a service is supposed to start
206     :param service: the service name
207     :return: True if the service is meant to start
208     """
209     lock_path = os.path.expandvars("${SNAP_DATA}/var/lock")
210     lock = "{}/{}".format(lock_path, service)
211     return os.path.exists(lock_path) and not os.path.isfile(lock)
212 
213 
214 def set_service_expected_to_start(service, start=True):
215     """
216     Check if a service is not expected to start.
217     :param service: the service name
218     :param start: should the service start or not
219     """
220     lock_path = os.path.expandvars("${SNAP_DATA}/var/lock")
221     lock = "{}/{}".format(lock_path, service)
222     if start:
223         os.remove(lock)
224     else:
225         fd = os.open(lock, os.O_CREAT, mode=0o700)
226         os.close(fd)
227 
228 
229 def check_help_flag(addons: list) -> bool:
230     """Checks to see if a help message needs to be printed for an addon.
231 
232     Not all addons check for help flags themselves. Until they do, intercept
233     calls to print help text and print out a generic message to that effect.
234     """
235     addon = addons[0]
236     if any(arg in addons for arg in ("-h", "--help")) and addon != "kubeflow":
237         print("Addon %s does not yet have a help message." % addon)
238         print("For more information about it, visit https://microk8s.io/docs/addons")
239         return True
240     return False
241 
242 
243 def xable(action: str, addons: list, xabled_addons: list):
244     """Enables or disables the given addons.
245 
246     Collated into a single function since the logic is identical other than
247     the script names.
248     """
249     actions = Path(__file__).absolute().parent / "../../../actions"
250     existing_addons = {sh.with_suffix("").name[7:] for sh in actions.glob("enable.*.sh")}
251 
252     # Backwards compatibility with enabling multiple addons at once, e.g.
253     # `microk8s.enable foo bar:"baz"`
254     if all(a.split(":")[0] in existing_addons for a in addons) and len(addons) > 1:
255         for addon in addons:
256             if addon in xabled_addons and addon != "kubeflow":
257                 click.echo("Addon %s is already %sd." % (addon, action))
258             else:
259                 addon, *args = addon.split(":")
260                 wait_for_ready(timeout=30)
261                 p = subprocess.run([str(actions / ("%s.%s.sh" % (action, addon)))] + args)
262                 if p.returncode:
263                     sys.exit(p.returncode)
264                 wait_for_ready(timeout=30)
265 
266     # The new way of xabling addons, that allows for unix-style argument passing,
267     # such as `microk8s.enable foo --bar`.
268     else:
269         addon, *args = addons[0].split(":")
270 
271         if addon in xabled_addons and addon != "kubeflow":
272             click.echo("Addon %s is already %sd." % (addon, action))
273             sys.exit(0)
274 
275         if addon not in existing_addons:
276             click.echo("Nothing to do for `%s`." % addon, err=True)
277             sys.exit(1)
278 
279         if args and addons[1:]:
280             click.echo(
281                 "Can't pass string arguments and flag arguments simultaneously!\n"
282                 "{0} an addon with only one argument style at a time:\n"
283                 "\n"
284                 "    microk8s {1} foo:'bar'\n"
285                 "or\n"
286                 "    microk8s {1} foo --bar\n".format(action.title(), action)
287             )
288             sys.exit(1)
289 
290         wait_for_ready(timeout=30)
291         script = [str(actions / ("%s.%s.sh" % (action, addon)))]
292         if args:
293             p = subprocess.run(script + args)
294         else:
295             p = subprocess.run(script + list(addons[1:]))
296 
297         if p.returncode:
298             sys.exit(p.returncode)
299 
300         wait_for_ready(timeout=30)
301
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/scripts/wrappers/common/utils.py b/scripts/wrappers/common/utils.py
--- a/scripts/wrappers/common/utils.py
+++ b/scripts/wrappers/common/utils.py
@@ -15,7 +15,7 @@
 
 def get_current_arch():
     # architecture mapping
-    arch_mapping = {"aarch64": "arm64", "x86_64": "amd64"}
+    arch_mapping = {"aarch64": "arm64", "armv7l": "armhf", "x86_64": "amd64"}
 
     return arch_mapping[platform.machine()]

{"golden_diff": "diff --git a/scripts/wrappers/common/utils.py b/scripts/wrappers/common/utils.py\n--- a/scripts/wrappers/common/utils.py\n+++ b/scripts/wrappers/common/utils.py\n@@ -15,7 +15,7 @@\n \n def get_current_arch():\n     # architecture mapping\n-    arch_mapping = {\"aarch64\": \"arm64\", \"x86_64\": \"amd64\"}\n+    arch_mapping = {\"aarch64\": \"arm64\", \"armv7l\": \"armhf\", \"x86_64\": \"amd64\"}\n \n     return arch_mapping[platform.machine()]\n", "issue": "Microk8s on armhf architecture\nHi all, \r\n\r\nThe armhf binary is missing and not available right now, which means that some users cannot install microk8s on Ubuntu. For example, if you use the armhf image for Raspberry Pi, you cannot install microk8s: \r\n\r\n> ubuntu@battlecruiser:~$ sudo snap install microk8s --classic\r\n> error: snap \"microk8s\" is not available on stable for this architecture (armhf)\r\n>        but exists on other architectures (amd64, arm64, ppc64el).\r\n\r\nIt would be really good if we could also get the build compiled for this architecture and make officially available. \r\n\r\nCheers,\r\n\r\n- Calvin \n", "before_files": [{"content": "import getpass\nimport json\nimport os\nimport platform\nimport subprocess\nimport sys\nimport time\nfrom pathlib import Path\n\nimport click\nimport yaml\n\nkubeconfig = \"--kubeconfig=\" + os.path.expandvars(\"${SNAP_DATA}/credentials/client.config\")\n\n\ndef get_current_arch():\n    # architecture mapping\n    arch_mapping = {\"aarch64\": \"arm64\", \"x86_64\": \"amd64\"}\n\n    return arch_mapping[platform.machine()]\n\n\ndef snap_data() -> Path:\n    try:\n        return Path(os.environ[\"SNAP_DATA\"])\n    except KeyError:\n        return Path(\"/var/snap/microk8s/current\")\n\n\ndef run(*args, die=True):\n    # Add wrappers to $PATH\n    env = os.environ.copy()\n    env[\"PATH\"] += \":%s\" % os.environ[\"SNAP\"]\n    result = subprocess.run(\n        args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env\n    )\n\n    try:\n        result.check_returncode()\n    except subprocess.CalledProcessError as err:\n        if die:\n            if result.stderr:\n                print(result.stderr.decode(\"utf-8\"))\n            print(err)\n            sys.exit(1)\n        else:\n            raise\n\n    return result.stdout.decode(\"utf-8\")\n\n\ndef is_cluster_ready():\n    try:\n        service_output = kubectl_get(\"all\")\n        node_output = kubectl_get(\"nodes\")\n        # Make sure to compare with the word \" Ready \" with spaces.\n        if \" Ready \" in node_output and \"service/kubernetes\" in service_output:\n            return True\n        else:\n            return False\n    except Exception:\n        return False\n\n\ndef is_ha_enabled():\n    ha_lock = os.path.expandvars(\"${SNAP_DATA}/var/lock/ha-cluster\")\n    return os.path.isfile(ha_lock)\n\n\ndef get_dqlite_info():\n    cluster_dir = os.path.expandvars(\"${SNAP_DATA}/var/kubernetes/backend\")\n    snap_path = os.environ.get(\"SNAP\")\n\n    info = []\n\n    if not is_ha_enabled():\n        return info\n\n    waits = 10\n    while waits > 0:\n        try:\n            with open(\"{}/info.yaml\".format(cluster_dir), mode=\"r\") as f:\n                data = yaml.load(f, Loader=yaml.FullLoader)\n                out = subprocess.check_output(\n                    \"{snappath}/bin/dqlite -s file://{dbdir}/cluster.yaml -c {dbdir}/cluster.crt \"\n                    \"-k {dbdir}/cluster.key -f json k8s .cluster\".format(\n                        snappath=snap_path, dbdir=cluster_dir\n                    ).split(),\n                    timeout=4,\n                )\n                if data[\"Address\"] in out.decode():\n                    break\n                else:\n                    time.sleep(5)\n                    waits -= 1\n        except (subprocess.CalledProcessError, subprocess.TimeoutExpired):\n            time.sleep(2)\n            waits -= 1\n\n    if waits == 0:\n        return info\n\n    nodes = json.loads(out.decode())\n    for n in nodes:\n        if n[\"Role\"] == 0:\n            info.append((n[\"Address\"], \"voter\"))\n        if n[\"Role\"] == 1:\n            info.append((n[\"Address\"], \"standby\"))\n        if n[\"Role\"] == 2:\n            info.append((n[\"Address\"], \"spare\"))\n    return info\n\n\ndef is_cluster_locked():\n    if (snap_data() / \"var/lock/clustered.lock\").exists():\n        click.echo(\"This MicroK8s deployment is acting as a node in a cluster.\")\n        click.echo(\"Please use the master node.\")\n        sys.exit(1)\n\n\ndef wait_for_ready(timeout):\n    start_time = time.time()\n\n    while True:\n        if is_cluster_ready():\n            return True\n        elif timeout and time.time() > start_time + timeout:\n            return False\n        else:\n            time.sleep(2)\n\n\ndef exit_if_stopped():\n    stoppedLockFile = os.path.expandvars(\"${SNAP_DATA}/var/lock/stopped.lock\")\n    if os.path.isfile(stoppedLockFile):\n        print(\"microk8s is not running, try microk8s start\")\n        exit(0)\n\n\ndef exit_if_no_permission():\n    user = getpass.getuser()\n    # test if we can access the default kubeconfig\n    clientConfigFile = os.path.expandvars(\"${SNAP_DATA}/credentials/client.config\")\n    if not os.access(clientConfigFile, os.R_OK):\n        print(\"Insufficient permissions to access MicroK8s.\")\n        print(\n            \"You can either try again with sudo or add the user {} to the 'microk8s' group:\".format(\n                user\n            )\n        )\n        print(\"\")\n        print(\"    sudo usermod -a -G microk8s {}\".format(user))\n        print(\"    sudo chown -f -R $USER ~/.kube\")\n        print(\"\")\n        print(\n            \"After this, reload the user groups either via a reboot or by running 'newgrp microk8s'.\"\n        )\n        exit(1)\n\n\ndef ensure_started():\n    if (snap_data() / \"var/lock/stopped.lock\").exists():\n        click.echo(\"microk8s is not running, try microk8s start\", err=True)\n        sys.exit(1)\n\n\ndef kubectl_get(cmd, namespace=\"--all-namespaces\"):\n    if namespace == \"--all-namespaces\":\n        return run(\"kubectl\", kubeconfig, \"get\", cmd, \"--all-namespaces\", die=False)\n    else:\n        return run(\"kubectl\", kubeconfig, \"get\", cmd, \"-n\", namespace, die=False)\n\n\ndef kubectl_get_clusterroles():\n    return run(\n        \"kubectl\", kubeconfig, \"get\", \"clusterroles\", \"--show-kind\", \"--no-headers\", die=False\n    )\n\n\ndef get_available_addons(arch):\n    addon_dataset = os.path.expandvars(\"${SNAP}/addon-lists.yaml\")\n    available = []\n    with open(addon_dataset, \"r\") as file:\n        # The FullLoader parameter handles the conversion from YAML\n        # scalar values to Python the dictionary format\n        addons = yaml.load(file, Loader=yaml.FullLoader)\n        for addon in addons[\"microk8s-addons\"][\"addons\"]:\n            if arch in addon[\"supported_architectures\"]:\n                available.append(addon)\n\n    available = sorted(available, key=lambda k: k[\"name\"])\n    return available\n\n\ndef get_addon_by_name(addons, name):\n    filtered_addon = []\n    for addon in addons:\n        if name == addon[\"name\"]:\n            filtered_addon.append(addon)\n    return filtered_addon\n\n\ndef is_service_expected_to_start(service):\n    \"\"\"\n    Check if a service is supposed to start\n    :param service: the service name\n    :return: True if the service is meant to start\n    \"\"\"\n    lock_path = os.path.expandvars(\"${SNAP_DATA}/var/lock\")\n    lock = \"{}/{}\".format(lock_path, service)\n    return os.path.exists(lock_path) and not os.path.isfile(lock)\n\n\ndef set_service_expected_to_start(service, start=True):\n    \"\"\"\n    Check if a service is not expected to start.\n    :param service: the service name\n    :param start: should the service start or not\n    \"\"\"\n    lock_path = os.path.expandvars(\"${SNAP_DATA}/var/lock\")\n    lock = \"{}/{}\".format(lock_path, service)\n    if start:\n        os.remove(lock)\n    else:\n        fd = os.open(lock, os.O_CREAT, mode=0o700)\n        os.close(fd)\n\n\ndef check_help_flag(addons: list) -> bool:\n    \"\"\"Checks to see if a help message needs to be printed for an addon.\n\n    Not all addons check for help flags themselves. Until they do, intercept\n    calls to print help text and print out a generic message to that effect.\n    \"\"\"\n    addon = addons[0]\n    if any(arg in addons for arg in (\"-h\", \"--help\")) and addon != \"kubeflow\":\n        print(\"Addon %s does not yet have a help message.\" % addon)\n        print(\"For more information about it, visit https://microk8s.io/docs/addons\")\n        return True\n    return False\n\n\ndef xable(action: str, addons: list, xabled_addons: list):\n    \"\"\"Enables or disables the given addons.\n\n    Collated into a single function since the logic is identical other than\n    the script names.\n    \"\"\"\n    actions = Path(__file__).absolute().parent / \"../../../actions\"\n    existing_addons = {sh.with_suffix(\"\").name[7:] for sh in actions.glob(\"enable.*.sh\")}\n\n    # Backwards compatibility with enabling multiple addons at once, e.g.\n    # `microk8s.enable foo bar:\"baz\"`\n    if all(a.split(\":\")[0] in existing_addons for a in addons) and len(addons) > 1:\n        for addon in addons:\n            if addon in xabled_addons and addon != \"kubeflow\":\n                click.echo(\"Addon %s is already %sd.\" % (addon, action))\n            else:\n                addon, *args = addon.split(\":\")\n                wait_for_ready(timeout=30)\n                p = subprocess.run([str(actions / (\"%s.%s.sh\" % (action, addon)))] + args)\n                if p.returncode:\n                    sys.exit(p.returncode)\n                wait_for_ready(timeout=30)\n\n    # The new way of xabling addons, that allows for unix-style argument passing,\n    # such as `microk8s.enable foo --bar`.\n    else:\n        addon, *args = addons[0].split(\":\")\n\n        if addon in xabled_addons and addon != \"kubeflow\":\n            click.echo(\"Addon %s is already %sd.\" % (addon, action))\n            sys.exit(0)\n\n        if addon not in existing_addons:\n            click.echo(\"Nothing to do for `%s`.\" % addon, err=True)\n            sys.exit(1)\n\n        if args and addons[1:]:\n            click.echo(\n                \"Can't pass string arguments and flag arguments simultaneously!\\n\"\n                \"{0} an addon with only one argument style at a time:\\n\"\n                \"\\n\"\n                \"    microk8s {1} foo:'bar'\\n\"\n                \"or\\n\"\n                \"    microk8s {1} foo --bar\\n\".format(action.title(), action)\n            )\n            sys.exit(1)\n\n        wait_for_ready(timeout=30)\n        script = [str(actions / (\"%s.%s.sh\" % (action, addon)))]\n        if args:\n            p = subprocess.run(script + args)\n        else:\n            p = subprocess.run(script + list(addons[1:]))\n\n        if p.returncode:\n            sys.exit(p.returncode)\n\n        wait_for_ready(timeout=30)\n", "path": "scripts/wrappers/common/utils.py"}], "after_files": [{"content": "import getpass\nimport json\nimport os\nimport platform\nimport subprocess\nimport sys\nimport time\nfrom pathlib import Path\n\nimport click\nimport yaml\n\nkubeconfig = \"--kubeconfig=\" + os.path.expandvars(\"${SNAP_DATA}/credentials/client.config\")\n\n\ndef get_current_arch():\n    # architecture mapping\n    arch_mapping = {\"aarch64\": \"arm64\", \"armv7l\": \"armhf\", \"x86_64\": \"amd64\"}\n\n    return arch_mapping[platform.machine()]\n\n\ndef snap_data() -> Path:\n    try:\n        return Path(os.environ[\"SNAP_DATA\"])\n    except KeyError:\n        return Path(\"/var/snap/microk8s/current\")\n\n\ndef run(*args, die=True):\n    # Add wrappers to $PATH\n    env = os.environ.copy()\n    env[\"PATH\"] += \":%s\" % os.environ[\"SNAP\"]\n    result = subprocess.run(\n        args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env\n    )\n\n    try:\n        result.check_returncode()\n    except subprocess.CalledProcessError as err:\n        if die:\n            if result.stderr:\n                print(result.stderr.decode(\"utf-8\"))\n            print(err)\n            sys.exit(1)\n        else:\n            raise\n\n    return result.stdout.decode(\"utf-8\")\n\n\ndef is_cluster_ready():\n    try:\n        service_output = kubectl_get(\"all\")\n        node_output = kubectl_get(\"nodes\")\n        # Make sure to compare with the word \" Ready \" with spaces.\n        if \" Ready \" in node_output and \"service/kubernetes\" in service_output:\n            return True\n        else:\n            return False\n    except Exception:\n        return False\n\n\ndef is_ha_enabled():\n    ha_lock = os.path.expandvars(\"${SNAP_DATA}/var/lock/ha-cluster\")\n    return os.path.isfile(ha_lock)\n\n\ndef get_dqlite_info():\n    cluster_dir = os.path.expandvars(\"${SNAP_DATA}/var/kubernetes/backend\")\n    snap_path = os.environ.get(\"SNAP\")\n\n    info = []\n\n    if not is_ha_enabled():\n        return info\n\n    waits = 10\n    while waits > 0:\n        try:\n            with open(\"{}/info.yaml\".format(cluster_dir), mode=\"r\") as f:\n                data = yaml.load(f, Loader=yaml.FullLoader)\n                out = subprocess.check_output(\n                    \"{snappath}/bin/dqlite -s file://{dbdir}/cluster.yaml -c {dbdir}/cluster.crt \"\n                    \"-k {dbdir}/cluster.key -f json k8s .cluster\".format(\n                        snappath=snap_path, dbdir=cluster_dir\n                    ).split(),\n                    timeout=4,\n                )\n                if data[\"Address\"] in out.decode():\n                    break\n                else:\n                    time.sleep(5)\n                    waits -= 1\n        except (subprocess.CalledProcessError, subprocess.TimeoutExpired):\n            time.sleep(2)\n            waits -= 1\n\n    if waits == 0:\n        return info\n\n    nodes = json.loads(out.decode())\n    for n in nodes:\n        if n[\"Role\"] == 0:\n            info.append((n[\"Address\"], \"voter\"))\n        if n[\"Role\"] == 1:\n            info.append((n[\"Address\"], \"standby\"))\n        if n[\"Role\"] == 2:\n            info.append((n[\"Address\"], \"spare\"))\n    return info\n\n\ndef is_cluster_locked():\n    if (snap_data() / \"var/lock/clustered.lock\").exists():\n        click.echo(\"This MicroK8s deployment is acting as a node in a cluster.\")\n        click.echo(\"Please use the master node.\")\n        sys.exit(1)\n\n\ndef wait_for_ready(timeout):\n    start_time = time.time()\n\n    while True:\n        if is_cluster_ready():\n            return True\n        elif timeout and time.time() > start_time + timeout:\n            return False\n        else:\n            time.sleep(2)\n\n\ndef exit_if_stopped():\n    stoppedLockFile = os.path.expandvars(\"${SNAP_DATA}/var/lock/stopped.lock\")\n    if os.path.isfile(stoppedLockFile):\n        print(\"microk8s is not running, try microk8s start\")\n        exit(0)\n\n\ndef exit_if_no_permission():\n    user = getpass.getuser()\n    # test if we can access the default kubeconfig\n    clientConfigFile = os.path.expandvars(\"${SNAP_DATA}/credentials/client.config\")\n    if not os.access(clientConfigFile, os.R_OK):\n        print(\"Insufficient permissions to access MicroK8s.\")\n        print(\n            \"You can either try again with sudo or add the user {} to the 'microk8s' group:\".format(\n                user\n            )\n        )\n        print(\"\")\n        print(\"    sudo usermod -a -G microk8s {}\".format(user))\n        print(\"    sudo chown -f -R $USER ~/.kube\")\n        print(\"\")\n        print(\n            \"After this, reload the user groups either via a reboot or by running 'newgrp microk8s'.\"\n        )\n        exit(1)\n\n\ndef ensure_started():\n    if (snap_data() / \"var/lock/stopped.lock\").exists():\n        click.echo(\"microk8s is not running, try microk8s start\", err=True)\n        sys.exit(1)\n\n\ndef kubectl_get(cmd, namespace=\"--all-namespaces\"):\n    if namespace == \"--all-namespaces\":\n        return run(\"kubectl\", kubeconfig, \"get\", cmd, \"--all-namespaces\", die=False)\n    else:\n        return run(\"kubectl\", kubeconfig, \"get\", cmd, \"-n\", namespace, die=False)\n\n\ndef kubectl_get_clusterroles():\n    return run(\n        \"kubectl\", kubeconfig, \"get\", \"clusterroles\", \"--show-kind\", \"--no-headers\", die=False\n    )\n\n\ndef get_available_addons(arch):\n    addon_dataset = os.path.expandvars(\"${SNAP}/addon-lists.yaml\")\n    available = []\n    with open(addon_dataset, \"r\") as file:\n        # The FullLoader parameter handles the conversion from YAML\n        # scalar values to Python the dictionary format\n        addons = yaml.load(file, Loader=yaml.FullLoader)\n        for addon in addons[\"microk8s-addons\"][\"addons\"]:\n            if arch in addon[\"supported_architectures\"]:\n                available.append(addon)\n\n    available = sorted(available, key=lambda k: k[\"name\"])\n    return available\n\n\ndef get_addon_by_name(addons, name):\n    filtered_addon = []\n    for addon in addons:\n        if name == addon[\"name\"]:\n            filtered_addon.append(addon)\n    return filtered_addon\n\n\ndef is_service_expected_to_start(service):\n    \"\"\"\n    Check if a service is supposed to start\n    :param service: the service name\n    :return: True if the service is meant to start\n    \"\"\"\n    lock_path = os.path.expandvars(\"${SNAP_DATA}/var/lock\")\n    lock = \"{}/{}\".format(lock_path, service)\n    return os.path.exists(lock_path) and not os.path.isfile(lock)\n\n\ndef set_service_expected_to_start(service, start=True):\n    \"\"\"\n    Check if a service is not expected to start.\n    :param service: the service name\n    :param start: should the service start or not\n    \"\"\"\n    lock_path = os.path.expandvars(\"${SNAP_DATA}/var/lock\")\n    lock = \"{}/{}\".format(lock_path, service)\n    if start:\n        os.remove(lock)\n    else:\n        fd = os.open(lock, os.O_CREAT, mode=0o700)\n        os.close(fd)\n\n\ndef check_help_flag(addons: list) -> bool:\n    \"\"\"Checks to see if a help message needs to be printed for an addon.\n\n    Not all addons check for help flags themselves. Until they do, intercept\n    calls to print help text and print out a generic message to that effect.\n    \"\"\"\n    addon = addons[0]\n    if any(arg in addons for arg in (\"-h\", \"--help\")) and addon != \"kubeflow\":\n        print(\"Addon %s does not yet have a help message.\" % addon)\n        print(\"For more information about it, visit https://microk8s.io/docs/addons\")\n        return True\n    return False\n\n\ndef xable(action: str, addons: list, xabled_addons: list):\n    \"\"\"Enables or disables the given addons.\n\n    Collated into a single function since the logic is identical other than\n    the script names.\n    \"\"\"\n    actions = Path(__file__).absolute().parent / \"../../../actions\"\n    existing_addons = {sh.with_suffix(\"\").name[7:] for sh in actions.glob(\"enable.*.sh\")}\n\n    # Backwards compatibility with enabling multiple addons at once, e.g.\n    # `microk8s.enable foo bar:\"baz\"`\n    if all(a.split(\":\")[0] in existing_addons for a in addons) and len(addons) > 1:\n        for addon in addons:\n            if addon in xabled_addons and addon != \"kubeflow\":\n                click.echo(\"Addon %s is already %sd.\" % (addon, action))\n            else:\n                addon, *args = addon.split(\":\")\n                wait_for_ready(timeout=30)\n                p = subprocess.run([str(actions / (\"%s.%s.sh\" % (action, addon)))] + args)\n                if p.returncode:\n                    sys.exit(p.returncode)\n                wait_for_ready(timeout=30)\n\n    # The new way of xabling addons, that allows for unix-style argument passing,\n    # such as `microk8s.enable foo --bar`.\n    else:\n        addon, *args = addons[0].split(\":\")\n\n        if addon in xabled_addons and addon != \"kubeflow\":\n            click.echo(\"Addon %s is already %sd.\" % (addon, action))\n            sys.exit(0)\n\n        if addon not in existing_addons:\n            click.echo(\"Nothing to do for `%s`.\" % addon, err=True)\n            sys.exit(1)\n\n        if args and addons[1:]:\n            click.echo(\n                \"Can't pass string arguments and flag arguments simultaneously!\\n\"\n                \"{0} an addon with only one argument style at a time:\\n\"\n                \"\\n\"\n                \"    microk8s {1} foo:'bar'\\n\"\n                \"or\\n\"\n                \"    microk8s {1} foo --bar\\n\".format(action.title(), action)\n            )\n            sys.exit(1)\n\n        wait_for_ready(timeout=30)\n        script = [str(actions / (\"%s.%s.sh\" % (action, addon)))]\n        if args:\n            p = subprocess.run(script + args)\n        else:\n            p = subprocess.run(script + list(addons[1:]))\n\n        if p.returncode:\n            sys.exit(p.returncode)\n\n        wait_for_ready(timeout=30)\n", "path": "scripts/wrappers/common/utils.py"}]}