We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
buildbot-worker doesn't support ipv6 address for master connection
I'm trying the following command line:
```shell
/usr/bin/buildbot-worker create-worker . "[2001\:\:324]:9989" test toto
```
But it gives the following error:
```
Traceback (most recent call last):
  File "/usr/bin/buildbot-worker", line 11, in <module>
    load_entry_point('buildbot-worker==2.0.1.post2', 'console_scripts', 'buildbot-worker')()
  File "/usr/lib/python3/dist-packages/buildbot_worker/scripts/runner.py", line 238, in run
    config.parseOptions()
  File "/usr/lib/python3/dist-packages/twisted/python/usage.py", line 267, in parseOptions
    self.subOptions.parseOptions(rest)
  File "/usr/lib/python3/dist-packages/twisted/python/usage.py", line 273, in parseOptions
    self.parseArgs(*args)
  File "/usr/lib/python3/dist-packages/buildbot_worker/scripts/runner.py", line 183, in parseArgs
    self['host'], self['port'] = self.validateMasterArgument(master)
  File "/usr/lib/python3/dist-packages/buildbot_worker/scripts/runner.py", line 161, in validateMasterArgument
    master, port = master_arg.split(":")
ValueError: too many values to unpack (expected 2)
```

Is Ipv6 address supposed to be supported?


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `worker/buildbot_worker/scripts/runner.py`
Content:
```
1 # This file is part of Buildbot.  Buildbot is free software: you can
2 # redistribute it and/or modify it under the terms of the GNU General Public
3 # License as published by the Free Software Foundation, version 2.
4 #
5 # This program is distributed in the hope that it will be useful, but WITHOUT
6 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
7 # FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
8 # details.
9 #
10 # You should have received a copy of the GNU General Public License along with
11 # this program; if not, write to the Free Software Foundation, Inc., 51
12 # Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
13 #
14 # Copyright Buildbot Team Members
15 
16 # N.B.: don't import anything that might pull in a reactor yet. Some of our
17 # subcommands want to load modules that need the gtk reactor.
18 
19 from __future__ import absolute_import
20 from __future__ import division
21 from __future__ import print_function
22 
23 import os
24 import re
25 import sys
26 import textwrap
27 
28 from twisted.python import log
29 from twisted.python import reflect
30 from twisted.python import usage
31 
32 # the create/start/stop commands should all be run as the same user,
33 # preferably a separate 'buildbot' account.
34 
35 # Note that the terms 'options' and 'config' are used interchangeably here - in
36 # fact, they are interchanged several times.  Caveat legator.
37 
38 
39 class MakerBase(usage.Options):
40     optFlags = [
41         ['help', 'h', "Display this message"],
42         ["quiet", "q", "Do not emit the commands being run"],
43     ]
44 
45     longdesc = textwrap.dedent("""
46     Operates upon the specified <basedir> (or the current directory, if not
47     specified).
48     """)
49 
50     # on tab completion, suggest directories as first argument
51     if hasattr(usage, 'Completions'):
52         # only set completion suggestion if running with
53         # twisted version (>=11.1.0) that supports it
54         compData = usage.Completions(
55             extraActions=[usage.CompleteDirs(descr="worker base directory")])
56 
57     opt_h = usage.Options.opt_help
58 
59     def parseArgs(self, *args):
60         if args:
61             self['basedir'] = args[0]
62         else:
63             # Use the current directory if no basedir was specified.
64             self['basedir'] = os.getcwd()
65         if len(args) > 1:
66             raise usage.UsageError("I wasn't expecting so many arguments")
67 
68     def postOptions(self):
69         self['basedir'] = os.path.abspath(self['basedir'])
70 
71 
72 class StartOptions(MakerBase):
73     subcommandFunction = "buildbot_worker.scripts.start.startCommand"
74     optFlags = [
75         ['quiet', 'q', "Don't display startup log messages"],
76         ['nodaemon', None, "Don't daemonize (stay in foreground)"],
77     ]
78 
79     def getSynopsis(self):
80         return "Usage:    buildbot-worker start [<basedir>]"
81 
82 
83 class StopOptions(MakerBase):
84     subcommandFunction = "buildbot_worker.scripts.stop.stop"
85 
86     def getSynopsis(self):
87         return "Usage:    buildbot-worker stop [<basedir>]"
88 
89 
90 class RestartOptions(MakerBase):
91     subcommandFunction = "buildbot_worker.scripts.restart.restart"
92     optFlags = [
93         ['quiet', 'q', "Don't display startup log messages"],
94         ['nodaemon', None, "Don't daemonize (stay in foreground)"],
95     ]
96 
97     def getSynopsis(self):
98         return "Usage:    buildbot-worker restart [<basedir>]"
99 
100 
101 class CreateWorkerOptions(MakerBase):
102     subcommandFunction = "buildbot_worker.scripts.create_worker.createWorker"
103     optFlags = [
104         ["force", "f", "Re-use an existing directory"],
105         ["relocatable", "r",
106          "Create a relocatable buildbot.tac"],
107         ["no-logrotate", "n",
108          "Do not permit buildmaster rotate logs by itself"],
109         ['use-tls', None,
110          "Uses TLS to connect to master"],
111         ['delete-leftover-dirs', None,
112          'Delete folders that are not required by the master on connection'],
113     ]
114     optParameters = [
115         ["keepalive", "k", 600,
116          "Interval at which keepalives should be sent (in seconds)"],
117         ["umask", None, "None",
118          "controls permissions of generated files. "
119          "Use --umask=0o22 to be world-readable"],
120         ["maxdelay", None, 300,
121          "Maximum time between connection attempts"],
122         ["maxretries", None, 'None',
123          "Maximum number of retries before worker shutdown"],
124         ["numcpus", None, "None",
125          "Number of available cpus to use on a build. "],
126         ["log-size", "s", "10000000",
127          "size at which to rotate twisted log files"],
128         ["log-count", "l", "10",
129          "limit the number of kept old twisted log files "
130          "(None for unlimited)"],
131         ["allow-shutdown", "a", None,
132          "Allows the worker to initiate a graceful shutdown. One of "
133          "'signal' or 'file'"]
134     ]
135 
136     longdesc = textwrap.dedent("""
137     This command creates a buildbot worker directory and buildbot.tac
138     file. The bot will use the <name> and <passwd> arguments to authenticate
139     itself when connecting to the master. All commands are run in a
140     build-specific subdirectory of <basedir>. <master> is a string of the
141     form 'hostname[:port]', and specifies where the buildmaster can be reached.
142     port defaults to 9989.
143 
144     The appropriate values for <name>, <passwd>, and <master> should be
145     provided to you by the buildmaster administrator. You must choose <basedir>
146     yourself.
147     """)
148 
149     def validateMasterArgument(self, master_arg):
150         """
151         Parse the <master> argument.
152 
153         @param master_arg: the <master> argument to parse
154 
155         @return: tuple of master's host and port
156         @raise UsageError: on errors parsing the argument
157         """
158         if master_arg[:5] == "http:":
159             raise usage.UsageError("<master> is not a URL - do not use URL")
160 
161         if ":" not in master_arg:
162             master = master_arg
163             port = 9989
164         else:
165             master, port = master_arg.split(":")
166 
167         if not master:
168             raise usage.UsageError("invalid <master> argument '{}'".format(
169                                    master_arg))
170         try:
171             port = int(port)
172         except ValueError:
173             raise usage.UsageError("invalid master port '{}', "
174                                    "needs to be a number".format(port))
175 
176         return master, port
177 
178     def getSynopsis(self):
179         return "Usage:    buildbot-worker create-worker " \
180             "[options] <basedir> <master> <name> <passwd>"
181 
182     def parseArgs(self, *args):
183         if len(args) != 4:
184             raise usage.UsageError("incorrect number of arguments")
185         basedir, master, name, passwd = args
186         self['basedir'] = basedir
187         self['host'], self['port'] = self.validateMasterArgument(master)
188         self['name'] = name
189         self['passwd'] = passwd
190 
191     def postOptions(self):
192         MakerBase.postOptions(self)
193 
194         # check and convert numeric parameters
195         for argument in ["keepalive", "maxdelay", "log-size"]:
196             try:
197                 self[argument] = int(self[argument])
198             except ValueError:
199                 raise usage.UsageError("{} parameter needs to be a number".format(
200                                        argument))
201 
202         for argument in ["log-count", "maxretries", "umask", "numcpus"]:
203             if not re.match(r'^(0o)?\d+$', self[argument]) and \
204                     self[argument] != 'None':
205                 raise usage.UsageError("{} parameter needs to be a number"
206                                     " or None".format(argument))
207 
208         if self['allow-shutdown'] not in [None, 'signal', 'file']:
209             raise usage.UsageError("allow-shutdown needs to be one of"
210                                    " 'signal' or 'file'")
211 
212 
213 class Options(usage.Options):
214     synopsis = "Usage:    buildbot-worker <command> [command options]"
215 
216     subCommands = [
217         # the following are all admin commands
218         ['create-worker', None, CreateWorkerOptions,
219          "Create and populate a directory for a new worker"],
220         ['start', None, StartOptions, "Start a worker"],
221         ['stop', None, StopOptions, "Stop a worker"],
222         ['restart', None, RestartOptions,
223          "Restart a worker"],
224     ]
225 
226     def opt_version(self):
227         import buildbot_worker  # pylint: disable=import-outside-toplevel
228         print("worker version: {}".format(buildbot_worker.version))
229         usage.Options.opt_version(self)
230 
231     def opt_verbose(self):
232         log.startLogging(sys.stderr)
233 
234     def postOptions(self):
235         if not hasattr(self, 'subOptions'):
236             raise usage.UsageError("must specify a command")
237 
238 
239 def run():
240     config = Options()
241     try:
242         config.parseOptions()
243     except usage.error as e:
244         print("{}:  {}".format(sys.argv[0], e))
245         print()
246         c = getattr(config, 'subOptions', config)
247         print(str(c))
248         sys.exit(1)
249 
250     subconfig = config.subOptions
251     subcommandFunction = reflect.namedObject(subconfig.subcommandFunction)
252     sys.exit(subcommandFunction(subconfig))
253
```
--- 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/worker/buildbot_worker/scripts/runner.py b/worker/buildbot_worker/scripts/runner.py
--- a/worker/buildbot_worker/scripts/runner.py
+++ b/worker/buildbot_worker/scripts/runner.py
@@ -158,11 +158,24 @@
         if master_arg[:5] == "http:":
             raise usage.UsageError("<master> is not a URL - do not use URL")
 
-        if ":" not in master_arg:
+        if master_arg.startswith("[") and "]" in master_arg:
+            # detect ipv6 address with format [2001:1:2:3:4::1]:4321
+            master, port_tmp = master_arg.split("]")
+            master = master[1:]
+            if ":" not in port_tmp:
+                port = 9989
+            else:
+                port = port_tmp.split(":")[1]
+
+        elif ":" not in master_arg:
             master = master_arg
             port = 9989
         else:
-            master, port = master_arg.split(":")
+            try:
+                master, port = master_arg.split(":")
+            except ValueError:
+                raise usage.UsageError("invalid <master> argument '{}', "
+                                       "if it is an ipv6 address, it must be enclosed by []".format(master_arg))
 
         if not master:
             raise usage.UsageError("invalid <master> argument '{}'".format(

{"golden_diff": "diff --git a/worker/buildbot_worker/scripts/runner.py b/worker/buildbot_worker/scripts/runner.py\n--- a/worker/buildbot_worker/scripts/runner.py\n+++ b/worker/buildbot_worker/scripts/runner.py\n@@ -158,11 +158,24 @@\n         if master_arg[:5] == \"http:\":\n             raise usage.UsageError(\"<master> is not a URL - do not use URL\")\n \n-        if \":\" not in master_arg:\n+        if master_arg.startswith(\"[\") and \"]\" in master_arg:\n+            # detect ipv6 address with format [2001:1:2:3:4::1]:4321\n+            master, port_tmp = master_arg.split(\"]\")\n+            master = master[1:]\n+            if \":\" not in port_tmp:\n+                port = 9989\n+            else:\n+                port = port_tmp.split(\":\")[1]\n+\n+        elif \":\" not in master_arg:\n             master = master_arg\n             port = 9989\n         else:\n-            master, port = master_arg.split(\":\")\n+            try:\n+                master, port = master_arg.split(\":\")\n+            except ValueError:\n+                raise usage.UsageError(\"invalid <master> argument '{}', \"\n+                                       \"if it is an ipv6 address, it must be enclosed by []\".format(master_arg))\n \n         if not master:\n             raise usage.UsageError(\"invalid <master> argument '{}'\".format(\n", "issue": "buildbot-worker doesn't support ipv6 address for master connection\nI'm trying the following command line:\r\n```shell\r\n/usr/bin/buildbot-worker create-worker . \"[2001\\:\\:324]:9989\" test toto\r\n```\r\nBut it gives the following error:\r\n```\r\nTraceback (most recent call last):\r\n  File \"/usr/bin/buildbot-worker\", line 11, in <module>\r\n    load_entry_point('buildbot-worker==2.0.1.post2', 'console_scripts', 'buildbot-worker')()\r\n  File \"/usr/lib/python3/dist-packages/buildbot_worker/scripts/runner.py\", line 238, in run\r\n    config.parseOptions()\r\n  File \"/usr/lib/python3/dist-packages/twisted/python/usage.py\", line 267, in parseOptions\r\n    self.subOptions.parseOptions(rest)\r\n  File \"/usr/lib/python3/dist-packages/twisted/python/usage.py\", line 273, in parseOptions\r\n    self.parseArgs(*args)\r\n  File \"/usr/lib/python3/dist-packages/buildbot_worker/scripts/runner.py\", line 183, in parseArgs\r\n    self['host'], self['port'] = self.validateMasterArgument(master)\r\n  File \"/usr/lib/python3/dist-packages/buildbot_worker/scripts/runner.py\", line 161, in validateMasterArgument\r\n    master, port = master_arg.split(\":\")\r\nValueError: too many values to unpack (expected 2)\r\n```\r\n\r\nIs Ipv6 address supposed to be supported?\r\n\n", "before_files": [{"content": "# This file is part of Buildbot.  Buildbot is free software: you can\n# redistribute it and/or modify it under the terms of the GNU General Public\n# License as published by the Free Software Foundation, version 2.\n#\n# This program is distributed in the hope that it will be useful, but WITHOUT\n# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS\n# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more\n# details.\n#\n# You should have received a copy of the GNU General Public License along with\n# this program; if not, write to the Free Software Foundation, Inc., 51\n# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n#\n# Copyright Buildbot Team Members\n\n# N.B.: don't import anything that might pull in a reactor yet. Some of our\n# subcommands want to load modules that need the gtk reactor.\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\n\nimport os\nimport re\nimport sys\nimport textwrap\n\nfrom twisted.python import log\nfrom twisted.python import reflect\nfrom twisted.python import usage\n\n# the create/start/stop commands should all be run as the same user,\n# preferably a separate 'buildbot' account.\n\n# Note that the terms 'options' and 'config' are used interchangeably here - in\n# fact, they are interchanged several times.  Caveat legator.\n\n\nclass MakerBase(usage.Options):\n    optFlags = [\n        ['help', 'h', \"Display this message\"],\n        [\"quiet\", \"q\", \"Do not emit the commands being run\"],\n    ]\n\n    longdesc = textwrap.dedent(\"\"\"\n    Operates upon the specified <basedir> (or the current directory, if not\n    specified).\n    \"\"\")\n\n    # on tab completion, suggest directories as first argument\n    if hasattr(usage, 'Completions'):\n        # only set completion suggestion if running with\n        # twisted version (>=11.1.0) that supports it\n        compData = usage.Completions(\n            extraActions=[usage.CompleteDirs(descr=\"worker base directory\")])\n\n    opt_h = usage.Options.opt_help\n\n    def parseArgs(self, *args):\n        if args:\n            self['basedir'] = args[0]\n        else:\n            # Use the current directory if no basedir was specified.\n            self['basedir'] = os.getcwd()\n        if len(args) > 1:\n            raise usage.UsageError(\"I wasn't expecting so many arguments\")\n\n    def postOptions(self):\n        self['basedir'] = os.path.abspath(self['basedir'])\n\n\nclass StartOptions(MakerBase):\n    subcommandFunction = \"buildbot_worker.scripts.start.startCommand\"\n    optFlags = [\n        ['quiet', 'q', \"Don't display startup log messages\"],\n        ['nodaemon', None, \"Don't daemonize (stay in foreground)\"],\n    ]\n\n    def getSynopsis(self):\n        return \"Usage:    buildbot-worker start [<basedir>]\"\n\n\nclass StopOptions(MakerBase):\n    subcommandFunction = \"buildbot_worker.scripts.stop.stop\"\n\n    def getSynopsis(self):\n        return \"Usage:    buildbot-worker stop [<basedir>]\"\n\n\nclass RestartOptions(MakerBase):\n    subcommandFunction = \"buildbot_worker.scripts.restart.restart\"\n    optFlags = [\n        ['quiet', 'q', \"Don't display startup log messages\"],\n        ['nodaemon', None, \"Don't daemonize (stay in foreground)\"],\n    ]\n\n    def getSynopsis(self):\n        return \"Usage:    buildbot-worker restart [<basedir>]\"\n\n\nclass CreateWorkerOptions(MakerBase):\n    subcommandFunction = \"buildbot_worker.scripts.create_worker.createWorker\"\n    optFlags = [\n        [\"force\", \"f\", \"Re-use an existing directory\"],\n        [\"relocatable\", \"r\",\n         \"Create a relocatable buildbot.tac\"],\n        [\"no-logrotate\", \"n\",\n         \"Do not permit buildmaster rotate logs by itself\"],\n        ['use-tls', None,\n         \"Uses TLS to connect to master\"],\n        ['delete-leftover-dirs', None,\n         'Delete folders that are not required by the master on connection'],\n    ]\n    optParameters = [\n        [\"keepalive\", \"k\", 600,\n         \"Interval at which keepalives should be sent (in seconds)\"],\n        [\"umask\", None, \"None\",\n         \"controls permissions of generated files. \"\n         \"Use --umask=0o22 to be world-readable\"],\n        [\"maxdelay\", None, 300,\n         \"Maximum time between connection attempts\"],\n        [\"maxretries\", None, 'None',\n         \"Maximum number of retries before worker shutdown\"],\n        [\"numcpus\", None, \"None\",\n         \"Number of available cpus to use on a build. \"],\n        [\"log-size\", \"s\", \"10000000\",\n         \"size at which to rotate twisted log files\"],\n        [\"log-count\", \"l\", \"10\",\n         \"limit the number of kept old twisted log files \"\n         \"(None for unlimited)\"],\n        [\"allow-shutdown\", \"a\", None,\n         \"Allows the worker to initiate a graceful shutdown. One of \"\n         \"'signal' or 'file'\"]\n    ]\n\n    longdesc = textwrap.dedent(\"\"\"\n    This command creates a buildbot worker directory and buildbot.tac\n    file. The bot will use the <name> and <passwd> arguments to authenticate\n    itself when connecting to the master. All commands are run in a\n    build-specific subdirectory of <basedir>. <master> is a string of the\n    form 'hostname[:port]', and specifies where the buildmaster can be reached.\n    port defaults to 9989.\n\n    The appropriate values for <name>, <passwd>, and <master> should be\n    provided to you by the buildmaster administrator. You must choose <basedir>\n    yourself.\n    \"\"\")\n\n    def validateMasterArgument(self, master_arg):\n        \"\"\"\n        Parse the <master> argument.\n\n        @param master_arg: the <master> argument to parse\n\n        @return: tuple of master's host and port\n        @raise UsageError: on errors parsing the argument\n        \"\"\"\n        if master_arg[:5] == \"http:\":\n            raise usage.UsageError(\"<master> is not a URL - do not use URL\")\n\n        if \":\" not in master_arg:\n            master = master_arg\n            port = 9989\n        else:\n            master, port = master_arg.split(\":\")\n\n        if not master:\n            raise usage.UsageError(\"invalid <master> argument '{}'\".format(\n                                   master_arg))\n        try:\n            port = int(port)\n        except ValueError:\n            raise usage.UsageError(\"invalid master port '{}', \"\n                                   \"needs to be a number\".format(port))\n\n        return master, port\n\n    def getSynopsis(self):\n        return \"Usage:    buildbot-worker create-worker \" \\\n            \"[options] <basedir> <master> <name> <passwd>\"\n\n    def parseArgs(self, *args):\n        if len(args) != 4:\n            raise usage.UsageError(\"incorrect number of arguments\")\n        basedir, master, name, passwd = args\n        self['basedir'] = basedir\n        self['host'], self['port'] = self.validateMasterArgument(master)\n        self['name'] = name\n        self['passwd'] = passwd\n\n    def postOptions(self):\n        MakerBase.postOptions(self)\n\n        # check and convert numeric parameters\n        for argument in [\"keepalive\", \"maxdelay\", \"log-size\"]:\n            try:\n                self[argument] = int(self[argument])\n            except ValueError:\n                raise usage.UsageError(\"{} parameter needs to be a number\".format(\n                                       argument))\n\n        for argument in [\"log-count\", \"maxretries\", \"umask\", \"numcpus\"]:\n            if not re.match(r'^(0o)?\\d+$', self[argument]) and \\\n                    self[argument] != 'None':\n                raise usage.UsageError(\"{} parameter needs to be a number\"\n                                    \" or None\".format(argument))\n\n        if self['allow-shutdown'] not in [None, 'signal', 'file']:\n            raise usage.UsageError(\"allow-shutdown needs to be one of\"\n                                   \" 'signal' or 'file'\")\n\n\nclass Options(usage.Options):\n    synopsis = \"Usage:    buildbot-worker <command> [command options]\"\n\n    subCommands = [\n        # the following are all admin commands\n        ['create-worker', None, CreateWorkerOptions,\n         \"Create and populate a directory for a new worker\"],\n        ['start', None, StartOptions, \"Start a worker\"],\n        ['stop', None, StopOptions, \"Stop a worker\"],\n        ['restart', None, RestartOptions,\n         \"Restart a worker\"],\n    ]\n\n    def opt_version(self):\n        import buildbot_worker  # pylint: disable=import-outside-toplevel\n        print(\"worker version: {}\".format(buildbot_worker.version))\n        usage.Options.opt_version(self)\n\n    def opt_verbose(self):\n        log.startLogging(sys.stderr)\n\n    def postOptions(self):\n        if not hasattr(self, 'subOptions'):\n            raise usage.UsageError(\"must specify a command\")\n\n\ndef run():\n    config = Options()\n    try:\n        config.parseOptions()\n    except usage.error as e:\n        print(\"{}:  {}\".format(sys.argv[0], e))\n        print()\n        c = getattr(config, 'subOptions', config)\n        print(str(c))\n        sys.exit(1)\n\n    subconfig = config.subOptions\n    subcommandFunction = reflect.namedObject(subconfig.subcommandFunction)\n    sys.exit(subcommandFunction(subconfig))\n", "path": "worker/buildbot_worker/scripts/runner.py"}], "after_files": [{"content": "# This file is part of Buildbot.  Buildbot is free software: you can\n# redistribute it and/or modify it under the terms of the GNU General Public\n# License as published by the Free Software Foundation, version 2.\n#\n# This program is distributed in the hope that it will be useful, but WITHOUT\n# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS\n# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more\n# details.\n#\n# You should have received a copy of the GNU General Public License along with\n# this program; if not, write to the Free Software Foundation, Inc., 51\n# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n#\n# Copyright Buildbot Team Members\n\n# N.B.: don't import anything that might pull in a reactor yet. Some of our\n# subcommands want to load modules that need the gtk reactor.\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\n\nimport os\nimport re\nimport sys\nimport textwrap\n\nfrom twisted.python import log\nfrom twisted.python import reflect\nfrom twisted.python import usage\n\n# the create/start/stop commands should all be run as the same user,\n# preferably a separate 'buildbot' account.\n\n# Note that the terms 'options' and 'config' are used interchangeably here - in\n# fact, they are interchanged several times.  Caveat legator.\n\n\nclass MakerBase(usage.Options):\n    optFlags = [\n        ['help', 'h', \"Display this message\"],\n        [\"quiet\", \"q\", \"Do not emit the commands being run\"],\n    ]\n\n    longdesc = textwrap.dedent(\"\"\"\n    Operates upon the specified <basedir> (or the current directory, if not\n    specified).\n    \"\"\")\n\n    # on tab completion, suggest directories as first argument\n    if hasattr(usage, 'Completions'):\n        # only set completion suggestion if running with\n        # twisted version (>=11.1.0) that supports it\n        compData = usage.Completions(\n            extraActions=[usage.CompleteDirs(descr=\"worker base directory\")])\n\n    opt_h = usage.Options.opt_help\n\n    def parseArgs(self, *args):\n        if args:\n            self['basedir'] = args[0]\n        else:\n            # Use the current directory if no basedir was specified.\n            self['basedir'] = os.getcwd()\n        if len(args) > 1:\n            raise usage.UsageError(\"I wasn't expecting so many arguments\")\n\n    def postOptions(self):\n        self['basedir'] = os.path.abspath(self['basedir'])\n\n\nclass StartOptions(MakerBase):\n    subcommandFunction = \"buildbot_worker.scripts.start.startCommand\"\n    optFlags = [\n        ['quiet', 'q', \"Don't display startup log messages\"],\n        ['nodaemon', None, \"Don't daemonize (stay in foreground)\"],\n    ]\n\n    def getSynopsis(self):\n        return \"Usage:    buildbot-worker start [<basedir>]\"\n\n\nclass StopOptions(MakerBase):\n    subcommandFunction = \"buildbot_worker.scripts.stop.stop\"\n\n    def getSynopsis(self):\n        return \"Usage:    buildbot-worker stop [<basedir>]\"\n\n\nclass RestartOptions(MakerBase):\n    subcommandFunction = \"buildbot_worker.scripts.restart.restart\"\n    optFlags = [\n        ['quiet', 'q', \"Don't display startup log messages\"],\n        ['nodaemon', None, \"Don't daemonize (stay in foreground)\"],\n    ]\n\n    def getSynopsis(self):\n        return \"Usage:    buildbot-worker restart [<basedir>]\"\n\n\nclass CreateWorkerOptions(MakerBase):\n    subcommandFunction = \"buildbot_worker.scripts.create_worker.createWorker\"\n    optFlags = [\n        [\"force\", \"f\", \"Re-use an existing directory\"],\n        [\"relocatable\", \"r\",\n         \"Create a relocatable buildbot.tac\"],\n        [\"no-logrotate\", \"n\",\n         \"Do not permit buildmaster rotate logs by itself\"],\n        ['use-tls', None,\n         \"Uses TLS to connect to master\"],\n        ['delete-leftover-dirs', None,\n         'Delete folders that are not required by the master on connection'],\n    ]\n    optParameters = [\n        [\"keepalive\", \"k\", 600,\n         \"Interval at which keepalives should be sent (in seconds)\"],\n        [\"umask\", None, \"None\",\n         \"controls permissions of generated files. \"\n         \"Use --umask=0o22 to be world-readable\"],\n        [\"maxdelay\", None, 300,\n         \"Maximum time between connection attempts\"],\n        [\"maxretries\", None, 'None',\n         \"Maximum number of retries before worker shutdown\"],\n        [\"numcpus\", None, \"None\",\n         \"Number of available cpus to use on a build. \"],\n        [\"log-size\", \"s\", \"10000000\",\n         \"size at which to rotate twisted log files\"],\n        [\"log-count\", \"l\", \"10\",\n         \"limit the number of kept old twisted log files \"\n         \"(None for unlimited)\"],\n        [\"allow-shutdown\", \"a\", None,\n         \"Allows the worker to initiate a graceful shutdown. One of \"\n         \"'signal' or 'file'\"]\n    ]\n\n    longdesc = textwrap.dedent(\"\"\"\n    This command creates a buildbot worker directory and buildbot.tac\n    file. The bot will use the <name> and <passwd> arguments to authenticate\n    itself when connecting to the master. All commands are run in a\n    build-specific subdirectory of <basedir>. <master> is a string of the\n    form 'hostname[:port]', and specifies where the buildmaster can be reached.\n    port defaults to 9989.\n\n    The appropriate values for <name>, <passwd>, and <master> should be\n    provided to you by the buildmaster administrator. You must choose <basedir>\n    yourself.\n    \"\"\")\n\n    def validateMasterArgument(self, master_arg):\n        \"\"\"\n        Parse the <master> argument.\n\n        @param master_arg: the <master> argument to parse\n\n        @return: tuple of master's host and port\n        @raise UsageError: on errors parsing the argument\n        \"\"\"\n        if master_arg[:5] == \"http:\":\n            raise usage.UsageError(\"<master> is not a URL - do not use URL\")\n\n        if master_arg.startswith(\"[\") and \"]\" in master_arg:\n            # detect ipv6 address with format [2001:1:2:3:4::1]:4321\n            master, port_tmp = master_arg.split(\"]\")\n            master = master[1:]\n            if \":\" not in port_tmp:\n                port = 9989\n            else:\n                port = port_tmp.split(\":\")[1]\n\n        elif \":\" not in master_arg:\n            master = master_arg\n            port = 9989\n        else:\n            try:\n                master, port = master_arg.split(\":\")\n            except ValueError:\n                raise usage.UsageError(\"invalid <master> argument '{}', \"\n                                       \"if it is an ipv6 address, it must be enclosed by []\".format(master_arg))\n\n        if not master:\n            raise usage.UsageError(\"invalid <master> argument '{}'\".format(\n                                   master_arg))\n        try:\n            port = int(port)\n        except ValueError:\n            raise usage.UsageError(\"invalid master port '{}', \"\n                                   \"needs to be a number\".format(port))\n\n        return master, port\n\n    def getSynopsis(self):\n        return \"Usage:    buildbot-worker create-worker \" \\\n            \"[options] <basedir> <master> <name> <passwd>\"\n\n    def parseArgs(self, *args):\n        if len(args) != 4:\n            raise usage.UsageError(\"incorrect number of arguments\")\n        basedir, master, name, passwd = args\n        self['basedir'] = basedir\n        self['host'], self['port'] = self.validateMasterArgument(master)\n        self['name'] = name\n        self['passwd'] = passwd\n\n    def postOptions(self):\n        MakerBase.postOptions(self)\n\n        # check and convert numeric parameters\n        for argument in [\"keepalive\", \"maxdelay\", \"log-size\"]:\n            try:\n                self[argument] = int(self[argument])\n            except ValueError:\n                raise usage.UsageError(\"{} parameter needs to be a number\".format(\n                                       argument))\n\n        for argument in [\"log-count\", \"maxretries\", \"umask\", \"numcpus\"]:\n            if not re.match(r'^(0o)?\\d+$', self[argument]) and \\\n                    self[argument] != 'None':\n                raise usage.UsageError(\"{} parameter needs to be a number\"\n                                    \" or None\".format(argument))\n\n        if self['allow-shutdown'] not in [None, 'signal', 'file']:\n            raise usage.UsageError(\"allow-shutdown needs to be one of\"\n                                   \" 'signal' or 'file'\")\n\n\nclass Options(usage.Options):\n    synopsis = \"Usage:    buildbot-worker <command> [command options]\"\n\n    subCommands = [\n        # the following are all admin commands\n        ['create-worker', None, CreateWorkerOptions,\n         \"Create and populate a directory for a new worker\"],\n        ['start', None, StartOptions, \"Start a worker\"],\n        ['stop', None, StopOptions, \"Stop a worker\"],\n        ['restart', None, RestartOptions,\n         \"Restart a worker\"],\n    ]\n\n    def opt_version(self):\n        import buildbot_worker  # pylint: disable=import-outside-toplevel\n        print(\"worker version: {}\".format(buildbot_worker.version))\n        usage.Options.opt_version(self)\n\n    def opt_verbose(self):\n        log.startLogging(sys.stderr)\n\n    def postOptions(self):\n        if not hasattr(self, 'subOptions'):\n            raise usage.UsageError(\"must specify a command\")\n\n\ndef run():\n    config = Options()\n    try:\n        config.parseOptions()\n    except usage.error as e:\n        print(\"{}:  {}\".format(sys.argv[0], e))\n        print()\n        c = getattr(config, 'subOptions', config)\n        print(str(c))\n        sys.exit(1)\n\n    subconfig = config.subOptions\n    subcommandFunction = reflect.namedObject(subconfig.subcommandFunction)\n    sys.exit(subcommandFunction(subconfig))\n", "path": "worker/buildbot_worker/scripts/runner.py"}]}