diff options
author | Max Illfelder <illfelder@google.com> | 2016-09-06 22:44:12 -0700 |
---|---|---|
committer | Max Illfelder <illfelder@google.com> | 2016-09-06 22:44:12 -0700 |
commit | e843cc6919bbed8203ef8ce87b5a3120d4e86800 (patch) | |
tree | bf5886e17dce4bb631a9518c3aa66b45f4f14d29 | |
parent | 9eea7221a41a938730aa6985f72a97ee1f2e1334 (diff) | |
parent | 04289b3dc3fd0b29d7c72ba7f6d4794de33b7231 (diff) | |
download | google-compute-image-packages-e843cc6919bbed8203ef8ce87b5a3120d4e86800.tar.gz |
Merge branch 'development'20160906
-rw-r--r-- | .gitignore | 3 | ||||
-rw-r--r-- | README.md | 11 | ||||
-rwxr-xr-x | build_packages.sh | 61 | ||||
-rw-r--r-- | google_compute_engine/instance_setup/instance_config.py | 2 | ||||
-rwxr-xr-x | google_compute_engine/network_setup/network_setup.py | 43 | ||||
-rw-r--r-- | google_compute_engine/network_setup/tests/network_setup_test.py | 89 | ||||
-rw-r--r-- | google_compute_engine/network_utils.py | 18 | ||||
-rw-r--r-- | google_compute_engine/tests/network_utils_test.py | 20 | ||||
-rwxr-xr-x | google_compute_engine_init/build_packages.sh | 62 | ||||
-rwxr-xr-x | google_config/build_packages.sh | 81 | ||||
-rwxr-xr-x | setup.py | 2 |
11 files changed, 271 insertions, 121 deletions
@@ -6,6 +6,9 @@ .tox .coverage *.egg-info +build +*.deb +*.rpm # emacs backup files *.*~ @@ -136,13 +136,8 @@ The library provides the following functions: A network utilities library retrieves information about a network interface. The library is used for IP forwarding and for setting up an Ethernet interface on -boot. - -The library exposes the following functions: - -* **GetNetworkInterface** retrieves the network interface name associated - with a MAC address. -* **IsEnabled** checks whether a network interface is enabled. +boot. The library exposes a `GetNetworkInterface` function that retrieves the +network interface name associated with a MAC address. ## Daemons @@ -249,7 +244,7 @@ InstanceSetup | set_multiqueue | `false` skips multiqueue driver suppo IpForwarding | ethernet_proto_id | Protocol ID string for daemon added routes. MetadataScripts | startup | `false` disables startup script execution. MetadataScripts | shutdown | `false` disables shutdown script execution. -NetworkInterfaces | dhcp_binary | DHCP binary string that enables a network interface parameter. +NetworkInterfaces | dhcp_command | String to execute to enable network interfaces. NetworkInterfaces | setup | `false` disables network interface setup. Setting `network_enabled` to `false` will skip setting up host keys and the diff --git a/build_packages.sh b/build_packages.sh index e669ca1..4298995 100755 --- a/build_packages.sh +++ b/build_packages.sh @@ -13,9 +13,17 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Build the Linux guest environment deb and rpm packages. +#/ Usage: build_packages.sh [options] +#/ +#/ Build the Python package for Linux daemons, scripts, and libraries. +#/ +#/ OPTIONS: +#/ -h Show this message +#/ -o DISTRO,... Build only specified distros -TIMESTAMP="$(date +%s)" +function usage() { + grep '^#/' < "$0" | cut -c 4- +} function build_distro() { declare -r distro="$1" @@ -48,10 +56,47 @@ function build_distro() { setup.py } -# RHEL/CentOS -build_distro 'el6' 'rpm' '/usr/lib/python2.6/site-packages' -build_distro 'el7' 'rpm' '/usr/lib/python2.7/site-packages' +TIMESTAMP="$(date +%s)" + +while getopts 'ho:' OPTION; do + case "$OPTION" in + h) + usage + exit 2 + ;; + o) + set -f + IFS=',' + BUILD=($OPTARG) + set +f + ;; + ?) + usage + exit + ;; + esac +done + +if [ -z "$BUILD" ]; then + BUILD=('el6' 'el7' 'wheezy' 'jessie') +fi -# Debian -build_distro 'wheezy' 'deb' '/usr/lib/python2.7/dist-packages' -build_distro 'jessie' 'deb' '/usr/lib/python2.7/dist-packages' +for build in "${BUILD[@]}"; do + case "$build" in + el6) # RHEL/CentOS 6 + build_distro 'el6' 'rpm' '/usr/lib/python2.6/site-packages' + ;; + el7) # RHEL/CentOS 7 + build_distro 'el7' 'rpm' '/usr/lib/python2.7/site-packages' + ;; + wheezy) # Debian 7 + build_distro 'wheezy' 'deb' '/usr/lib/python2.7/dist-packages' + ;; + jessie) # Debian 8 + build_distro 'jessie' 'deb' '/usr/lib/python2.7/dist-packages' + ;; + *) + echo "Invalid build '${build}'. Use 'el6', 'el7', 'wheezy', or 'jessie'." + ;; + esac +done diff --git a/google_compute_engine/instance_setup/instance_config.py b/google_compute_engine/instance_setup/instance_config.py index 9d37875..f13351b 100644 --- a/google_compute_engine/instance_setup/instance_config.py +++ b/google_compute_engine/instance_setup/instance_config.py @@ -67,7 +67,7 @@ class InstanceConfig(config_manager.ConfigManager): }, 'NetworkInterfaces': { 'setup': 'true', - 'dhcp_binary': 'dhclient', + 'dhcp_command': '', }, } diff --git a/google_compute_engine/network_setup/network_setup.py b/google_compute_engine/network_setup/network_setup.py index 4c6e3a7..52c6399 100755 --- a/google_compute_engine/network_setup/network_setup.py +++ b/google_compute_engine/network_setup/network_setup.py @@ -30,14 +30,14 @@ class NetworkSetup(object): network_interfaces = 'instance/network-interfaces' - def __init__(self, dhcp_binary=None, debug=False): + def __init__(self, dhcp_command=None, debug=False): """Constructor. Args: - dhcp_binary: string, an executable to enable an Ethernet interface. + dhcp_command: string, a command to enable Ethernet interfaces. debug: bool, True if debug output should write to the console. """ - self.dhcp_binary = dhcp_binary or 'dhclient' + self.dhcp_command = dhcp_command facility = logging.handlers.SysLogHandler.LOG_DAEMON self.logger = logger.Logger( name='network-setup', debug=debug, facility=facility) @@ -45,36 +45,47 @@ class NetworkSetup(object): self.network_utils = network_utils.NetworkUtils(logger=self.logger) self._SetupNetworkInterfaces() - def _EnableNetworkInterface(self, interface): - """Enable the network interface. + def _EnableNetworkInterfaces(self, interfaces): + """Enable the list of network interfaces. Args: - interface: string, the output device to enable. + interface: list of string, the output devices to enable. """ - if self.network_utils.IsEnabled(interface): - return - - command = [self.dhcp_binary, interface] try: - self.logger.info('Enabling the Ethernet interface %s.', interface) - subprocess.check_call(command) + self.logger.info('Enabling the Ethernet interface %s.', interfaces) + subprocess.check_call(['dhclient', '-r'] + interfaces) + subprocess.check_call(['dhclient'] + interfaces) except subprocess.CalledProcessError: - self.logger.warning('Could not enable the interface %s.', interface) + self.logger.warning('Could not enable interfaces %s.', interfaces) def _SetupNetworkInterfaces(self): """Get network interfaces metadata and enable each Ethernet interface.""" result = self.watcher.GetMetadata( metadata_key=self.network_interfaces, recursive=True) + interfaces = [] for network_interface in result: mac_address = network_interface.get('mac') interface = self.network_utils.GetNetworkInterface(mac_address) if interface: - self._EnableNetworkInterface(interface) + interfaces.append(interface) else: message = 'Network interface not found for MAC address: %s.' self.logger.warning(message, mac_address) + # The default Ethernet interface is enabled by default. Do not attempt to + # enable interfaces if only one interface is specified in metadata. + if len(interfaces) <= 1: + return + + if self.dhcp_command: + try: + subprocess.check_call([self.dhcp_command]) + except subprocess.CalledProcessError: + self.logger.warning('Could not enable Ethernet interfaces.') + else: + self._EnableNetworkInterfaces(interfaces) + def main(): parser = optparse.OptionParser() @@ -85,8 +96,8 @@ def main(): instance_config = config_manager.ConfigManager() if instance_config.GetOptionBool('NetworkInterfaces', 'setup'): NetworkSetup( - dhcp_binary=instance_config.GetOptionString( - 'NetworkInterfaces', 'dhcp_binary'), + dhcp_command=instance_config.GetOptionString( + 'NetworkInterfaces', 'dhcp_command'), debug=bool(options.debug)) diff --git a/google_compute_engine/network_setup/tests/network_setup_test.py b/google_compute_engine/network_setup/tests/network_setup_test.py index 12b2588..4c3f01e 100644 --- a/google_compute_engine/network_setup/tests/network_setup_test.py +++ b/google_compute_engine/network_setup/tests/network_setup_test.py @@ -30,14 +30,13 @@ class NetworkSetupTest(unittest.TestCase): self.mock_ip_forwarding_utils = mock.Mock() self.mock_network_utils = mock.Mock() self.metadata_key = 'metadata_key' - self.dhcp_binary = 'binary' self.mock_setup = mock.create_autospec(network_setup.NetworkSetup) self.mock_setup.logger = self.mock_logger self.mock_setup.watcher = self.mock_watcher self.mock_setup.network_utils = self.mock_network_utils self.mock_setup.network_interfaces = self.metadata_key - self.mock_setup.dhcp_binary = self.dhcp_binary + self.mock_setup.dhcp_command = '' @mock.patch('google_compute_engine.network_setup.network_setup.network_utils') @mock.patch('google_compute_engine.network_setup.network_setup.metadata_watcher') @@ -51,7 +50,7 @@ class NetworkSetupTest(unittest.TestCase): mocks.attach_mock(mock_network_utils, 'network') with mock.patch.object( network_setup.NetworkSetup, '_SetupNetworkInterfaces'): - network_setup.NetworkSetup(dhcp_binary='binary', debug=True) + network_setup.NetworkSetup(debug=True) expected_calls = [ mock.call.logger.Logger(name=mock.ANY, debug=True, facility=mock.ANY), mock.call.watcher.MetadataWatcher(logger=mock_logger_instance), @@ -60,30 +59,28 @@ class NetworkSetupTest(unittest.TestCase): self.assertEqual(mocks.mock_calls, expected_calls) @mock.patch('google_compute_engine.network_setup.network_setup.subprocess.check_call') - def testEnableNetworkInterface(self, mock_call): + def testEnableNetworkInterfaces(self, mock_call): mocks = mock.Mock() mocks.attach_mock(mock_call, 'call') mocks.attach_mock(self.mock_logger, 'logger') mocks.attach_mock(self.mock_watcher, 'watcher') mocks.attach_mock(self.mock_network_utils, 'network') - mock_call.side_effect = [None, subprocess.CalledProcessError(1, 'Test')] - self.mock_network_utils.IsEnabled.side_effect = [True, False, False] + mock_call.side_effect = [ + None, None, subprocess.CalledProcessError(1, 'Test')] - network_setup.NetworkSetup._EnableNetworkInterface(self.mock_setup, 'a') - network_setup.NetworkSetup._EnableNetworkInterface(self.mock_setup, 'b') - network_setup.NetworkSetup._EnableNetworkInterface(self.mock_setup, 'c') + network_setup.NetworkSetup._EnableNetworkInterfaces( + self.mock_setup, ['a', 'b', 'c']) + network_setup.NetworkSetup._EnableNetworkInterfaces( + self.mock_setup, []) expected_calls = [ - # The network interface is already enabled. - mock.call.network.IsEnabled('a'), # Successfully enable the network interface. - mock.call.network.IsEnabled('b'), - mock.call.logger.info(mock.ANY, 'b'), - mock.call.call([self.dhcp_binary, 'b']), + mock.call.logger.info(mock.ANY, ['a', 'b', 'c']), + mock.call.call(['dhclient', '-r', 'a', 'b', 'c']), + mock.call.call(['dhclient', 'a', 'b', 'c']), # Exception while enabling the network interface. - mock.call.network.IsEnabled('c'), - mock.call.logger.info(mock.ANY, 'c'), - mock.call.call([self.dhcp_binary, 'c']), - mock.call.logger.warning(mock.ANY, 'c'), + mock.call.logger.info(mock.ANY, []), + mock.call.call(['dhclient', '-r']), + mock.call.logger.warning(mock.ANY, []), ] self.assertEqual(mocks.mock_calls, expected_calls) @@ -94,21 +91,67 @@ class NetworkSetupTest(unittest.TestCase): mocks.attach_mock(self.mock_network_utils, 'network') mocks.attach_mock(self.mock_setup, 'setup') self.mock_watcher.GetMetadata.return_value = [ - {'mac': '1'}, {'mac': '2'}, {}] + {'mac': '1'}, {'mac': '2'}, {'mac': '3'}, {}] self.mock_network_utils.GetNetworkInterface.side_effect = [ - 'eth0', None, None] + 'eth0', 'eth1', None, None] with mock.patch.object( - network_setup.NetworkSetup, '_EnableNetworkInterface'): + network_setup.NetworkSetup, '_EnableNetworkInterfaces'): network_setup.NetworkSetup._SetupNetworkInterfaces(self.mock_setup) expected_calls = [ mock.call.watcher.GetMetadata( metadata_key=self.metadata_key, recursive=True), mock.call.network.GetNetworkInterface('1'), - mock.call.setup._EnableNetworkInterface('eth0'), mock.call.network.GetNetworkInterface('2'), - mock.call.logger.warning(mock.ANY, '2'), + mock.call.network.GetNetworkInterface('3'), + mock.call.logger.warning(mock.ANY, '3'), mock.call.network.GetNetworkInterface(None), mock.call.logger.warning(mock.ANY, None), + mock.call.setup._EnableNetworkInterfaces(['eth0', 'eth1']), + ] + self.assertEqual(mocks.mock_calls, expected_calls) + + def testSetupNetworkInterfacesSkip(self): + mocks = mock.Mock() + mocks.attach_mock(self.mock_logger, 'logger') + mocks.attach_mock(self.mock_watcher, 'watcher') + mocks.attach_mock(self.mock_network_utils, 'network') + mocks.attach_mock(self.mock_setup, 'setup') + self.mock_watcher.GetMetadata.return_value = [{'mac': '1'}] + + with mock.patch.object( + network_setup.NetworkSetup, '_EnableNetworkInterfaces'): + network_setup.NetworkSetup._SetupNetworkInterfaces(self.mock_setup) + expected_calls = [ + mock.call.watcher.GetMetadata( + metadata_key=self.metadata_key, recursive=True), + mock.call.network.GetNetworkInterface('1'), + ] + self.assertEqual(mocks.mock_calls, expected_calls) + + @mock.patch('google_compute_engine.network_setup.network_setup.subprocess.check_call') + def testSetupNetworkInterfacesCommand(self, mock_call): + mocks = mock.Mock() + mocks.attach_mock(mock_call, 'call') + mocks.attach_mock(self.mock_logger, 'logger') + mocks.attach_mock(self.mock_watcher, 'watcher') + mocks.attach_mock(self.mock_network_utils, 'network') + mocks.attach_mock(self.mock_setup, 'setup') + self.mock_watcher.GetMetadata.return_value = [ + {'mac': '1'}, {'mac': '2'}] + self.mock_network_utils.GetNetworkInterface.side_effect = ['eth0', 'eth1'] + mock_call.side_effect = subprocess.CalledProcessError(1, 'Test') + + with mock.patch.object( + network_setup.NetworkSetup, '_EnableNetworkInterfaces'): + self.mock_setup.dhcp_command = 'command' + network_setup.NetworkSetup._SetupNetworkInterfaces(self.mock_setup) + expected_calls = [ + mock.call.watcher.GetMetadata( + metadata_key=self.metadata_key, recursive=True), + mock.call.network.GetNetworkInterface('1'), + mock.call.network.GetNetworkInterface('2'), + mock.call.call(['command']), + mock.call.logger.warning(mock.ANY), ] self.assertEqual(mocks.mock_calls, expected_calls) diff --git a/google_compute_engine/network_utils.py b/google_compute_engine/network_utils.py index 3c94deb..3ed36bb 100644 --- a/google_compute_engine/network_utils.py +++ b/google_compute_engine/network_utils.py @@ -59,21 +59,3 @@ class NetworkUtils(object): string, the network interface associated with a MAC address or None. """ return self.interfaces.get(mac_address) - - def IsEnabled(self, interface): - """Check whether a network interface is enabled. - - Args: - interface: string, the network interface. - - Returns: - bool, True if the network interface is enabled. - """ - try: - state = open('/sys/class/net/%s/operstate' % interface).read().strip() - except (IOError, OSError) as e: - message = 'Unable to determine the state for %s. %s.' - self.logger.warning(message, interface, str(e)) - return False - else: - return 'up' == state diff --git a/google_compute_engine/tests/network_utils_test.py b/google_compute_engine/tests/network_utils_test.py index 1c0cb7c..60386b3 100644 --- a/google_compute_engine/tests/network_utils_test.py +++ b/google_compute_engine/tests/network_utils_test.py @@ -64,23 +64,3 @@ class NetworkUtilsTest(unittest.TestCase): self.assertIsNone(self.mock_utils.GetNetworkInterface('invalid')) self.assertEqual( self.mock_utils.GetNetworkInterface('address'), 'interface') - - def testIsEnabled(self): - mock_open = mock.mock_open() - - with mock.patch('%s.open' % builtin, mock_open, create=False): - mock_open().read.side_effect = ['up', 'down', 'up\n', '', 'Garbage'] - self.assertEqual(self.mock_utils.IsEnabled('a'), True) - self.assertEqual(self.mock_utils.IsEnabled('a'), False) - self.assertEqual(self.mock_utils.IsEnabled('a'), True) - self.assertEqual(self.mock_utils.IsEnabled('a'), False) - self.assertEqual(self.mock_utils.IsEnabled('a'), False) - - def testIsEnabledError(self): - mock_open = mock.mock_open() - - with mock.patch('%s.open' % builtin, mock_open, create=False): - mock_open().read.side_effect = [OSError('OSError')] - self.assertEqual(self.mock_utils.IsEnabled('a'), False) - self.mock_logger.warning.assert_called_once_with( - mock.ANY, 'a', 'OSError'), diff --git a/google_compute_engine_init/build_packages.sh b/google_compute_engine_init/build_packages.sh index ce119ad..4d8c20b 100755 --- a/google_compute_engine_init/build_packages.sh +++ b/google_compute_engine_init/build_packages.sh @@ -13,7 +13,18 @@ # See the License for the specific language governing permissions and # limitations under the License. -TIMESTAMP="$(date +%s)" +#/ Usage: build_packages.sh [options] +#/ +#/ Build the package that contains init configuration for the +#/ google-compute-engine Python package. +#/ +#/ OPTIONS: +#/ -h Show this message +#/ -o DISTRO,... Build only specified distros + +function usage() { + grep '^#/' < "$0" | cut -c 4- +} function build_distro() { declare -r distro="$1" @@ -63,10 +74,47 @@ function build_distro() { "${init_files[@]}" } -# RHEL/CentOS -build_distro 'el6' 'rpm' 'upstart' '/etc/init' -build_distro 'el7' 'rpm' 'systemd' '/usr/lib/systemd/system' +TIMESTAMP="$(date +%s)" + +while getopts 'ho:' OPTION; do + case "$OPTION" in + h) + usage + exit 2 + ;; + o) + set -f + IFS=',' + BUILD=($OPTARG) + set +f + ;; + ?) + usage + exit + ;; + esac +done + +if [ -z "$BUILD" ]; then + BUILD=('el6' 'el7' 'wheezy' 'jessie') +fi -# Debian -build_distro 'wheezy' 'deb' 'sysvinit' '/etc/init.d' -build_distro 'jessie' 'deb' 'systemd' '/usr/lib/systemd/system' +for build in "${BUILD[@]}"; do + case "$build" in + el6) # RHEL/CentOS 6 + build_distro 'el6' 'rpm' 'upstart' '/etc/init' + ;; + el7) # RHEL/CentOS 7 + build_distro 'el7' 'rpm' 'systemd' '/usr/lib/systemd/system' + ;; + wheezy) # Debian 7 + build_distro 'wheezy' 'deb' 'sysvinit' '/etc/init.d' + ;; + jessie) # Debian 8 + build_distro 'jessie' 'deb' 'systemd' '/usr/lib/systemd/system' + ;; + *) + echo "Invalid build '${build}'. Use 'el6', 'el7', 'wheezy', or 'jessie'." + ;; + esac +done diff --git a/google_config/build_packages.sh b/google_config/build_packages.sh index af35e7d..00acc2a 100755 --- a/google_config/build_packages.sh +++ b/google_config/build_packages.sh @@ -13,12 +13,17 @@ # See the License for the specific language governing permissions and # limitations under the License. -COMMON_FILES=( - 'rsyslog/90-google.conf=/etc/rsyslog.d/90-google.conf' - 'sysctl/11-gce-network-security.conf=/etc/sysctl.d/11-gce-network-security.conf' - 'udev/64-gce-disk-removal.rules=/etc/udev/rules.d/64-gce-disk-removal.rules' - 'udev/65-gce-disk-naming.rules=/etc/udev/rules.d/65-gce-disk-naming.rules') -TIMESTAMP="$(date +%s)" +#/ Usage: build_packages.sh [options] +#/ +#/ Build the package containing non-Python scripts and guest configuration. +#/ +#/ OPTIONS: +#/ -h Show this message +#/ -o DISTRO,... Build only specified distros + +function usage() { + grep '^#/' < "$0" | cut -c 4- +} function build_distro() { declare -r distro="$1" @@ -46,19 +51,57 @@ function build_distro() { "${files[@]:2}" } -# RHEL/CentOS 6 -build_distro 'el6' 'rpm' \ - 'bin/set_hostname=/etc/dhcp/dhclient-exit-hooks' +COMMON_FILES=( + 'rsyslog/90-google.conf=/etc/rsyslog.d/90-google.conf' + 'sysctl/11-gce-network-security.conf=/etc/sysctl.d/11-gce-network-security.conf' + 'udev/64-gce-disk-removal.rules=/etc/udev/rules.d/64-gce-disk-removal.rules' + 'udev/65-gce-disk-naming.rules=/etc/udev/rules.d/65-gce-disk-naming.rules') +TIMESTAMP="$(date +%s)" -# RHEL/CentOS 7 -build_distro 'el7' 'rpm' \ - 'bin/set_hostname=/usr/bin/set_hostname' \ - 'dhcp/google_hostname.sh=/etc/dhcp/dhclient.d/google_hostname.sh' +while getopts 'ho:' OPTION; do + case "$OPTION" in + h) + usage + exit 2 + ;; + o) + set -f + IFS=',' + BUILD=($OPTARG) + set +f + ;; + ?) + usage + exit + ;; + esac +done -# Debian 7 -build_distro 'wheezy' 'deb' \ - 'bin/set_hostname=/etc/dhcp/dhclient-exit-hooks.d/set_hostname' +if [ -z "$BUILD" ]; then + BUILD=('el6' 'el7' 'wheezy' 'jessie') +fi -# Debian 8 -build_distro 'jessie' 'deb' \ - 'bin/set_hostname=/etc/dhcp/dhclient-exit-hooks.d/set_hostname' +for build in "${BUILD[@]}"; do + case "$build" in + el6) # RHEL/CentOS 6 + build_distro 'el6' 'rpm' \ + 'bin/set_hostname=/etc/dhcp/dhclient-exit-hooks' + ;; + el7) # RHEL/CentOS 7 + build_distro 'el7' 'rpm' \ + 'bin/set_hostname=/usr/bin/set_hostname' \ + 'dhcp/google_hostname.sh=/etc/dhcp/dhclient.d/google_hostname.sh' + ;; + wheezy) # Debian 7 + build_distro 'wheezy' 'deb' \ + 'bin/set_hostname=/etc/dhcp/dhclient-exit-hooks.d/set_hostname' + ;; + jessie) # Debian 8 + build_distro 'jessie' 'deb' \ + 'bin/set_hostname=/etc/dhcp/dhclient-exit-hooks.d/set_hostname' + ;; + *) + echo "Invalid build '${build}'. Use 'el6', 'el7', 'wheezy', or 'jessie'." + ;; + esac +done @@ -32,7 +32,7 @@ setuptools.setup( packages=setuptools.find_packages(), scripts=glob.glob('scripts/*'), url='https://github.com/GoogleCloudPlatform/compute-image-packages', - version='2.2.2', + version='2.2.3', # Entry points create scripts in /usr/bin that call a function. entry_points={ 'console_scripts': [ |