Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(114)

Side by Side Diff: client/swarming.py

Issue 2870793002: swarming: fix support for raw command + isolated file (Closed)
Patch Set: . Created 3 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « appengine/swarming/swarming_bot/bot_code/task_runner.py ('k') | client/tests/swarming_test.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # Copyright 2013 The LUCI Authors. All rights reserved. 2 # Copyright 2013 The LUCI Authors. All rights reserved.
3 # Use of this source code is governed under the Apache License, Version 2.0 3 # Use of this source code is governed under the Apache License, Version 2.0
4 # that can be found in the LICENSE file. 4 # that can be found in the LICENSE file.
5 5
6 """Client tool to trigger tasks or retrieve results from a Swarming server.""" 6 """Client tool to trigger tasks or retrieve results from a Swarming server."""
7 7
8 __version__ = '0.9.0' 8 __version__ = '0.9.1'
iannucci 2017/05/09 21:40:23 maybe 1.0.0 since the CLI is changing in a backwar
M-A Ruel 2017/05/09 22:49:28 I prefer to not jump too fast. :)
9 9
10 import collections 10 import collections
11 import datetime 11 import datetime
12 import json 12 import json
13 import logging 13 import logging
14 import optparse 14 import optparse
15 import os 15 import os
16 import subprocess 16 import subprocess
17 import sys 17 import sys
18 import textwrap 18 import textwrap
(...skipping 24 matching lines...) Expand all
43 43
44 ROOT_DIR = os.path.dirname(os.path.abspath( 44 ROOT_DIR = os.path.dirname(os.path.abspath(
45 __file__.decode(sys.getfilesystemencoding()))) 45 __file__.decode(sys.getfilesystemencoding())))
46 46
47 47
48 class Failure(Exception): 48 class Failure(Exception):
49 """Generic failure.""" 49 """Generic failure."""
50 pass 50 pass
51 51
52 52
53 ### Isolated file handling. 53 def default_task_name(options):
54 54 """Returns a default task name if not specified."""
55
56 def isolated_handle_options(options, args):
57 """Handles '--isolated <isolated>' and '-- <args...>' arguments.
58
59 Returns:
60 tuple(command, inputs_ref).
61 """
62 isolated_cmd_args = []
63 if not options.isolated:
64 if '--' in args:
65 index = args.index('--')
66 isolated_cmd_args = args[index+1:]
67 args = args[:index]
68 else:
69 # optparse eats '--' sometimes.
70 isolated_cmd_args = args[1:]
71 args = args[:1]
72 if len(args) != 1:
73 raise ValueError(
74 'Use --isolated, --raw-cmd or \'--\' to pass arguments to the called '
75 'process.')
76 elif args:
77 if '--' in args:
78 index = args.index('--')
79 isolated_cmd_args = args[index+1:]
80 if index != 0:
81 raise ValueError('Unexpected arguments.')
82 else:
83 # optparse eats '--' sometimes.
84 isolated_cmd_args = args
85
86 if not options.task_name: 55 if not options.task_name:
87 options.task_name = u'%s/%s/%s' % ( 56 task_name = u'%s/%s' % (
88 options.user, 57 options.user,
89 '_'.join( 58 '_'.join(
90 '%s=%s' % (k, v) 59 '%s=%s' % (k, v)
91 for k, v in sorted(options.dimensions.iteritems())), 60 for k, v in sorted(options.dimensions.iteritems())))
92 options.isolated) 61 if options.isolated:
93 62 task_name += u'/' + options.isolated
94 inputs_ref = FilesRef( 63 return task_name
95 isolated=options.isolated, 64 return options.task_name
96 isolatedserver=options.isolate_server,
97 namespace=options.namespace)
98 return isolated_cmd_args, inputs_ref
99 65
100 66
101 ### Triggering. 67 ### Triggering.
102 68
103 69
104 # See ../appengine/swarming/swarming_rpcs.py. 70 # See ../appengine/swarming/swarming_rpcs.py.
105 CipdPackage = collections.namedtuple( 71 CipdPackage = collections.namedtuple(
106 'CipdPackage', 72 'CipdPackage',
107 [ 73 [
108 'package_name', 74 'package_name',
(...skipping 787 matching lines...) Expand 10 before | Expand all | Expand 10 after
896 'this task.') 862 'this task.')
897 group.add_option( 863 group.add_option(
898 '--hard-timeout', type='int', default=60*60, 864 '--hard-timeout', type='int', default=60*60,
899 help='Seconds to allow the task to complete.') 865 help='Seconds to allow the task to complete.')
900 group.add_option( 866 group.add_option(
901 '--io-timeout', type='int', default=20*60, 867 '--io-timeout', type='int', default=20*60,
902 help='Seconds to allow the task to be silent.') 868 help='Seconds to allow the task to be silent.')
903 group.add_option( 869 group.add_option(
904 '--raw-cmd', action='store_true', default=False, 870 '--raw-cmd', action='store_true', default=False,
905 help='When set, the command after -- is used as-is without run_isolated. ' 871 help='When set, the command after -- is used as-is without run_isolated. '
906 'In this case, no .isolated file is expected.') 872 'In this case, the .isolated file is expected to not have a command')
907 group.add_option( 873 group.add_option(
908 '--cipd-package', action='append', default=[], 874 '--cipd-package', action='append', default=[],
909 help='CIPD packages to install on the Swarming bot. Uses the format: ' 875 help='CIPD packages to install on the Swarming bot. Uses the format: '
910 'path:package_name:version') 876 'path:package_name:version')
911 group.add_option( 877 group.add_option(
912 '--named-cache', action='append', nargs=2, default=[], 878 '--named-cache', action='append', nargs=2, default=[],
913 help='"<name> <relpath>" items to keep a persistent bot managed cache') 879 help='"<name> <relpath>" items to keep a persistent bot managed cache')
914 group.add_option( 880 group.add_option(
915 '--service-account', 881 '--service-account',
916 help='Name of a service account to run the task as. Only literal "bot" ' 882 help='Name of a service account to run the task as. Only literal "bot" '
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after
951 parser.add_option_group(group) 917 parser.add_option_group(group)
952 918
953 919
954 def process_trigger_options(parser, options, args): 920 def process_trigger_options(parser, options, args):
955 """Processes trigger options and does preparatory steps. 921 """Processes trigger options and does preparatory steps.
956 922
957 Generates service account tokens if necessary. 923 Generates service account tokens if necessary.
958 """ 924 """
959 options.dimensions = dict(options.dimensions) 925 options.dimensions = dict(options.dimensions)
960 options.env = dict(options.env) 926 options.env = dict(options.env)
927 if args and args[0] == '--':
928 args = args[1:]
961 929
962 if not options.dimensions: 930 if not options.dimensions:
963 parser.error('Please at least specify one --dimension') 931 parser.error('Please at least specify one --dimension')
932 if not all(len(t.split(':', 1)) == 2 for t in options.tags):
933 parser.error('--tags must be in the format key:value')
934 if options.raw_cmd and not args:
935 parser.error(
936 'Arguments with --raw-cmd should be passed after -- as command '
937 'delimiter.')
938 if options.isolate_server and not options.namespace:
939 parser.error(
940 '--namespace must be a valid value when --isolate-server is used')
941 if not options.isolated and not options.raw_cmd:
942 parser.error('Specify at least one of --raw-cmd or --isolated or both')
943
944 # Isolated
945 # --isolated is required only if --raw-cmd wasn't provided.
946 # TODO(maruel): --isolate-server may be optional as Swarming may have its own
947 # preferred server.
948 isolateserver.process_isolate_server_options(
949 parser, options, False, not options.raw_cmd)
950 inputs_ref = None
951 if options.isolate_server:
952 inputs_ref = FilesRef(
953 isolated=options.isolated,
954 isolatedserver=options.isolate_server,
955 namespace=options.namespace)
956
957 # Command
958 command = None
959 extra_args = None
964 if options.raw_cmd: 960 if options.raw_cmd:
965 if not args: 961 command = args
966 parser.error( 962 else:
967 'Arguments with --raw-cmd should be passed after -- as command ' 963 extra_args = args
968 'delimiter.')
969 if options.isolate_server:
970 parser.error('Can\'t use both --raw-cmd and --isolate-server.')
971 964
972 command = args 965 # CIPD
973 if not options.task_name:
974 options.task_name = u'%s/%s' % (
975 options.user,
976 '_'.join(
977 '%s=%s' % (k, v)
978 for k, v in sorted(options.dimensions.iteritems())))
979 inputs_ref = None
980 else:
981 isolateserver.process_isolate_server_options(parser, options, False, True)
982 try:
983 command, inputs_ref = isolated_handle_options(options, args)
984 except ValueError as e:
985 parser.error(str(e))
986
987 cipd_packages = [] 966 cipd_packages = []
988 for p in options.cipd_package: 967 for p in options.cipd_package:
989 split = p.split(':', 2) 968 split = p.split(':', 2)
990 if len(split) != 3: 969 if len(split) != 3:
991 parser.error('CIPD packages must take the form: path:package:version') 970 parser.error('CIPD packages must take the form: path:package:version')
992 cipd_packages.append(CipdPackage( 971 cipd_packages.append(CipdPackage(
993 package_name=split[1], 972 package_name=split[1],
994 path=split[0], 973 path=split[0],
995 version=split[2])) 974 version=split[2]))
996 cipd_input = None 975 cipd_input = None
997 if cipd_packages: 976 if cipd_packages:
998 cipd_input = CipdInput( 977 cipd_input = CipdInput(
999 client_package=None, 978 client_package=None,
1000 packages=cipd_packages, 979 packages=cipd_packages,
1001 server=None) 980 server=None)
1002 981
982 # Secrets
1003 secret_bytes = None 983 secret_bytes = None
1004 if options.secret_bytes_path: 984 if options.secret_bytes_path:
1005 with open(options.secret_bytes_path, 'r') as f: 985 with open(options.secret_bytes_path, 'r') as f:
1006 secret_bytes = f.read().encode('base64') 986 secret_bytes = f.read().encode('base64')
1007 987
988 # Named caches
1008 caches = [ 989 caches = [
1009 {u'name': unicode(i[0]), u'path': unicode(i[1])} 990 {u'name': unicode(i[0]), u'path': unicode(i[1])}
1010 for i in options.named_cache 991 for i in options.named_cache
1011 ] 992 ]
1012 # If inputs_ref.isolated is used, command is actually extra_args. 993
1013 # Otherwise it's an actual command to run.
1014 isolated_input = inputs_ref and inputs_ref.isolated
1015 properties = TaskProperties( 994 properties = TaskProperties(
1016 caches=caches, 995 caches=caches,
1017 cipd_input=cipd_input, 996 cipd_input=cipd_input,
1018 command=None if isolated_input else command, 997 command=command,
1019 dimensions=options.dimensions, 998 dimensions=options.dimensions,
1020 env=options.env, 999 env=options.env,
1021 execution_timeout_secs=options.hard_timeout, 1000 execution_timeout_secs=options.hard_timeout,
1022 extra_args=command if isolated_input else None, 1001 extra_args=extra_args,
1023 grace_period_secs=30, 1002 grace_period_secs=30,
1024 idempotent=options.idempotent, 1003 idempotent=options.idempotent,
1025 inputs_ref=inputs_ref, 1004 inputs_ref=inputs_ref,
1026 io_timeout_secs=options.io_timeout, 1005 io_timeout_secs=options.io_timeout,
1027 outputs=options.output, 1006 outputs=options.output,
1028 secret_bytes=secret_bytes) 1007 secret_bytes=secret_bytes)
1029 if not all(len(t.split(':', 1)) == 2 for t in options.tags):
1030 parser.error('--tags must be in the format key:value')
1031 1008
1032 # Convert a service account email to a signed service account token to pass 1009 # Convert a service account email to a signed service account token to pass
1033 # to Swarming. 1010 # to Swarming.
1034 service_account_token = None 1011 service_account_token = None
1035 if options.service_account in ('bot', 'none'): 1012 if options.service_account in ('bot', 'none'):
1036 service_account_token = options.service_account 1013 service_account_token = options.service_account
1037 elif options.service_account: 1014 elif options.service_account:
1038 # pylint: disable=assignment-from-no-return 1015 # pylint: disable=assignment-from-no-return
1039 service_account_token = mint_service_account_token(options.service_account) 1016 service_account_token = mint_service_account_token(options.service_account)
1040 1017
1041 return NewTaskRequest( 1018 return NewTaskRequest(
1042 expiration_secs=options.expiration, 1019 expiration_secs=options.expiration,
1043 name=options.task_name, 1020 name=default_task_name(options),
1044 parent_task_id=os.environ.get('SWARMING_TASK_ID', ''), 1021 parent_task_id=os.environ.get('SWARMING_TASK_ID', ''),
1045 priority=options.priority, 1022 priority=options.priority,
1046 properties=properties, 1023 properties=properties,
1047 service_account_token=service_account_token, 1024 service_account_token=service_account_token,
1048 tags=options.tags, 1025 tags=options.tags,
1049 user=options.user) 1026 user=options.user)
1050 1027
1051 1028
1052 def add_collect_options(parser): 1029 def add_collect_options(parser):
1053 parser.server_group.add_option( 1030 parser.server_group.add_option(
(...skipping 366 matching lines...) Expand 10 before | Expand all | Expand 10 after
1420 add_collect_options(parser) 1397 add_collect_options(parser)
1421 add_sharding_options(parser) 1398 add_sharding_options(parser)
1422 options, args = parser.parse_args(args) 1399 options, args = parser.parse_args(args)
1423 task_request = process_trigger_options(parser, options, args) 1400 task_request = process_trigger_options(parser, options, args)
1424 try: 1401 try:
1425 tasks = trigger_task_shards( 1402 tasks = trigger_task_shards(
1426 options.swarming, task_request, options.shards) 1403 options.swarming, task_request, options.shards)
1427 except Failure as e: 1404 except Failure as e:
1428 on_error.report( 1405 on_error.report(
1429 'Failed to trigger %s(%s): %s' % 1406 'Failed to trigger %s(%s): %s' %
1430 (options.task_name, args[0], e.args[0])) 1407 (task_request.name, args[0], e.args[0]))
1431 return 1 1408 return 1
1432 if not tasks: 1409 if not tasks:
1433 on_error.report('Failed to trigger the task.') 1410 on_error.report('Failed to trigger the task.')
1434 return 1 1411 return 1
1435 print('Triggered task: %s' % options.task_name) 1412 print('Triggered task: %s' % task_request.name)
1436 task_ids = [ 1413 task_ids = [
1437 t['task_id'] 1414 t['task_id']
1438 for t in sorted(tasks.itervalues(), key=lambda x: x['shard_index']) 1415 for t in sorted(tasks.itervalues(), key=lambda x: x['shard_index'])
1439 ] 1416 ]
1440 if options.timeout is None: 1417 if options.timeout is None:
1441 options.timeout = ( 1418 options.timeout = (
1442 task_request.properties.execution_timeout_secs + 1419 task_request.properties.execution_timeout_secs +
1443 task_request.expiration_secs + 10.) 1420 task_request.expiration_secs + 10.)
1444 try: 1421 try:
1445 return collect( 1422 return collect(
(...skipping 151 matching lines...) Expand 10 before | Expand all | Expand 10 after
1597 parser.add_option( 1574 parser.add_option(
1598 '--dump-json', 1575 '--dump-json',
1599 metavar='FILE', 1576 metavar='FILE',
1600 help='Dump details about the triggered task(s) to this file as json') 1577 help='Dump details about the triggered task(s) to this file as json')
1601 options, args = parser.parse_args(args) 1578 options, args = parser.parse_args(args)
1602 task_request = process_trigger_options(parser, options, args) 1579 task_request = process_trigger_options(parser, options, args)
1603 try: 1580 try:
1604 tasks = trigger_task_shards( 1581 tasks = trigger_task_shards(
1605 options.swarming, task_request, options.shards) 1582 options.swarming, task_request, options.shards)
1606 if tasks: 1583 if tasks:
1607 print('Triggered task: %s' % options.task_name) 1584 print('Triggered task: %s' % task_request.name)
1608 tasks_sorted = sorted( 1585 tasks_sorted = sorted(
1609 tasks.itervalues(), key=lambda x: x['shard_index']) 1586 tasks.itervalues(), key=lambda x: x['shard_index'])
1610 if options.dump_json: 1587 if options.dump_json:
1611 data = { 1588 data = {
1612 'base_task_name': options.task_name, 1589 'base_task_name': task_request.name,
1613 'tasks': tasks, 1590 'tasks': tasks,
1614 'request': task_request_to_raw_request(task_request, True), 1591 'request': task_request_to_raw_request(task_request, True),
1615 } 1592 }
1616 tools.write_json(unicode(options.dump_json), data, True) 1593 tools.write_json(unicode(options.dump_json), data, True)
1617 print('To collect results, use:') 1594 print('To collect results, use:')
1618 print(' swarming.py collect -S %s --json %s' % 1595 print(' swarming.py collect -S %s --json %s' %
1619 (options.swarming, options.dump_json)) 1596 (options.swarming, options.dump_json))
1620 else: 1597 else:
1621 print('To collect results, use:') 1598 print('To collect results, use:')
1622 print(' swarming.py collect -S %s %s' % 1599 print(' swarming.py collect -S %s %s' %
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after
1674 dispatcher = subcommand.CommandDispatcher(__name__) 1651 dispatcher = subcommand.CommandDispatcher(__name__)
1675 return dispatcher.execute(OptionParserSwarming(version=__version__), args) 1652 return dispatcher.execute(OptionParserSwarming(version=__version__), args)
1676 1653
1677 1654
1678 if __name__ == '__main__': 1655 if __name__ == '__main__':
1679 subprocess42.inhibit_os_error_reporting() 1656 subprocess42.inhibit_os_error_reporting()
1680 fix_encoding.fix_encoding() 1657 fix_encoding.fix_encoding()
1681 tools.disable_buffering() 1658 tools.disable_buffering()
1682 colorama.init() 1659 colorama.init()
1683 sys.exit(main(sys.argv[1:])) 1660 sys.exit(main(sys.argv[1:]))
OLDNEW
« no previous file with comments | « appengine/swarming/swarming_bot/bot_code/task_runner.py ('k') | client/tests/swarming_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698