We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Non-HA single node, leaving node removes all pods


#### Summary
Iam running the microk8s in ubuntu in no high availability i.e. there is a single node onto the same machine where it is installed. I updated the certificates and then i issue following command:
`sudo microk8s leave`

It give following messages:

```
Generating new cluster certificates.
Waiting for node to start. 
```

and then i stopped microk8s and then started again, the node appeared  however all of my pods / namespaces are gone, how to recover?

#### What Should Happen Instead?
All the pods should retain

#### Reproduction Steps
Already described above

#### Introspection Report

#### Can you suggest a fix?
<!-- (This section is optional). How do you propose that the issue be fixed? -->
[https://wetransfer.com/downloads/ee891f9d62bd9ffd7fdac2f9597e638f20230529135310/bf5d38484b8a54a7107a5447153c884820230529135327/7b32a3](url)


--- 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/remove_node.py`
Content:
```
1 #!/usr/bin/python3
2 import json
3 import os
4 import shutil
5 import subprocess
6 import sys
7 
8 import click
9 import netifaces
10 
11 from ipaddress import ip_address, IPv4Address
12 
13 from common.cluster.utils import (
14     try_set_file_permissions,
15     is_node_running_dqlite,
16     is_token_auth_enabled,
17 )
18 
19 snap_path = os.environ.get("SNAP")
20 snapdata_path = os.environ.get("SNAP_DATA")
21 callback_tokens_file = "{}/credentials/callback-tokens.txt".format(snapdata_path)
22 
23 cluster_dir = "{}/var/kubernetes/backend".format(snapdata_path)
24 
25 
26 def remove_dqlite_node(node, force=False):
27     try:
28         # If node is an IP address, find the node name.
29         if type(ip_address(node)) is IPv4Address:
30             node_info = subprocess.check_output(
31                 "{}/microk8s-kubectl.wrapper get no -o json".format(snap_path).split()
32             )
33             info = json.loads(node_info.decode())
34             found = False
35             for n in info["items"]:
36                 if found:
37                     break
38                 for a in n["status"]["addresses"]:
39                     if a["type"] == "InternalIP" and a["address"] == node:
40                         node = n["metadata"]["name"]
41                         found = True
42                         break
43 
44         # Make sure this node exists
45         node_info = subprocess.check_output(
46             "{}/microk8s-kubectl.wrapper get no {} -o json".format(snap_path, node).split()
47         )
48         info = json.loads(node_info.decode())
49         node_address = None
50         for a in info["status"]["addresses"]:
51             if a["type"] == "InternalIP":
52                 node_address = a["address"]
53                 break
54 
55         if not node_address:
56             print("Node {} is not part of the cluster.".format(node))
57             exit(1)
58 
59         node_ep = None
60         my_ep, other_ep = get_dqlite_endpoints()
61         for ep in other_ep:
62             if ep.startswith("{}:".format(node_address)):
63                 node_ep = ep
64 
65         if node_ep and force:
66             delete_dqlite_node([node_ep], my_ep)
67         elif node_ep and not force:
68             print(
69                 "Removal failed. Node {} is registered with dqlite. "
70                 "Please, run first 'microk8s leave' on the departing node. \n"
71                 "If the node is not available anymore and will never attempt to join the cluster "
72                 "in the future use the '--force' flag \n"
73                 "to unregister the node while removing it.".format(node)
74             )
75             exit(1)
76 
77     except subprocess.CalledProcessError:
78         print("Node {} does not exist in Kubernetes.".format(node))
79         if force:
80             print("Attempting to remove {} from dqlite.".format(node))
81             # Make sure we do not have the node in dqlite.
82             # We assume the IP is provided to denote the
83             my_ep, other_ep = get_dqlite_endpoints()
84             for ep in other_ep:
85                 if ep.startswith("{}:".format(node)):
86                     print("Removing node entry found in dqlite.")
87                     delete_dqlite_node([ep], my_ep)
88         exit(1)
89 
90     remove_node(node)
91 
92 
93 def remove_node(node):
94     try:
95         # Make sure this node exists
96         subprocess.check_call(
97             "{}/microk8s-kubectl.wrapper get no {}".format(snap_path, node).split(),
98             stdout=subprocess.DEVNULL,
99             stderr=subprocess.DEVNULL,
100         )
101     except subprocess.CalledProcessError:
102         print("Node {} does not exist.".format(node))
103         exit(1)
104 
105     if is_token_auth_enabled():
106         remove_kubelet_token(node)
107     remove_callback_token(node)
108     subprocess.check_call(
109         "{}/microk8s-kubectl.wrapper delete no {}".format(snap_path, node).split(),
110         stdout=subprocess.DEVNULL,
111         stderr=subprocess.DEVNULL,
112     )
113 
114 
115 def remove_kubelet_token(node):
116     """
117     Remove a token for a node in the known tokens
118 
119     :param node: the name of the node
120     """
121     file = "{}/credentials/known_tokens.csv".format(snapdata_path)
122     backup_file = "{}.backup".format(file)
123     token = "system:node:{}".format(node)
124     # That is a critical section. We need to protect it.
125     with open(backup_file, "w") as back_fp:
126         with open(file, "r") as fp:
127             for _, line in enumerate(fp):
128                 if token in line:
129                     continue
130                 back_fp.write("{}".format(line))
131 
132     try_set_file_permissions(backup_file)
133     shutil.copyfile(backup_file, file)
134 
135 
136 def get_dqlite_endpoints():
137     """
138     Return the endpoints the current node has on dqlite and the endpoints of the rest of the nodes.
139 
140     :return: two lists with the endpoints
141     """
142     out = subprocess.check_output(
143         "{snappath}/bin/dqlite -s file://{dbdir}/cluster.yaml -c {dbdir}/cluster.crt "
144         "-k {dbdir}/cluster.key -f json k8s .cluster".format(
145             snappath=snap_path, dbdir=cluster_dir
146         ).split()
147     )
148     data = json.loads(out.decode())
149     ep_addresses = []
150     for ep in data:
151         ep_addresses.append(ep["Address"])
152     local_ips = []
153     for interface in netifaces.interfaces():
154         if netifaces.AF_INET not in netifaces.ifaddresses(interface):
155             continue
156         for link in netifaces.ifaddresses(interface)[netifaces.AF_INET]:
157             local_ips.append(link["addr"])
158     my_ep = []
159     other_ep = []
160     for ep in ep_addresses:
161         found = False
162         for ip in local_ips:
163             if "{}:".format(ip) in ep:
164                 my_ep.append(ep)
165                 found = True
166         if not found:
167             other_ep.append(ep)
168 
169     return my_ep, other_ep
170 
171 
172 def delete_dqlite_node(delete_node, dqlite_ep):
173     if len(delete_node) > 0 and "127.0.0.1" not in delete_node[0]:
174         for ep in dqlite_ep:
175             try:
176                 cmd = (
177                     "{snappath}/bin/dqlite -s file://{dbdir}/cluster.yaml -c {dbdir}/cluster.crt "
178                     "-k {dbdir}/cluster.key -f json k8s".format(
179                         snappath=snap_path, dbdir=cluster_dir
180                     ).split()
181                 )
182                 cmd.append(".remove {}".format(delete_node[0]))
183                 subprocess.check_output(cmd)
184                 break
185             except Exception as err:
186                 print("Contacting node {} failed. Error:".format(ep))
187                 print(repr(err))
188                 exit(2)
189 
190 
191 def remove_callback_token(node):
192     """
193     Remove a callback token
194 
195     :param node: the node
196     """
197     tmp_file = "{}.tmp".format(callback_tokens_file)
198     if not os.path.isfile(callback_tokens_file):
199         open(callback_tokens_file, "a+")
200         os.chmod(callback_tokens_file, 0o600)
201     with open(tmp_file, "w") as backup_fp:
202         os.chmod(tmp_file, 0o600)
203         with open(callback_tokens_file, "r+") as callback_fp:
204             # Entries are of the format: 'node_hostname:agent_port token'
205             # We need to get the node_hostname part
206             for line in callback_fp:
207                 parts = line.split(":")
208                 if parts[0] == node:
209                     continue
210                 else:
211                     backup_fp.write(line)
212 
213     try_set_file_permissions(tmp_file)
214     shutil.move(tmp_file, callback_tokens_file)
215 
216 
217 @click.command()
218 @click.argument("node", required=True)
219 @click.option(
220     "--force",
221     is_flag=True,
222     required=False,
223     default=False,
224     help="Force the node removal operation. (default: false)",
225 )
226 def reset(node, force):
227     """
228     Remove a node from the cluster
229     """
230     if is_node_running_dqlite():
231         remove_dqlite_node(node, force)
232     else:
233         remove_node(node)
234     sys.exit(0)
235 
236 
237 if __name__ == "__main__":
238     reset(prog_name="microk8s remove-node")
239
```
--- 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/remove_node.py b/scripts/wrappers/remove_node.py
--- a/scripts/wrappers/remove_node.py
+++ b/scripts/wrappers/remove_node.py
@@ -8,7 +8,7 @@
 import click
 import netifaces
 
-from ipaddress import ip_address, IPv4Address
+from ipaddress import ip_address
 
 from common.cluster.utils import (
     try_set_file_permissions,
@@ -26,7 +26,13 @@
 def remove_dqlite_node(node, force=False):
     try:
         # If node is an IP address, find the node name.
-        if type(ip_address(node)) is IPv4Address:
+        is_node_ip = True
+        try:
+            ip_address(node)
+        except ValueError:
+            is_node_ip = False
+
+        if is_node_ip:
             node_info = subprocess.check_output(
                 "{}/microk8s-kubectl.wrapper get no -o json".format(snap_path).split()
             )

{"golden_diff": "diff --git a/scripts/wrappers/remove_node.py b/scripts/wrappers/remove_node.py\n--- a/scripts/wrappers/remove_node.py\n+++ b/scripts/wrappers/remove_node.py\n@@ -8,7 +8,7 @@\n import click\n import netifaces\n \n-from ipaddress import ip_address, IPv4Address\n+from ipaddress import ip_address\n \n from common.cluster.utils import (\n     try_set_file_permissions,\n@@ -26,7 +26,13 @@\n def remove_dqlite_node(node, force=False):\n     try:\n         # If node is an IP address, find the node name.\n-        if type(ip_address(node)) is IPv4Address:\n+        is_node_ip = True\n+        try:\n+            ip_address(node)\n+        except ValueError:\n+            is_node_ip = False\n+\n+        if is_node_ip:\n             node_info = subprocess.check_output(\n                 \"{}/microk8s-kubectl.wrapper get no -o json\".format(snap_path).split()\n             )\n", "issue": "Non-HA single node, leaving node removes all pods\n\r\n\r\n#### Summary\r\nIam running the microk8s in ubuntu in no high availability i.e. there is a single node onto the same machine where it is installed. I updated the certificates and then i issue following command:\r\n`sudo microk8s leave`\r\n\r\nIt give following messages:\r\n\r\n```\r\nGenerating new cluster certificates.\r\nWaiting for node to start. \r\n```\r\n\r\nand then i stopped microk8s and then started again, the node appeared  however all of my pods / namespaces are gone, how to recover?\r\n\r\n#### What Should Happen Instead?\r\nAll the pods should retain\r\n\r\n#### Reproduction Steps\r\nAlready described above\r\n\r\n#### Introspection Report\r\n\r\n#### Can you suggest a fix?\r\n<!-- (This section is optional). How do you propose that the issue be fixed? -->\r\n[https://wetransfer.com/downloads/ee891f9d62bd9ffd7fdac2f9597e638f20230529135310/bf5d38484b8a54a7107a5447153c884820230529135327/7b32a3](url)\r\n\n", "before_files": [{"content": "#!/usr/bin/python3\nimport json\nimport os\nimport shutil\nimport subprocess\nimport sys\n\nimport click\nimport netifaces\n\nfrom ipaddress import ip_address, IPv4Address\n\nfrom common.cluster.utils import (\n    try_set_file_permissions,\n    is_node_running_dqlite,\n    is_token_auth_enabled,\n)\n\nsnap_path = os.environ.get(\"SNAP\")\nsnapdata_path = os.environ.get(\"SNAP_DATA\")\ncallback_tokens_file = \"{}/credentials/callback-tokens.txt\".format(snapdata_path)\n\ncluster_dir = \"{}/var/kubernetes/backend\".format(snapdata_path)\n\n\ndef remove_dqlite_node(node, force=False):\n    try:\n        # If node is an IP address, find the node name.\n        if type(ip_address(node)) is IPv4Address:\n            node_info = subprocess.check_output(\n                \"{}/microk8s-kubectl.wrapper get no -o json\".format(snap_path).split()\n            )\n            info = json.loads(node_info.decode())\n            found = False\n            for n in info[\"items\"]:\n                if found:\n                    break\n                for a in n[\"status\"][\"addresses\"]:\n                    if a[\"type\"] == \"InternalIP\" and a[\"address\"] == node:\n                        node = n[\"metadata\"][\"name\"]\n                        found = True\n                        break\n\n        # Make sure this node exists\n        node_info = subprocess.check_output(\n            \"{}/microk8s-kubectl.wrapper get no {} -o json\".format(snap_path, node).split()\n        )\n        info = json.loads(node_info.decode())\n        node_address = None\n        for a in info[\"status\"][\"addresses\"]:\n            if a[\"type\"] == \"InternalIP\":\n                node_address = a[\"address\"]\n                break\n\n        if not node_address:\n            print(\"Node {} is not part of the cluster.\".format(node))\n            exit(1)\n\n        node_ep = None\n        my_ep, other_ep = get_dqlite_endpoints()\n        for ep in other_ep:\n            if ep.startswith(\"{}:\".format(node_address)):\n                node_ep = ep\n\n        if node_ep and force:\n            delete_dqlite_node([node_ep], my_ep)\n        elif node_ep and not force:\n            print(\n                \"Removal failed. Node {} is registered with dqlite. \"\n                \"Please, run first 'microk8s leave' on the departing node. \\n\"\n                \"If the node is not available anymore and will never attempt to join the cluster \"\n                \"in the future use the '--force' flag \\n\"\n                \"to unregister the node while removing it.\".format(node)\n            )\n            exit(1)\n\n    except subprocess.CalledProcessError:\n        print(\"Node {} does not exist in Kubernetes.\".format(node))\n        if force:\n            print(\"Attempting to remove {} from dqlite.\".format(node))\n            # Make sure we do not have the node in dqlite.\n            # We assume the IP is provided to denote the\n            my_ep, other_ep = get_dqlite_endpoints()\n            for ep in other_ep:\n                if ep.startswith(\"{}:\".format(node)):\n                    print(\"Removing node entry found in dqlite.\")\n                    delete_dqlite_node([ep], my_ep)\n        exit(1)\n\n    remove_node(node)\n\n\ndef remove_node(node):\n    try:\n        # Make sure this node exists\n        subprocess.check_call(\n            \"{}/microk8s-kubectl.wrapper get no {}\".format(snap_path, node).split(),\n            stdout=subprocess.DEVNULL,\n            stderr=subprocess.DEVNULL,\n        )\n    except subprocess.CalledProcessError:\n        print(\"Node {} does not exist.\".format(node))\n        exit(1)\n\n    if is_token_auth_enabled():\n        remove_kubelet_token(node)\n    remove_callback_token(node)\n    subprocess.check_call(\n        \"{}/microk8s-kubectl.wrapper delete no {}\".format(snap_path, node).split(),\n        stdout=subprocess.DEVNULL,\n        stderr=subprocess.DEVNULL,\n    )\n\n\ndef remove_kubelet_token(node):\n    \"\"\"\n    Remove a token for a node in the known tokens\n\n    :param node: the name of the node\n    \"\"\"\n    file = \"{}/credentials/known_tokens.csv\".format(snapdata_path)\n    backup_file = \"{}.backup\".format(file)\n    token = \"system:node:{}\".format(node)\n    # That is a critical section. We need to protect it.\n    with open(backup_file, \"w\") as back_fp:\n        with open(file, \"r\") as fp:\n            for _, line in enumerate(fp):\n                if token in line:\n                    continue\n                back_fp.write(\"{}\".format(line))\n\n    try_set_file_permissions(backup_file)\n    shutil.copyfile(backup_file, file)\n\n\ndef get_dqlite_endpoints():\n    \"\"\"\n    Return the endpoints the current node has on dqlite and the endpoints of the rest of the nodes.\n\n    :return: two lists with the endpoints\n    \"\"\"\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    )\n    data = json.loads(out.decode())\n    ep_addresses = []\n    for ep in data:\n        ep_addresses.append(ep[\"Address\"])\n    local_ips = []\n    for interface in netifaces.interfaces():\n        if netifaces.AF_INET not in netifaces.ifaddresses(interface):\n            continue\n        for link in netifaces.ifaddresses(interface)[netifaces.AF_INET]:\n            local_ips.append(link[\"addr\"])\n    my_ep = []\n    other_ep = []\n    for ep in ep_addresses:\n        found = False\n        for ip in local_ips:\n            if \"{}:\".format(ip) in ep:\n                my_ep.append(ep)\n                found = True\n        if not found:\n            other_ep.append(ep)\n\n    return my_ep, other_ep\n\n\ndef delete_dqlite_node(delete_node, dqlite_ep):\n    if len(delete_node) > 0 and \"127.0.0.1\" not in delete_node[0]:\n        for ep in dqlite_ep:\n            try:\n                cmd = (\n                    \"{snappath}/bin/dqlite -s file://{dbdir}/cluster.yaml -c {dbdir}/cluster.crt \"\n                    \"-k {dbdir}/cluster.key -f json k8s\".format(\n                        snappath=snap_path, dbdir=cluster_dir\n                    ).split()\n                )\n                cmd.append(\".remove {}\".format(delete_node[0]))\n                subprocess.check_output(cmd)\n                break\n            except Exception as err:\n                print(\"Contacting node {} failed. Error:\".format(ep))\n                print(repr(err))\n                exit(2)\n\n\ndef remove_callback_token(node):\n    \"\"\"\n    Remove a callback token\n\n    :param node: the node\n    \"\"\"\n    tmp_file = \"{}.tmp\".format(callback_tokens_file)\n    if not os.path.isfile(callback_tokens_file):\n        open(callback_tokens_file, \"a+\")\n        os.chmod(callback_tokens_file, 0o600)\n    with open(tmp_file, \"w\") as backup_fp:\n        os.chmod(tmp_file, 0o600)\n        with open(callback_tokens_file, \"r+\") as callback_fp:\n            # Entries are of the format: 'node_hostname:agent_port token'\n            # We need to get the node_hostname part\n            for line in callback_fp:\n                parts = line.split(\":\")\n                if parts[0] == node:\n                    continue\n                else:\n                    backup_fp.write(line)\n\n    try_set_file_permissions(tmp_file)\n    shutil.move(tmp_file, callback_tokens_file)\n\n\n@click.command()\n@click.argument(\"node\", required=True)\n@click.option(\n    \"--force\",\n    is_flag=True,\n    required=False,\n    default=False,\n    help=\"Force the node removal operation. (default: false)\",\n)\ndef reset(node, force):\n    \"\"\"\n    Remove a node from the cluster\n    \"\"\"\n    if is_node_running_dqlite():\n        remove_dqlite_node(node, force)\n    else:\n        remove_node(node)\n    sys.exit(0)\n\n\nif __name__ == \"__main__\":\n    reset(prog_name=\"microk8s remove-node\")\n", "path": "scripts/wrappers/remove_node.py"}], "after_files": [{"content": "#!/usr/bin/python3\nimport json\nimport os\nimport shutil\nimport subprocess\nimport sys\n\nimport click\nimport netifaces\n\nfrom ipaddress import ip_address\n\nfrom common.cluster.utils import (\n    try_set_file_permissions,\n    is_node_running_dqlite,\n    is_token_auth_enabled,\n)\n\nsnap_path = os.environ.get(\"SNAP\")\nsnapdata_path = os.environ.get(\"SNAP_DATA\")\ncallback_tokens_file = \"{}/credentials/callback-tokens.txt\".format(snapdata_path)\n\ncluster_dir = \"{}/var/kubernetes/backend\".format(snapdata_path)\n\n\ndef remove_dqlite_node(node, force=False):\n    try:\n        # If node is an IP address, find the node name.\n        is_node_ip = True\n        try:\n            ip_address(node)\n        except ValueError:\n            is_node_ip = False\n\n        if is_node_ip:\n            node_info = subprocess.check_output(\n                \"{}/microk8s-kubectl.wrapper get no -o json\".format(snap_path).split()\n            )\n            info = json.loads(node_info.decode())\n            found = False\n            for n in info[\"items\"]:\n                if found:\n                    break\n                for a in n[\"status\"][\"addresses\"]:\n                    if a[\"type\"] == \"InternalIP\" and a[\"address\"] == node:\n                        node = n[\"metadata\"][\"name\"]\n                        found = True\n                        break\n\n        # Make sure this node exists\n        node_info = subprocess.check_output(\n            \"{}/microk8s-kubectl.wrapper get no {} -o json\".format(snap_path, node).split()\n        )\n        info = json.loads(node_info.decode())\n        node_address = None\n        for a in info[\"status\"][\"addresses\"]:\n            if a[\"type\"] == \"InternalIP\":\n                node_address = a[\"address\"]\n                break\n\n        if not node_address:\n            print(\"Node {} is not part of the cluster.\".format(node))\n            exit(1)\n\n        node_ep = None\n        my_ep, other_ep = get_dqlite_endpoints()\n        for ep in other_ep:\n            if ep.startswith(\"{}:\".format(node_address)):\n                node_ep = ep\n\n        if node_ep and force:\n            delete_dqlite_node([node_ep], my_ep)\n        elif node_ep and not force:\n            print(\n                \"Removal failed. Node {} is registered with dqlite. \"\n                \"Please, run first 'microk8s leave' on the departing node. \\n\"\n                \"If the node is not available anymore and will never attempt to join the cluster \"\n                \"in the future use the '--force' flag \\n\"\n                \"to unregister the node while removing it.\".format(node)\n            )\n            exit(1)\n\n    except subprocess.CalledProcessError:\n        print(\"Node {} does not exist in Kubernetes.\".format(node))\n        if force:\n            print(\"Attempting to remove {} from dqlite.\".format(node))\n            # Make sure we do not have the node in dqlite.\n            # We assume the IP is provided to denote the\n            my_ep, other_ep = get_dqlite_endpoints()\n            for ep in other_ep:\n                if ep.startswith(\"{}:\".format(node)):\n                    print(\"Removing node entry found in dqlite.\")\n                    delete_dqlite_node([ep], my_ep)\n        exit(1)\n\n    remove_node(node)\n\n\ndef remove_node(node):\n    try:\n        # Make sure this node exists\n        subprocess.check_call(\n            \"{}/microk8s-kubectl.wrapper get no {}\".format(snap_path, node).split(),\n            stdout=subprocess.DEVNULL,\n            stderr=subprocess.DEVNULL,\n        )\n    except subprocess.CalledProcessError:\n        print(\"Node {} does not exist.\".format(node))\n        exit(1)\n\n    if is_token_auth_enabled():\n        remove_kubelet_token(node)\n    remove_callback_token(node)\n    subprocess.check_call(\n        \"{}/microk8s-kubectl.wrapper delete no {}\".format(snap_path, node).split(),\n        stdout=subprocess.DEVNULL,\n        stderr=subprocess.DEVNULL,\n    )\n\n\ndef remove_kubelet_token(node):\n    \"\"\"\n    Remove a token for a node in the known tokens\n\n    :param node: the name of the node\n    \"\"\"\n    file = \"{}/credentials/known_tokens.csv\".format(snapdata_path)\n    backup_file = \"{}.backup\".format(file)\n    token = \"system:node:{}\".format(node)\n    # That is a critical section. We need to protect it.\n    with open(backup_file, \"w\") as back_fp:\n        with open(file, \"r\") as fp:\n            for _, line in enumerate(fp):\n                if token in line:\n                    continue\n                back_fp.write(\"{}\".format(line))\n\n    try_set_file_permissions(backup_file)\n    shutil.copyfile(backup_file, file)\n\n\ndef get_dqlite_endpoints():\n    \"\"\"\n    Return the endpoints the current node has on dqlite and the endpoints of the rest of the nodes.\n\n    :return: two lists with the endpoints\n    \"\"\"\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    )\n    data = json.loads(out.decode())\n    ep_addresses = []\n    for ep in data:\n        ep_addresses.append(ep[\"Address\"])\n    local_ips = []\n    for interface in netifaces.interfaces():\n        if netifaces.AF_INET not in netifaces.ifaddresses(interface):\n            continue\n        for link in netifaces.ifaddresses(interface)[netifaces.AF_INET]:\n            local_ips.append(link[\"addr\"])\n    my_ep = []\n    other_ep = []\n    for ep in ep_addresses:\n        found = False\n        for ip in local_ips:\n            if \"{}:\".format(ip) in ep:\n                my_ep.append(ep)\n                found = True\n        if not found:\n            other_ep.append(ep)\n\n    return my_ep, other_ep\n\n\ndef delete_dqlite_node(delete_node, dqlite_ep):\n    if len(delete_node) > 0 and \"127.0.0.1\" not in delete_node[0]:\n        for ep in dqlite_ep:\n            try:\n                cmd = (\n                    \"{snappath}/bin/dqlite -s file://{dbdir}/cluster.yaml -c {dbdir}/cluster.crt \"\n                    \"-k {dbdir}/cluster.key -f json k8s\".format(\n                        snappath=snap_path, dbdir=cluster_dir\n                    ).split()\n                )\n                cmd.append(\".remove {}\".format(delete_node[0]))\n                subprocess.check_output(cmd)\n                break\n            except Exception as err:\n                print(\"Contacting node {} failed. Error:\".format(ep))\n                print(repr(err))\n                exit(2)\n\n\ndef remove_callback_token(node):\n    \"\"\"\n    Remove a callback token\n\n    :param node: the node\n    \"\"\"\n    tmp_file = \"{}.tmp\".format(callback_tokens_file)\n    if not os.path.isfile(callback_tokens_file):\n        open(callback_tokens_file, \"a+\")\n        os.chmod(callback_tokens_file, 0o600)\n    with open(tmp_file, \"w\") as backup_fp:\n        os.chmod(tmp_file, 0o600)\n        with open(callback_tokens_file, \"r+\") as callback_fp:\n            # Entries are of the format: 'node_hostname:agent_port token'\n            # We need to get the node_hostname part\n            for line in callback_fp:\n                parts = line.split(\":\")\n                if parts[0] == node:\n                    continue\n                else:\n                    backup_fp.write(line)\n\n    try_set_file_permissions(tmp_file)\n    shutil.move(tmp_file, callback_tokens_file)\n\n\n@click.command()\n@click.argument(\"node\", required=True)\n@click.option(\n    \"--force\",\n    is_flag=True,\n    required=False,\n    default=False,\n    help=\"Force the node removal operation. (default: false)\",\n)\ndef reset(node, force):\n    \"\"\"\n    Remove a node from the cluster\n    \"\"\"\n    if is_node_running_dqlite():\n        remove_dqlite_node(node, force)\n    else:\n        remove_node(node)\n    sys.exit(0)\n\n\nif __name__ == \"__main__\":\n    reset(prog_name=\"microk8s remove-node\")\n", "path": "scripts/wrappers/remove_node.py"}]}