| OLD | NEW |
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright 2012 The LUCI Authors. All rights reserved. | 2 # Copyright 2012 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 """Runs a command with optional isolated input/output. | 6 """Runs a command with optional isolated input/output. |
| 7 | 7 |
| 8 Despite name "run_isolated", can run a generic non-isolated command specified as | 8 Despite name "run_isolated", can run a generic non-isolated command specified as |
| 9 args. | 9 args. |
| 10 | 10 |
| 11 If input isolated hash is provided, fetches it, creates a tree of hard links, | 11 If input isolated hash is provided, fetches it, creates a tree of hard links, |
| 12 appends args to the command in the fetched isolated and runs it. | 12 appends args to the command in the fetched isolated and runs it. |
| 13 To improve performance, keeps a local cache. | 13 To improve performance, keeps a local cache. |
| 14 The local cache can safely be deleted. | 14 The local cache can safely be deleted. |
| 15 | 15 |
| 16 Any ${EXECUTABLE_SUFFIX} on the command line will be replaced with ".exe" string | 16 Any ${EXECUTABLE_SUFFIX} on the command line will be replaced with ".exe" string |
| 17 on Windows and "" on other platforms. | 17 on Windows and "" on other platforms. |
| 18 | 18 |
| 19 Any ${ISOLATED_OUTDIR} on the command line will be replaced by the location of a | 19 Any ${ISOLATED_OUTDIR} on the command line will be replaced by the location of a |
| 20 temporary directory upon execution of the command specified in the .isolated | 20 temporary directory upon execution of the command specified in the .isolated |
| 21 file. All content written to this directory will be uploaded upon termination | 21 file. All content written to this directory will be uploaded upon termination |
| 22 and the .isolated file describing this directory will be printed to stdout. | 22 and the .isolated file describing this directory will be printed to stdout. |
| 23 | 23 |
| 24 Any ${SWARMING_BOT_FILE} on the command line will be replaced by the value of | 24 Any ${SWARMING_BOT_FILE} on the command line will be replaced by the value of |
| 25 the --bot-file parameter. This file is used by a swarming bot to communicate | 25 the --bot-file parameter. This file is used by a swarming bot to communicate |
| 26 state of the host to tasks. It is written to by the swarming bot's | 26 state of the host to tasks. It is written to by the swarming bot's |
| 27 on_before_task() hook in the swarming server's custom bot_config.py. | 27 on_before_task() hook in the swarming server's custom bot_config.py. |
| 28 """ | 28 """ |
| 29 | 29 |
| 30 __version__ = '0.8.5' | 30 __version__ = '0.8.6' |
| 31 | 31 |
| 32 import argparse |
| 32 import base64 | 33 import base64 |
| 33 import collections | 34 import collections |
| 35 import json |
| 34 import logging | 36 import logging |
| 35 import optparse | 37 import optparse |
| 36 import os | 38 import os |
| 37 import sys | 39 import sys |
| 38 import tempfile | 40 import tempfile |
| 39 import time | 41 import time |
| 40 | 42 |
| 41 from third_party.depot_tools import fix_encoding | 43 from third_party.depot_tools import fix_encoding |
| 42 | 44 |
| 43 from utils import file_path | 45 from utils import file_path |
| (...skipping 680 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 724 'non-zero only on internal failure') | 726 'non-zero only on internal failure') |
| 725 parser.add_option( | 727 parser.add_option( |
| 726 '--hard-timeout', type='float', help='Enforce hard timeout in execution') | 728 '--hard-timeout', type='float', help='Enforce hard timeout in execution') |
| 727 parser.add_option( | 729 parser.add_option( |
| 728 '--grace-period', type='float', | 730 '--grace-period', type='float', |
| 729 help='Grace period between SIGTERM and SIGKILL') | 731 help='Grace period between SIGTERM and SIGKILL') |
| 730 parser.add_option( | 732 parser.add_option( |
| 731 '--bot-file', | 733 '--bot-file', |
| 732 help='Path to a file describing the state of the host. The content is ' | 734 help='Path to a file describing the state of the host. The content is ' |
| 733 'defined by on_before_task() in bot_config.') | 735 'defined by on_before_task() in bot_config.') |
| 736 parser.add_option( |
| 737 '-a', '--argsfile', |
| 738 # This is actually handled in parse_args; it's included here purely so it |
| 739 # can make it into the help text. |
| 740 help='Specify a file containing a JSON array of arguments to this ' |
| 741 'script. If --argsfile is provided, no other argument may be ' |
| 742 'provided on the command line.') |
| 734 data_group = optparse.OptionGroup(parser, 'Data source') | 743 data_group = optparse.OptionGroup(parser, 'Data source') |
| 735 data_group.add_option( | 744 data_group.add_option( |
| 736 '-s', '--isolated', | 745 '-s', '--isolated', |
| 737 help='Hash of the .isolated to grab from the isolate server.') | 746 help='Hash of the .isolated to grab from the isolate server.') |
| 738 isolateserver.add_isolate_server_options(data_group) | 747 isolateserver.add_isolate_server_options(data_group) |
| 739 parser.add_option_group(data_group) | 748 parser.add_option_group(data_group) |
| 740 | 749 |
| 741 isolateserver.add_cache_options(parser) | 750 isolateserver.add_cache_options(parser) |
| 742 | 751 |
| 743 cipd.add_cipd_options(parser) | 752 cipd.add_cipd_options(parser) |
| 744 | 753 |
| 745 debug_group = optparse.OptionGroup(parser, 'Debugging') | 754 debug_group = optparse.OptionGroup(parser, 'Debugging') |
| 746 debug_group.add_option( | 755 debug_group.add_option( |
| 747 '--leak-temp-dir', | 756 '--leak-temp-dir', |
| 748 action='store_true', | 757 action='store_true', |
| 749 help='Deliberately leak isolate\'s temp dir for later examination. ' | 758 help='Deliberately leak isolate\'s temp dir for later examination. ' |
| 750 'Default: %default') | 759 'Default: %default') |
| 751 debug_group.add_option( | 760 debug_group.add_option( |
| 752 '--root-dir', help='Use a directory instead of a random one') | 761 '--root-dir', help='Use a directory instead of a random one') |
| 753 parser.add_option_group(debug_group) | 762 parser.add_option_group(debug_group) |
| 754 | 763 |
| 755 auth.add_auth_options(parser) | 764 auth.add_auth_options(parser) |
| 756 | 765 |
| 757 parser.set_defaults(cache='cache', cipd_cache='cipd_cache') | 766 parser.set_defaults(cache='cache', cipd_cache='cipd_cache') |
| 758 return parser | 767 return parser |
| 759 | 768 |
| 760 | 769 |
| 761 def main(args): | 770 def parse_args(args): |
| 771 # Create a fake mini-parser just to get out the "-a" command. Note that |
| 772 # it's not documented here; instead, it's documented in create_option_parser |
| 773 # even though that parser will never actually get to parse it. This is |
| 774 # because --argsfile is exclusive with all other options and arguments. |
| 775 file_argparse = argparse.ArgumentParser(add_help=False) |
| 776 file_argparse.add_argument('-a', '--argsfile') |
| 777 (file_args, nonfile_args) = file_argparse.parse_known_args(args) |
| 778 if file_args.argsfile: |
| 779 if nonfile_args: |
| 780 file_argparse.error('Can\'t specify --argsfile with' |
| 781 'any other arguments (%s)' % nonfile_args) |
| 782 try: |
| 783 with open(file_args.argsfile, 'r') as f: |
| 784 args = json.load(f) |
| 785 except (IOError, OSError, ValueError) as e: |
| 786 # We don't need to error out here - "args" is now empty, |
| 787 # so the call below to parser.parse_args(args) will fail |
| 788 # and print the full help text. |
| 789 print >> sys.stderr, 'Couldn\'t read arguments: %s' % e |
| 790 |
| 791 # Even if we failed to read the args, just call the normal parser now since it |
| 792 # will print the correct help message. |
| 762 parser = create_option_parser() | 793 parser = create_option_parser() |
| 763 options, args = parser.parse_args(args) | 794 options, args = parser.parse_args(args) |
| 795 return (parser, options, args) |
| 796 |
| 797 |
| 798 def main(args): |
| 799 (parser, options, args) = parse_args(args) |
| 764 | 800 |
| 765 isolated_cache = isolateserver.process_cache_options(options) | 801 isolated_cache = isolateserver.process_cache_options(options) |
| 766 if options.clean: | 802 if options.clean: |
| 767 if options.isolated: | 803 if options.isolated: |
| 768 parser.error('Can\'t use --isolated with --clean.') | 804 parser.error('Can\'t use --isolated with --clean.') |
| 769 if options.isolate_server: | 805 if options.isolate_server: |
| 770 parser.error('Can\'t use --isolate-server with --clean.') | 806 parser.error('Can\'t use --isolate-server with --clean.') |
| 771 if options.json: | 807 if options.json: |
| 772 parser.error('Can\'t use --json with --clean.') | 808 parser.error('Can\'t use --json with --clean.') |
| 773 isolated_cache.cleanup() | 809 isolated_cache.cleanup() |
| (...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 822 except cipd.Error as ex: | 858 except cipd.Error as ex: |
| 823 print >> sys.stderr, ex.message | 859 print >> sys.stderr, ex.message |
| 824 return 1 | 860 return 1 |
| 825 | 861 |
| 826 | 862 |
| 827 if __name__ == '__main__': | 863 if __name__ == '__main__': |
| 828 subprocess42.inhibit_os_error_reporting() | 864 subprocess42.inhibit_os_error_reporting() |
| 829 # Ensure that we are always running with the correct encoding. | 865 # Ensure that we are always running with the correct encoding. |
| 830 fix_encoding.fix_encoding() | 866 fix_encoding.fix_encoding() |
| 831 file_path.enable_symlink() | 867 file_path.enable_symlink() |
| 868 |
| 832 sys.exit(main(sys.argv[1:])) | 869 sys.exit(main(sys.argv[1:])) |
| OLD | NEW |