├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── LICENSE.txt ├── README.md ├── cloud_utils ├── __init__.py └── list_instances.py ├── setup.cfg └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | cloud_utils.egg-info/ -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | We'd love to accept your patches and contributions to this project. There are 4 | just a few small guidelines you need to follow. 5 | 6 | ## Contributor License Agreement 7 | 8 | Contributions to this project must be accompanied by a Contributor License 9 | Agreement. You (or your employer) retain the copyright to your contribution, 10 | this simply gives us permission to use and redistribute your contributions as 11 | part of the project. Head over to to see 12 | your current agreements on file or to sign a new one. 13 | 14 | You generally only need to submit a CLA once, so if you've already submitted one 15 | (even if it was for a different project), you probably don't need to do it 16 | again. 17 | 18 | ## Code reviews 19 | 20 | All submissions, including submissions by project members, require review. We 21 | use GitHub pull requests for this purpose. Consult 22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more 23 | information on using pull requests. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2017 Google Inc. 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2017 Google Inc. 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Multi Cloud Utils 2 | 3 | Multi Cloud Utils is a library to handle standard operations across mutliple cloud platforms. 4 | 5 | Currently supported clouds are GCP, and AWS. 6 | 7 | This is not an official Google product 8 | 9 | supported opertations: 10 | 11 | ## list_instances 12 | 13 | Example: list_instances.py foo\*bar\*ex\* 14 | ``` 15 | Cloud Name Zone Id State Ip Address Type Created Autoscaling Group Iam Or Service Account Private Ip Address Project 16 | aws aws-foo-bar-exampl ap-west-1a i-123456789abcdefgh stopped None r3.8xl 2017-06-01 10:48:56 None foobar None aws 17 | gcp gcp-foo-bar-exampl us-west1-b 0123456789012345678 running None mem-2 2017-06-01 10:48:56 None foobar None gcp 18 | ``` 19 | 20 | Recommended alias: 21 | ```alias li='path/to/list_instances.py'``` 22 | -------------------------------------------------------------------------------- /cloud_utils/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | -------------------------------------------------------------------------------- /cloud_utils/list_instances.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from __future__ import print_function 16 | 17 | """list instances from AWS, and GCP.""" 18 | import sys 19 | 20 | from argparse import ArgumentParser 21 | import boto3 22 | import botocore 23 | from botocore.exceptions import ClientError 24 | from collections import namedtuple 25 | from concurrent.futures import ThreadPoolExecutor 26 | from dateutil.parser import parse as parse_date 27 | from functools import partial 28 | import kubernetes.client.rest 29 | import kubernetes.config.config_exception 30 | from pytz import UTC 31 | import re 32 | from texttable import Texttable 33 | 34 | try: 35 | from oauth2client.client import GoogleCredentials # pylint: disable=g-import-not-at-top 36 | import google.auth.exceptions # pylint: disable=g-import-not-at-top 37 | from googleapiclient import discovery # pylint: disable=g-import-not-at-top 38 | from googleapiclient import errors # pylint: disable=g-import-not-at-top 39 | except ImportError: 40 | print("Warning, google-api-python-client or google-auth not installed, can't connect to GCP instances.") 41 | 42 | try: 43 | from kubernetes import client, config # pylint: disable=g-import-not-at-top,g-multiple-import 44 | except ImportError: 45 | print("Warning, kubernetes not installed, not getting pods") 46 | 47 | AUTOSCALING_GROUP_TAG = u'aws:autoscaling:groupName' 48 | AWS_REGION_LIST = ['us-east-1', 'eu-west-1', 'us-west-2', 49 | 'sa-east-1', 'ap-southeast-1', 'eu-central-1'] 50 | GCP_REGION_LIST = ['us-east1', 'asia-east1', 'asia-northeast1', 51 | 'asia-southeast1', 'europe-west1', 'us-central1', 'us-west1'] 52 | INSTANCE_FILEDS = ['cloud', 'created', 'name', 'region', 'id', 'state', 'ip_address', 'type', 'autoscaling_group', 53 | 'iam_or_service_account', 'public_dns_name', 'private_ip_address', 'project', 'zone', 'security_groups', 54 | 'tags', 'vpc_id', 'reservation_type'] 55 | DEFAULT_SHOWN_FIELDS = ['project', 'name', 'zone', 'id', 'state', 'ip_address', 'type', 'created', 56 | 'autoscaling_group', 'iam_or_service_account', 'private_ip_address'] 57 | GCP_INSTANCE_TYPE_DICT = {'standard': 'std', 'highmem': 'mem', 'n1-': '', 'highcpu': 'cpu', 'custom': 'ctm'} 58 | ALIGN_OPTIONS = ['l', 'c', 'r'] 59 | Instance = namedtuple('Instance', INSTANCE_FILEDS) 60 | SUPPORTED_CLOUDS = ('gcp', 'aws', 'k8s') 61 | 62 | 63 | def pretify_field(field): 64 | return ' '.join([word[0].upper() + word[1:] for word in field.split('_')]) 65 | 66 | 67 | def aws_get_instances_by_name(region, name, raw=True): 68 | """Get all instances in a region matching a name (with wildcards).""" 69 | return _aws_get_instance_by_tag(region, name, 'tag:Name', raw) 70 | 71 | 72 | def aws_get_instances_by_autoscaling_group_in_region(region, name, raw=True): 73 | return _aws_get_instance_by_tag(region, name, 'tag:aws:autoscaling:groupName', raw) 74 | 75 | 76 | def datetime_to_str(date): 77 | return date.astimezone(UTC).isoformat().split('+')[0].split('.')[0].replace('T', ' ') 78 | 79 | 80 | def _aws_instance_from_dict(region, instance, raw): 81 | tags = {tag['Key']: tag['Value'] for tag in instance.get('Tags', [])} 82 | return Instance(cloud='aws', # pylint: disable=expression-not-assigned 83 | zone=instance['Placement']['AvailabilityZone'], 84 | region=region, 85 | id=instance['InstanceId'], 86 | name=tags.get('Name', 'Empty'), 87 | state=instance['State']['Name'], 88 | type=instance['InstanceType'] if raw else instance['InstanceType'].replace('xlarge', 'xl'), 89 | autoscaling_group=tags.get(AUTOSCALING_GROUP_TAG, None), 90 | public_dns_name=instance['PublicDnsName'], 91 | ip_address=instance.get('PublicIpAddress'), 92 | iam_or_service_account=instance['IamInstanceProfile'][ 93 | 'Arn'].split('/')[1] if instance.get('IamInstanceProfile') else None, 94 | private_ip_address=instance.get('PrivateIpAddress'), 95 | project='aws', 96 | security_groups=[group['GroupName'] for group in instance['SecurityGroups']], 97 | tags=tags, 98 | created=datetime_to_str(instance['LaunchTime']), 99 | reservation_type=instance.get('InstanceLifecycle', 'on_demand'), 100 | vpc_id=instance.get('VpcId')) 101 | 102 | 103 | def _aws_get_instance_by_tag(region, name, tag, raw): 104 | """Get all instances matching a tag.""" 105 | client = boto3.session.Session().client('ec2', region) 106 | matching_reservations = client.describe_instances(Filters=[{'Name': tag, 'Values': [name]}]).get('Reservations', []) 107 | instances = [] 108 | [[instances.append(_aws_instance_from_dict(region, instance, raw)) # pylint: disable=expression-not-assigned 109 | for instance in reservation.get('Instances')] for reservation in matching_reservations if reservation] 110 | return instances 111 | 112 | 113 | def gcp_filter_projects_instances(projects, filters, raw=True, credentials=None): 114 | try: 115 | credentials = credentials or GoogleCredentials.get_application_default() 116 | except google.auth.exceptions.DefaultCredentialsError: 117 | return None 118 | 119 | if projects is None: 120 | resourcemanager = discovery.build('cloudresourcemanager', 'v1', credentials=credentials) 121 | projects = [project['projectId'] 122 | for project in resourcemanager.projects().list().execute().get('projects', [])] 123 | 124 | compute = discovery.build('compute', 'v1', credentials=credentials) 125 | batch = compute.new_batch_http_request() 126 | results = [] 127 | replies = [] 128 | for project in projects: 129 | for filter_to_apply in filters: 130 | batch.add(compute.instances().aggregatedList(project=project, 131 | filter=filter_to_apply), 132 | callback=partial(lambda request_id, result, exception, inside_filter, inside_project: replies.append((inside_project, inside_filter, result)), 133 | inside_filter=filter_to_apply, inside_project=project)) 134 | batch.execute() 135 | 136 | results.extend([result for project, filter, result in replies]) 137 | batch = compute.new_batch_http_request() 138 | next_batch_requests = [(project, filter, result) 139 | for project, filter, result in replies if result and result.get('nextPageToken')] 140 | replies = [] 141 | while next_batch_requests: 142 | [batch.add(compute.instances().aggregatedList(project=project, 143 | filter=filter, 144 | pageToken=result['nextPageToken']), 145 | callback=partial(lambda request_id, result, exception, inside_filter, inside_project: replies.append((inside_project, inside_filter, result)), 146 | inside_filter=filter, inside_project=project)) 147 | for project, filter, result in next_batch_requests] 148 | batch.execute() 149 | results.extend([result for project, filter, result in replies]) 150 | batch = compute.new_batch_http_request() 151 | next_batch_requests = [(project, filter, result) 152 | for project, filter, result in replies if result and result.get('nextPageToken')] 153 | replies = [] 154 | 155 | instances = [] 156 | [instances.extend(get_instace_object_from_gcp_list(result['id'].split('/', 2)[1], # pylint: disable=expression-not-assigned 157 | raw, 158 | result.get('items', {}))) 159 | for result in results if result is not None] 160 | return instances 161 | 162 | 163 | def gcp_get_instances_by_label(project, name, raw=True, credentials=None): 164 | return gcp_filter_projects_instances(projects=[project], 165 | filters=['labels.name eq {name}'.format(name=name.replace('*', '.*'))], 166 | raw=raw, 167 | credentials=credentials) 168 | 169 | 170 | def gcp_get_instances_by_name(project, name, raw=True, credentials=None): 171 | """Get instances in a GCP region matching name (with * wildcards).""" 172 | return gcp_filter_projects_instances(projects=[project], 173 | filters=['name eq {name}'.format(name=name.replace('*', '.*'))], 174 | raw=raw, 175 | credentials=credentials) 176 | 177 | 178 | def get_instace_object_from_gcp_list(project, raw, region_to_instances): 179 | matching_intsances = [] 180 | for region, region_instances in region_to_instances.items(): 181 | for matching_instance in region_instances.get('instances', []): 182 | created_by = [item['value'] for item in matching_instance.get( 183 | 'metadata', {}).get('items', []) if item['key'] == 'created-by'] 184 | if created_by and 'instanceGroupManagers' in created_by[0]: 185 | if raw: 186 | autoscaling_group = created_by[0] 187 | else: 188 | autoscaling_group = created_by[0].split('/')[-1] 189 | else: 190 | autoscaling_group = None 191 | public_ip = None 192 | if raw: 193 | iam_or_service_account = ','.join([account['email'] 194 | for account in matching_instance.get('serviceAccounts', [])]) 195 | instance_type = matching_instance['machineType'].split('/')[-1] 196 | else: 197 | iam_or_service_account = ','.join([account['email'].split('@')[0] 198 | for account in matching_instance.get('serviceAccounts', [])]) 199 | instance_type = matching_instance['machineType'].split('/')[-1] 200 | for raw_type, pretty_type in GCP_INSTANCE_TYPE_DICT.items(): 201 | instance_type = instance_type.replace(raw_type, pretty_type) 202 | for interface in matching_instance['networkInterfaces']: 203 | if 'accessConfigs' in interface and interface['accessConfigs'] and interface['accessConfigs'][0].get('natIP'): 204 | public_ip = interface['accessConfigs'][0]['natIP'] 205 | tags = {metadata['key']: metadata['value'] for metadata in matching_instance['metadata'].get('items', [])} 206 | tags.update(matching_instance.get('labels', {})) 207 | tags[None] = matching_instance['tags'].get('items', []) 208 | 209 | creation_datetime = datetime_to_str(parse_date(matching_instance['creationTimestamp'])) 210 | matching_intsances.append(Instance(cloud='gcp', 211 | zone=region.replace('zones/', ''), 212 | region=region.replace('zones/', '')[:-2], 213 | id=matching_instance['name'], 214 | name=matching_instance['name'] if 'name' not in tags else tags['name'], 215 | state=matching_instance['status'].lower(), 216 | type=instance_type, 217 | autoscaling_group=autoscaling_group, 218 | public_dns_name=None, 219 | ip_address=public_ip, 220 | iam_or_service_account=iam_or_service_account, 221 | private_ip_address=matching_instance['networkInterfaces'][0].get( 222 | 'networkIP'), 223 | project=project, 224 | security_groups=[], 225 | tags=tags, 226 | created=creation_datetime, 227 | reservation_type='preemptible' if matching_instance.get( 228 | 'preemptible') else 'on-demand', 229 | vpc_id=None)) 230 | return matching_intsances 231 | 232 | 233 | def aws_get_instances_by_id(region, instance_id, raw=True): 234 | """Returns instances mathing an id.""" 235 | client = boto3.session.Session().client('ec2', region) 236 | try: 237 | matching_reservations = client.describe_instances(InstanceIds=[instance_id]).get('Reservations', []) 238 | except ClientError as exc: 239 | if exc.response.get('Error', {}).get('Code') != 'InvalidInstanceID.NotFound': 240 | raise 241 | return [] 242 | instances = [] 243 | [[instances.append(_aws_instance_from_dict(region, instance, raw)) # pylint: disable=expression-not-assigned 244 | for instance in reservation.get('Instances')] for reservation in matching_reservations if reservation] 245 | return instances 246 | 247 | 248 | def get_instances_by_id(instance_id, regions=None, projects=None, raw=True, sort_by_order=('cloud', 'name'), clouds=SUPPORTED_CLOUDS): # pylint: disable=unused-argument 249 | instance_id = instance_id.replace('*', '') 250 | with ThreadPoolExecutor(max_workers=len(AWS_REGION_LIST)) as executor: 251 | if 'aws' in clouds: 252 | results = executor.map(partial(aws_get_instances_by_id, instance_id=instance_id), 253 | AWS_REGION_LIST) 254 | else: 255 | results = [] 256 | matching_instances = [] 257 | for result in results: 258 | matching_instances.extend(result) 259 | if regions: 260 | matching_instances = [instance for instance in matching_instances if instance.region in regions] 261 | return matching_instances 262 | 263 | 264 | def all_clouds_get_instances_by_name(name, projects, raw, credentials, clouds=SUPPORTED_CLOUDS): 265 | with ThreadPoolExecutor(max_workers=2 * len(AWS_REGION_LIST) + 2) as executor: 266 | if 'aws' in clouds: 267 | aws_name_results = executor.map(partial(aws_get_instances_by_name, name=name, raw=raw), 268 | AWS_REGION_LIST) 269 | aws_autoscaling_results = executor.map(partial(aws_get_instances_by_autoscaling_group_in_region, name=name, raw=raw), 270 | AWS_REGION_LIST) 271 | if 'gcp' in clouds: 272 | gcp_job = executor.submit(gcp_filter_projects_instances, 273 | projects=projects, 274 | filters=['labels.name eq {name}'.format(name=name.replace('*', '.*')), 275 | 'name eq {name}'.format(name=name.replace('*', '.*'))], 276 | raw=raw, 277 | credentials=credentials) 278 | if 'k8s' in clouds: 279 | kube_job = executor.submit(get_all_pods, name) 280 | 281 | matching_instances = [] 282 | if 'gcp' in clouds: 283 | matching_instances = gcp_job.result() 284 | if 'aws' in clouds: 285 | for instances in [result for result in aws_name_results] + [result for result in aws_autoscaling_results]: 286 | # for instances in results: 287 | matching_instances.extend(instances) 288 | if 'k8s' in clouds: 289 | matching_instances.extend(kube_job.result()) 290 | 291 | # Filter instances seen more than once (matching both autoscaling group and name) 292 | seen_instances = set() 293 | matching_instances = [instance 294 | for instance in matching_instances if instance.id not in seen_instances and ( 295 | seen_instances.add(instance.id) or True)] 296 | return matching_instances 297 | 298 | 299 | def get_instances_by_name(name, sort_by_order=('cloud', 'name'), projects=None, raw=True, regions=None, gcp_credentials=None, clouds=SUPPORTED_CLOUDS): 300 | """Get intsances from GCP and AWS by name.""" 301 | matching_instances = all_clouds_get_instances_by_name( 302 | name, projects, raw, credentials=gcp_credentials, clouds=clouds) 303 | if regions: 304 | matching_instances = [instance for instance in matching_instances if instance.region in regions] 305 | matching_instances.sort(key=lambda instance: [getattr(instance, field) for field in sort_by_order]) 306 | return matching_instances 307 | 308 | 309 | def get_instances_by_id_or_name(identifier, *args, **kwargs): 310 | id_regex = re.compile(r'^\*?i?-?(?P[0-9a-f]{17}|[0-9a-f]{8})\*?$') 311 | if id_regex.match(identifier): 312 | return get_instances_by_id(identifier, *args, **kwargs) 313 | return get_instances_by_name(identifier, *args, **kwargs) 314 | 315 | 316 | def run_map(func, args, max_workers=None): 317 | def wrapper(arg): 318 | result = func(arg) 319 | return arg, result 320 | 321 | with ThreadPoolExecutor(max_workers or len(args)) as executor: 322 | results = executor.map(wrapper, args) 323 | 324 | return {result[0]: result[1] for result in results} 325 | 326 | 327 | def get_pods_for_context(context, name): 328 | try: 329 | kube = client.CoreV1Api(config.new_client_from_config(context=context)) 330 | pod_dicts = [pod.to_dict() 331 | for pod in kube.list_pod_for_all_namespaces(watch=False, 332 | field_selector='metadata.namespace!=kube-system').items] 333 | except (kubernetes.client.rest.ApiException, kubernetes.config.config_exception.ConfigException): 334 | return [] 335 | return [pod for pod in pod_dicts if re.match(name, pod['metadata']['name'])] 336 | 337 | 338 | def get_all_pods(name): 339 | name = name.replace('*', '.*') 340 | try: 341 | all_contexts = [context['name'] for context in config.list_kube_config_contexts()[0]] 342 | except: # pylint:disable=bare-except 343 | return [] 344 | results = run_map(partial(get_pods_for_context, name=name), all_contexts, max_workers=2) 345 | instances = [] 346 | [[instances.append(Instance(cloud='k8s', # pylint: disable=expression-not-assigned 347 | zone=context, 348 | region=context, 349 | id=pod['metadata']['name'], 350 | name=pod['metadata']['name'], 351 | state=pod['status']['phase'].lower(), 352 | type='pod', 353 | autoscaling_group=None, 354 | public_dns_name=None, 355 | ip_address=None, 356 | iam_or_service_account=None, 357 | private_ip_address=None, 358 | project='k8s', 359 | security_groups=None, 360 | tags=None, 361 | created=datetime_to_str(pod['status']['start_time']), 362 | reservation_type='container', 363 | vpc_id=None)) 364 | for pod in pods] 365 | for context, pods in results.items()] 366 | return instances 367 | 368 | 369 | def get_os_version(instance): 370 | """Get OS Version for instances.""" 371 | if instance.cloud == 'aws': 372 | client = boto3.client('ec2', instance.region) 373 | image_id = client.describe_instances(InstanceIds=[instance.id])['Reservations'][0]['Instances'][0]['ImageId'] 374 | return '16.04' if '16.04' in client.describe_images(ImageIds=[image_id])['Images'][0]['Name'] else '14.04' 375 | if instance.cloud == 'gcp': 376 | credentials = GoogleCredentials.get_application_default() 377 | compute = discovery.build('compute', 'v1', credentials=credentials) 378 | for disk in compute.instances().get(instance=instance.name, 379 | zone=instance.zone, 380 | project=instance.project).execute()['disks']: 381 | if not disk.get('boot'): 382 | continue 383 | for value in disk.get('licenses', []): 384 | if '1604' in value: 385 | return '16.04' 386 | if '1404' in value: 387 | return '14.04' 388 | return '14.04' 389 | return '14.04' 390 | 391 | 392 | def get_volumes(instance): 393 | """Returns all the volumes of an instance.""" 394 | if instance.cloud == 'aws': 395 | client = boto3.session.Session().client('ec2', instance.region) 396 | devices = client.describe_instance_attribute( 397 | InstanceId=instance.id, Attribute='blockDeviceMapping').get('BlockDeviceMappings', []) 398 | volumes = client.describe_volumes(VolumeIds=[device['Ebs']['VolumeId'] 399 | for device in devices if device.get('Ebs', {}).get('VolumeId')]).get('Volumes', []) 400 | return {volume['Attachments'][0]['Device']: {'size': volume['Size'], 'volume_type': volume['VolumeType']} for volume in volumes} 401 | if instance.cloud == 'gcp': 402 | credentials = GoogleCredentials.get_application_default() 403 | compute = discovery.build('compute', 'v1', credentials=credentials) 404 | volumes = {} 405 | for disk in compute.instances().get(instance=instance.id, 406 | zone=instance.zone, 407 | project=instance.project).execute()['disks']: 408 | index = disk['index'] 409 | name = disk['deviceName'] if disk['deviceName'] not in [u'persistent-disk-0', 'boot'] else instance.id 410 | if 'local-ssd' in disk['deviceName']: 411 | size = 375.0 412 | disk_type = 'local-ssd' 413 | else: 414 | size = float(disk.get('diskSizeGb', 0.)) 415 | disk_type = 'pd-ssd' 416 | volumes[index] = {'size': size, 417 | 'type': disk['type'], 418 | 'deviceName': disk['deviceName'], 419 | 'interface': disk['interface'], 420 | 'diskType': disk_type} 421 | return volumes 422 | raise ValueError('Unknown cloud %s' % instance.cloud) 423 | 424 | 425 | def get_persistent_address(instance): 426 | """Returns the public ip address of an instance.""" 427 | if instance.cloud == 'aws': 428 | client = boto3.client('ec2', instance.region) 429 | try: 430 | client.describe_addresses(PublicIps=[instance.ip_address]) 431 | return instance.ip_address 432 | except botocore.client.ClientError as exc: 433 | if exc.response.get('Error', {}).get('Code') != 'InvalidAddress.NotFound': 434 | raise 435 | # Address is not public 436 | return None 437 | if instance.cloud == 'gcp': 438 | credentials = GoogleCredentials.get_application_default() 439 | compute = discovery.build('compute', 'v1', credentials=credentials) 440 | try: 441 | return compute.addresses().get(address=instance.name, project=instance.project, region=instance.region).execute()['address'] 442 | except errors.HttpError as exc: 443 | if 'was not found' in str(exc): 444 | return None 445 | raise 446 | raise ValueError('Unknown cloud %s' % instance.cloud) 447 | 448 | 449 | def get_image(instance): 450 | if instance.cloud == 'gcp': 451 | if instance.autoscaling_group is None: 452 | return None 453 | compute = discovery.build('compute', 'v1') 454 | instance_template = compute.instanceTemplates().get(project=instance.project, 455 | instanceTemplate=instance.tags['instance-template'] 456 | .rsplit('/', 1)[1]).execute() 457 | 458 | return instance_template['properties']['disks'][0]['initializeParams']['sourceImage'].split('/')[-1] 459 | elif instance.cloud == 'aws': 460 | ec2 = boto3.client('ec2', instance.region) 461 | return ec2.describe_instances(InstanceIds=[instance.id]).get(u'Reservations', [])[0]['Instances'][0].get('ImageId', None) 462 | else: 463 | raise NotImplementedError('Unsupported cloud {}'.format(instance.cloud)) 464 | return None 465 | 466 | 467 | def get_cloud_from_zone(zone): 468 | for region in AWS_REGION_LIST: 469 | if region in zone: 470 | return 'aws' 471 | for region in GCP_REGION_LIST: 472 | if region in zone: 473 | return 'gcp' 474 | raise ValueError('Unknown zone %s' % zone) 475 | 476 | 477 | def parse_args(): 478 | parser = ArgumentParser() 479 | parser.add_argument('name', nargs='*', help='name to search') 480 | parser.add_argument('--by-id', action='store_true', default=False, 481 | help='Deprecated- All searches are by id or name') 482 | parser.add_argument('-w', '--width', required=False, type=int, default=0, help='printed table width') 483 | parser.add_argument('-l', '--align', required=False, type=str, default='c', choices=ALIGN_OPTIONS, 484 | help='align output {0}'.format(ALIGN_OPTIONS)) 485 | parser.add_argument('-s', '--sort-by', nargs='*', required=False, 486 | default=['cloud', 'name', 'region'], help='sort by (list of fields to sort by)') 487 | parser.add_argument('-f', '--fields', nargs='*', required=False, default=DEFAULT_SHOWN_FIELDS, 488 | help='fields to output legal values are: {}'.format(INSTANCE_FILEDS)) 489 | parser.add_argument('-P', '--projects', nargs='*', required=False, default=None, 490 | help='gcp projects to search') 491 | parser.add_argument('--raw', action='store_true', default=False, help='raw ouput (no pretify)') 492 | parser.add_argument('-r', '--regions', default=None, nargs='*', help='Regions to search in') 493 | parser.add_argument('--strict', action='store_true', default=False, 494 | help='search without automatic prefix and suffix *') 495 | parser.add_argument('--active', action='store_true', default=False, help='Only list active instances') 496 | parser.add_argument('--gevent', action='store_true', default=False, help='Use gevent') 497 | parser.add_argument('-q', '--quiet', action='store_true', default=False, help='Quiet (no headlines)') 498 | parser.add_argument('--clouds', nargs='*', default=SUPPORTED_CLOUDS, 499 | help='clouds to search default: {}'.format(SUPPORTED_CLOUDS)) 500 | 501 | args = parser.parse_args() 502 | if not args.name: 503 | name = sys.argv.pop(-1) 504 | args = parser.parse_args() 505 | args.name = [name] 506 | args.clouds = [cloud.lower() for cloud in args.clouds] 507 | 508 | return args 509 | 510 | 511 | def main(args): 512 | if args.gevent: 513 | import gevent.monkey 514 | gevent.monkey.patch_all() 515 | if not args.strict: 516 | args.name = ['*' + name + '*' for name in args.name] 517 | table = Texttable(args.width) 518 | table.set_cols_dtype(['t'] * len(args.fields)) 519 | table.set_cols_align([args.align] * len(args.fields)) 520 | if not args.quiet: 521 | table.add_row([pretify_field(field) for field in args.fields]) 522 | table.set_deco(0) 523 | instances = [] 524 | for required_name in args.name: 525 | instances.extend(get_instances_by_id_or_name(required_name, projects=args.projects, 526 | raw=args.raw, regions=args.regions, clouds=args.clouds)) 527 | if args.active: 528 | instances = [instance for instance in instances if instance.state == 'running'] 529 | if not instances: 530 | sys.exit(1) 531 | instances.sort(key=lambda instance: [getattr(instance, field) for field in args.sort_by]) 532 | table.add_rows([[getattr(instance, field) for field in args.fields] 533 | for instance in instances], header=False) 534 | print(table.draw()) 535 | 536 | 537 | if __name__ == '__main__': 538 | main(parse_args()) 539 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from setuptools import setup 16 | LICENSE = '''Copyright 2017 Google Inc. 17 | 18 | Licensed under the Apache License, Version 2.0 (the "License"); 19 | you may not use this file except in compliance with the License. 20 | You may obtain a copy of the License at 21 | 22 | https://www.apache.org/licenses/LICENSE-2.0 23 | 24 | Unless required by applicable law or agreed to in writing, software 25 | distributed under the License is distributed on an "AS IS" BASIS, 26 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 27 | See the License for the specific language governing permissions and 28 | limitations under the License.''' 29 | 30 | VERSION = '1.0.43' 31 | setup(name='cloud-utils', 32 | version=VERSION, 33 | description='Python Cloud Utilities', 34 | author='Arie Abramovici', 35 | author_email='beast@google.com', 36 | url='https://github.com/google/python-cloud-utils', 37 | license=LICENSE, 38 | homepage='https://github.com/google/python-cloud-utils', 39 | packages=['cloud_utils'], 40 | entry_points={'console_scripts': ['list_instances = cloud_utils.list_instances:main']}, 41 | install_requires=['boto', 'boto3', 'botocore', 'google-api-python-client==1.6.2', 'urllib3>=1.24.2', 42 | 'google-auth', 'texttable', 'python-dateutil', 'pytz', 'kubernetes'], 43 | zip_safe=False) 44 | --------------------------------------------------------------------------------