├── .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 |
--------------------------------------------------------------------------------