We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
latest/edge: server cert generation failure too verbose
```
Restarting cluster-agent to load new server certificate

error: error running snapctl: snap "microk8s" has "service-control" change in progress

Traceback (most recent call last):

  File "/snap/microk8s/3125/scripts/cluster/add_token.py", line 190, in <module>

    subprocess.check_call(["snapctl", "restart", "microk8s.daemon-cluster-agent"])

  File "/snap/microk8s/3125/usr/lib/python3.6/subprocess.py", line 311, in check_call

    raise CalledProcessError(retcode, cmd)

subprocess.CalledProcessError: Command '['snapctl', 'restart', 'microk8s.daemon-cluster-agent']' returned non-zero exit status 1.

```

The action failing is fine, but the call stack being printed is un-necessary 

--- 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/cluster/add_token.py`
Content:
```
1 import json
2 import yaml
3 import os
4 import sys
5 import time
6 import argparse
7 import subprocess
8 import hashlib
9 import ssl
10 import http.client
11 
12 from common.utils import is_node_running_dqlite
13 
14 try:
15     from secrets import token_hex
16 except ImportError:
17     from os import urandom
18 
19     def token_hex(nbytes=None):
20         return urandom(nbytes).hex()
21 
22 
23 cluster_tokens_file = os.path.expandvars("${SNAP_DATA}/credentials/cluster-tokens.txt")
24 utils_sh_file = os.path.expandvars("${SNAP}/actions/common/utils.sh")
25 token_with_expiry = "{}|{}\n"
26 token_without_expiry = "{}\n"
27 
28 
29 def add_token_with_expiry(token, file, ttl):
30     """
31     This method will add a token to the token file with or without expiry
32     Expiry time is in seconds.
33 
34     Format of the item in the file: <token>|<expiry in seconds>
35 
36     :param str token: The token to add to the file
37     :param str file: The file name for which the token will be written to
38     :param ttl: How long the token should last before expiry, represented in seconds.
39     """
40 
41     with open(file, "a+") as fp:
42         if ttl != -1:
43             expiry = int(round(time.time())) + ttl
44             fp.write(token_with_expiry.format(token, expiry))
45         else:
46             fp.write(token_without_expiry.format(token))
47 
48 
49 def run_util(*args, debug=False):
50     env = os.environ.copy()
51     prog = ["bash", utils_sh_file]
52     prog.extend(args)
53 
54     if debug:
55         print("\033[;1;32m+ %s\033[;0;0m" % " ".join(prog))
56 
57     result = subprocess.run(
58         prog,
59         stdin=subprocess.PIPE,
60         stdout=subprocess.PIPE,
61         stderr=subprocess.PIPE,
62         env=env,
63     )
64 
65     try:
66         result.check_returncode()
67     except subprocess.CalledProcessError:
68         print("Failed to call utility function.")
69         sys.exit(1)
70 
71     return result.stdout.decode("utf-8").strip()
72 
73 
74 def get_network_info():
75     """
76     Obtain machine IP address(es) and cluster agent port.
77     :return: tuple of default IP, all IPs, and cluster agent port
78     """
79     default_ip = run_util("get_default_ip")
80     all_ips = run_util("get_ips").split(" ")
81     port = run_util("cluster_agent_port")
82 
83     return (default_ip, all_ips, port)
84 
85 
86 def print_pretty(token, check):
87     default_ip, all_ips, port = get_network_info()
88 
89     print("From the node you wish to join to this cluster, run the following:")
90     print(f"microk8s join {default_ip}:{port}/{token}/{check}\n")
91 
92     if is_node_running_dqlite():
93         print(
94             "Use the '--worker' flag to join a node as a worker not running the control plane, eg:"
95         )
96         print(f"microk8s join {default_ip}:{port}/{token}/{check} --worker\n")
97 
98     print(
99         "If the node you are adding is not reachable through the default interface you can use one of the following:"
100     )
101     for ip in all_ips:
102         print(f"microk8s join {ip}:{port}/{token}/{check}")
103 
104 
105 def get_output_dict(token, check):
106     _, all_ips, port = get_network_info()
107     info = {
108         "token": f"{token}/{check}",
109         "urls": [f"{ip}:{port}/{token}/{check}" for ip in all_ips],
110     }
111     return info
112 
113 
114 def print_json(token, check):
115     info = get_output_dict(token, check)
116     print(json.dumps(info, indent=2))
117 
118 
119 def print_yaml(token, check):
120     info = get_output_dict(token, check)
121     print(yaml.dump(info, indent=2))
122 
123 
124 def print_short(token, check):
125     default_ip, all_ips, port = get_network_info()
126 
127     print(f"microk8s join {default_ip}:{port}/{token}/{check}")
128     for ip in all_ips:
129         print(f"microk8s join {ip}:{port}/{token}/{check}")
130 
131 
132 if __name__ == "__main__":
133 
134     # initiate the parser with a description
135     parser = argparse.ArgumentParser(
136         description="Produce a connection string for a node to join the cluster.",
137         prog="microk8s add-node",
138     )
139     parser.add_argument(
140         "--token-ttl",
141         "-l",
142         help="Specify how long the token is valid, before it expires. "
143         'Value of "-1" indicates that the token is usable only once '
144         "(i.e. after joining a node, the token becomes invalid)",
145         type=int,
146         default="-1",
147     )
148     parser.add_argument(
149         "--token",
150         "-t",
151         help="Specify the bootstrap token to add, must be 32 characters long. "
152         "Auto generates when empty.",
153     )
154     parser.add_argument(
155         "--format",
156         help="Format the output of the token in pretty, short, token, or token-check",
157         default="pretty",
158         choices={"pretty", "short", "token", "token-check", "json", "yaml"},
159     )
160 
161     # read arguments from the command line
162     args = parser.parse_args()
163 
164     ttl = args.token_ttl
165 
166     if args.token is not None:
167         token = args.token
168     else:
169         token = token_hex(16)
170 
171     if len(token) < 32:
172         print("Invalid token size.  It must be 32 characters long.")
173         exit(1)
174 
175     add_token_with_expiry(token, cluster_tokens_file, ttl)
176     check = run_util("server_cert_check")
177 
178     # NOTE(akolaitis): if the server certificate has changed after the cluster-agent
179     # service started, the server hash will be out of date. make sure to restart
180     # the cluster-agent in this case, otherwise the joining nodes will be unable
181     # to verify.
182     context = ssl._create_unverified_context()
183     conn = http.client.HTTPSConnection("127.0.0.1:25000", context=context)
184     conn.connect()
185     der_cert_bin = conn.sock.getpeercert(True)
186     conn.close()
187     peer_cert_hash = hashlib.sha256(der_cert_bin).hexdigest()
188     if not peer_cert_hash.startswith(check):
189         print("Restarting cluster-agent to load new server certificate")
190         subprocess.check_call(["snapctl", "restart", "microk8s.daemon-cluster-agent"])
191 
192     if args.format == "pretty":
193         print_pretty(token, check)
194     elif args.format == "short":
195         print_short(token, check)
196     elif args.format == "token-check":
197         print(f"{token}/{check}")
198     elif args.format == "json":
199         print_json(token, check)
200     elif args.format == "yaml":
201         print_yaml(token, check)
202     else:
203         print(token)
204
```
--- 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/cluster/add_token.py b/scripts/cluster/add_token.py
--- a/scripts/cluster/add_token.py
+++ b/scripts/cluster/add_token.py
@@ -5,9 +5,6 @@
 import time
 import argparse
 import subprocess
-import hashlib
-import ssl
-import http.client
 
 from common.utils import is_node_running_dqlite
 
@@ -175,20 +172,6 @@
     add_token_with_expiry(token, cluster_tokens_file, ttl)
     check = run_util("server_cert_check")
 
-    # NOTE(akolaitis): if the server certificate has changed after the cluster-agent
-    # service started, the server hash will be out of date. make sure to restart
-    # the cluster-agent in this case, otherwise the joining nodes will be unable
-    # to verify.
-    context = ssl._create_unverified_context()
-    conn = http.client.HTTPSConnection("127.0.0.1:25000", context=context)
-    conn.connect()
-    der_cert_bin = conn.sock.getpeercert(True)
-    conn.close()
-    peer_cert_hash = hashlib.sha256(der_cert_bin).hexdigest()
-    if not peer_cert_hash.startswith(check):
-        print("Restarting cluster-agent to load new server certificate")
-        subprocess.check_call(["snapctl", "restart", "microk8s.daemon-cluster-agent"])
-
     if args.format == "pretty":
         print_pretty(token, check)
     elif args.format == "short":

{"golden_diff": "diff --git a/scripts/cluster/add_token.py b/scripts/cluster/add_token.py\n--- a/scripts/cluster/add_token.py\n+++ b/scripts/cluster/add_token.py\n@@ -5,9 +5,6 @@\n import time\n import argparse\n import subprocess\n-import hashlib\n-import ssl\n-import http.client\n \n from common.utils import is_node_running_dqlite\n \n@@ -175,20 +172,6 @@\n     add_token_with_expiry(token, cluster_tokens_file, ttl)\n     check = run_util(\"server_cert_check\")\n \n-    # NOTE(akolaitis): if the server certificate has changed after the cluster-agent\n-    # service started, the server hash will be out of date. make sure to restart\n-    # the cluster-agent in this case, otherwise the joining nodes will be unable\n-    # to verify.\n-    context = ssl._create_unverified_context()\n-    conn = http.client.HTTPSConnection(\"127.0.0.1:25000\", context=context)\n-    conn.connect()\n-    der_cert_bin = conn.sock.getpeercert(True)\n-    conn.close()\n-    peer_cert_hash = hashlib.sha256(der_cert_bin).hexdigest()\n-    if not peer_cert_hash.startswith(check):\n-        print(\"Restarting cluster-agent to load new server certificate\")\n-        subprocess.check_call([\"snapctl\", \"restart\", \"microk8s.daemon-cluster-agent\"])\n-\n     if args.format == \"pretty\":\n         print_pretty(token, check)\n     elif args.format == \"short\":\n", "issue": "latest/edge: server cert generation failure too verbose\n```\r\nRestarting cluster-agent to load new server certificate\r\n\r\nerror: error running snapctl: snap \"microk8s\" has \"service-control\" change in progress\r\n\r\nTraceback (most recent call last):\r\n\r\n  File \"/snap/microk8s/3125/scripts/cluster/add_token.py\", line 190, in <module>\r\n\r\n    subprocess.check_call([\"snapctl\", \"restart\", \"microk8s.daemon-cluster-agent\"])\r\n\r\n  File \"/snap/microk8s/3125/usr/lib/python3.6/subprocess.py\", line 311, in check_call\r\n\r\n    raise CalledProcessError(retcode, cmd)\r\n\r\nsubprocess.CalledProcessError: Command '['snapctl', 'restart', 'microk8s.daemon-cluster-agent']' returned non-zero exit status 1.\r\n\r\n```\r\n\r\nThe action failing is fine, but the call stack being printed is un-necessary \n", "before_files": [{"content": "import json\nimport yaml\nimport os\nimport sys\nimport time\nimport argparse\nimport subprocess\nimport hashlib\nimport ssl\nimport http.client\n\nfrom common.utils import is_node_running_dqlite\n\ntry:\n    from secrets import token_hex\nexcept ImportError:\n    from os import urandom\n\n    def token_hex(nbytes=None):\n        return urandom(nbytes).hex()\n\n\ncluster_tokens_file = os.path.expandvars(\"${SNAP_DATA}/credentials/cluster-tokens.txt\")\nutils_sh_file = os.path.expandvars(\"${SNAP}/actions/common/utils.sh\")\ntoken_with_expiry = \"{}|{}\\n\"\ntoken_without_expiry = \"{}\\n\"\n\n\ndef add_token_with_expiry(token, file, ttl):\n    \"\"\"\n    This method will add a token to the token file with or without expiry\n    Expiry time is in seconds.\n\n    Format of the item in the file: <token>|<expiry in seconds>\n\n    :param str token: The token to add to the file\n    :param str file: The file name for which the token will be written to\n    :param ttl: How long the token should last before expiry, represented in seconds.\n    \"\"\"\n\n    with open(file, \"a+\") as fp:\n        if ttl != -1:\n            expiry = int(round(time.time())) + ttl\n            fp.write(token_with_expiry.format(token, expiry))\n        else:\n            fp.write(token_without_expiry.format(token))\n\n\ndef run_util(*args, debug=False):\n    env = os.environ.copy()\n    prog = [\"bash\", utils_sh_file]\n    prog.extend(args)\n\n    if debug:\n        print(\"\\033[;1;32m+ %s\\033[;0;0m\" % \" \".join(prog))\n\n    result = subprocess.run(\n        prog,\n        stdin=subprocess.PIPE,\n        stdout=subprocess.PIPE,\n        stderr=subprocess.PIPE,\n        env=env,\n    )\n\n    try:\n        result.check_returncode()\n    except subprocess.CalledProcessError:\n        print(\"Failed to call utility function.\")\n        sys.exit(1)\n\n    return result.stdout.decode(\"utf-8\").strip()\n\n\ndef get_network_info():\n    \"\"\"\n    Obtain machine IP address(es) and cluster agent port.\n    :return: tuple of default IP, all IPs, and cluster agent port\n    \"\"\"\n    default_ip = run_util(\"get_default_ip\")\n    all_ips = run_util(\"get_ips\").split(\" \")\n    port = run_util(\"cluster_agent_port\")\n\n    return (default_ip, all_ips, port)\n\n\ndef print_pretty(token, check):\n    default_ip, all_ips, port = get_network_info()\n\n    print(\"From the node you wish to join to this cluster, run the following:\")\n    print(f\"microk8s join {default_ip}:{port}/{token}/{check}\\n\")\n\n    if is_node_running_dqlite():\n        print(\n            \"Use the '--worker' flag to join a node as a worker not running the control plane, eg:\"\n        )\n        print(f\"microk8s join {default_ip}:{port}/{token}/{check} --worker\\n\")\n\n    print(\n        \"If the node you are adding is not reachable through the default interface you can use one of the following:\"\n    )\n    for ip in all_ips:\n        print(f\"microk8s join {ip}:{port}/{token}/{check}\")\n\n\ndef get_output_dict(token, check):\n    _, all_ips, port = get_network_info()\n    info = {\n        \"token\": f\"{token}/{check}\",\n        \"urls\": [f\"{ip}:{port}/{token}/{check}\" for ip in all_ips],\n    }\n    return info\n\n\ndef print_json(token, check):\n    info = get_output_dict(token, check)\n    print(json.dumps(info, indent=2))\n\n\ndef print_yaml(token, check):\n    info = get_output_dict(token, check)\n    print(yaml.dump(info, indent=2))\n\n\ndef print_short(token, check):\n    default_ip, all_ips, port = get_network_info()\n\n    print(f\"microk8s join {default_ip}:{port}/{token}/{check}\")\n    for ip in all_ips:\n        print(f\"microk8s join {ip}:{port}/{token}/{check}\")\n\n\nif __name__ == \"__main__\":\n\n    # initiate the parser with a description\n    parser = argparse.ArgumentParser(\n        description=\"Produce a connection string for a node to join the cluster.\",\n        prog=\"microk8s add-node\",\n    )\n    parser.add_argument(\n        \"--token-ttl\",\n        \"-l\",\n        help=\"Specify how long the token is valid, before it expires. \"\n        'Value of \"-1\" indicates that the token is usable only once '\n        \"(i.e. after joining a node, the token becomes invalid)\",\n        type=int,\n        default=\"-1\",\n    )\n    parser.add_argument(\n        \"--token\",\n        \"-t\",\n        help=\"Specify the bootstrap token to add, must be 32 characters long. \"\n        \"Auto generates when empty.\",\n    )\n    parser.add_argument(\n        \"--format\",\n        help=\"Format the output of the token in pretty, short, token, or token-check\",\n        default=\"pretty\",\n        choices={\"pretty\", \"short\", \"token\", \"token-check\", \"json\", \"yaml\"},\n    )\n\n    # read arguments from the command line\n    args = parser.parse_args()\n\n    ttl = args.token_ttl\n\n    if args.token is not None:\n        token = args.token\n    else:\n        token = token_hex(16)\n\n    if len(token) < 32:\n        print(\"Invalid token size.  It must be 32 characters long.\")\n        exit(1)\n\n    add_token_with_expiry(token, cluster_tokens_file, ttl)\n    check = run_util(\"server_cert_check\")\n\n    # NOTE(akolaitis): if the server certificate has changed after the cluster-agent\n    # service started, the server hash will be out of date. make sure to restart\n    # the cluster-agent in this case, otherwise the joining nodes will be unable\n    # to verify.\n    context = ssl._create_unverified_context()\n    conn = http.client.HTTPSConnection(\"127.0.0.1:25000\", context=context)\n    conn.connect()\n    der_cert_bin = conn.sock.getpeercert(True)\n    conn.close()\n    peer_cert_hash = hashlib.sha256(der_cert_bin).hexdigest()\n    if not peer_cert_hash.startswith(check):\n        print(\"Restarting cluster-agent to load new server certificate\")\n        subprocess.check_call([\"snapctl\", \"restart\", \"microk8s.daemon-cluster-agent\"])\n\n    if args.format == \"pretty\":\n        print_pretty(token, check)\n    elif args.format == \"short\":\n        print_short(token, check)\n    elif args.format == \"token-check\":\n        print(f\"{token}/{check}\")\n    elif args.format == \"json\":\n        print_json(token, check)\n    elif args.format == \"yaml\":\n        print_yaml(token, check)\n    else:\n        print(token)\n", "path": "scripts/cluster/add_token.py"}], "after_files": [{"content": "import json\nimport yaml\nimport os\nimport sys\nimport time\nimport argparse\nimport subprocess\n\nfrom common.utils import is_node_running_dqlite\n\ntry:\n    from secrets import token_hex\nexcept ImportError:\n    from os import urandom\n\n    def token_hex(nbytes=None):\n        return urandom(nbytes).hex()\n\n\ncluster_tokens_file = os.path.expandvars(\"${SNAP_DATA}/credentials/cluster-tokens.txt\")\nutils_sh_file = os.path.expandvars(\"${SNAP}/actions/common/utils.sh\")\ntoken_with_expiry = \"{}|{}\\n\"\ntoken_without_expiry = \"{}\\n\"\n\n\ndef add_token_with_expiry(token, file, ttl):\n    \"\"\"\n    This method will add a token to the token file with or without expiry\n    Expiry time is in seconds.\n\n    Format of the item in the file: <token>|<expiry in seconds>\n\n    :param str token: The token to add to the file\n    :param str file: The file name for which the token will be written to\n    :param ttl: How long the token should last before expiry, represented in seconds.\n    \"\"\"\n\n    with open(file, \"a+\") as fp:\n        if ttl != -1:\n            expiry = int(round(time.time())) + ttl\n            fp.write(token_with_expiry.format(token, expiry))\n        else:\n            fp.write(token_without_expiry.format(token))\n\n\ndef run_util(*args, debug=False):\n    env = os.environ.copy()\n    prog = [\"bash\", utils_sh_file]\n    prog.extend(args)\n\n    if debug:\n        print(\"\\033[;1;32m+ %s\\033[;0;0m\" % \" \".join(prog))\n\n    result = subprocess.run(\n        prog,\n        stdin=subprocess.PIPE,\n        stdout=subprocess.PIPE,\n        stderr=subprocess.PIPE,\n        env=env,\n    )\n\n    try:\n        result.check_returncode()\n    except subprocess.CalledProcessError:\n        print(\"Failed to call utility function.\")\n        sys.exit(1)\n\n    return result.stdout.decode(\"utf-8\").strip()\n\n\ndef get_network_info():\n    \"\"\"\n    Obtain machine IP address(es) and cluster agent port.\n    :return: tuple of default IP, all IPs, and cluster agent port\n    \"\"\"\n    default_ip = run_util(\"get_default_ip\")\n    all_ips = run_util(\"get_ips\").split(\" \")\n    port = run_util(\"cluster_agent_port\")\n\n    return (default_ip, all_ips, port)\n\n\ndef print_pretty(token, check):\n    default_ip, all_ips, port = get_network_info()\n\n    print(\"From the node you wish to join to this cluster, run the following:\")\n    print(f\"microk8s join {default_ip}:{port}/{token}/{check}\\n\")\n\n    if is_node_running_dqlite():\n        print(\n            \"Use the '--worker' flag to join a node as a worker not running the control plane, eg:\"\n        )\n        print(f\"microk8s join {default_ip}:{port}/{token}/{check} --worker\\n\")\n\n    print(\n        \"If the node you are adding is not reachable through the default interface you can use one of the following:\"\n    )\n    for ip in all_ips:\n        print(f\"microk8s join {ip}:{port}/{token}/{check}\")\n\n\ndef get_output_dict(token, check):\n    _, all_ips, port = get_network_info()\n    info = {\n        \"token\": f\"{token}/{check}\",\n        \"urls\": [f\"{ip}:{port}/{token}/{check}\" for ip in all_ips],\n    }\n    return info\n\n\ndef print_json(token, check):\n    info = get_output_dict(token, check)\n    print(json.dumps(info, indent=2))\n\n\ndef print_yaml(token, check):\n    info = get_output_dict(token, check)\n    print(yaml.dump(info, indent=2))\n\n\ndef print_short(token, check):\n    default_ip, all_ips, port = get_network_info()\n\n    print(f\"microk8s join {default_ip}:{port}/{token}/{check}\")\n    for ip in all_ips:\n        print(f\"microk8s join {ip}:{port}/{token}/{check}\")\n\n\nif __name__ == \"__main__\":\n\n    # initiate the parser with a description\n    parser = argparse.ArgumentParser(\n        description=\"Produce a connection string for a node to join the cluster.\",\n        prog=\"microk8s add-node\",\n    )\n    parser.add_argument(\n        \"--token-ttl\",\n        \"-l\",\n        help=\"Specify how long the token is valid, before it expires. \"\n        'Value of \"-1\" indicates that the token is usable only once '\n        \"(i.e. after joining a node, the token becomes invalid)\",\n        type=int,\n        default=\"-1\",\n    )\n    parser.add_argument(\n        \"--token\",\n        \"-t\",\n        help=\"Specify the bootstrap token to add, must be 32 characters long. \"\n        \"Auto generates when empty.\",\n    )\n    parser.add_argument(\n        \"--format\",\n        help=\"Format the output of the token in pretty, short, token, or token-check\",\n        default=\"pretty\",\n        choices={\"pretty\", \"short\", \"token\", \"token-check\", \"json\", \"yaml\"},\n    )\n\n    # read arguments from the command line\n    args = parser.parse_args()\n\n    ttl = args.token_ttl\n\n    if args.token is not None:\n        token = args.token\n    else:\n        token = token_hex(16)\n\n    if len(token) < 32:\n        print(\"Invalid token size.  It must be 32 characters long.\")\n        exit(1)\n\n    add_token_with_expiry(token, cluster_tokens_file, ttl)\n    check = run_util(\"server_cert_check\")\n\n    if args.format == \"pretty\":\n        print_pretty(token, check)\n    elif args.format == \"short\":\n        print_short(token, check)\n    elif args.format == \"token-check\":\n        print(f\"{token}/{check}\")\n    elif args.format == \"json\":\n        print_json(token, check)\n    elif args.format == \"yaml\":\n        print_yaml(token, check)\n    else:\n        print(token)\n", "path": "scripts/cluster/add_token.py"}]}