Skip to content

Commit bbe1023

Browse files
committed
new APIs to avoid exceptions
1 parent 3534bd5 commit bbe1023

15 files changed

+190
-106
lines changed

.gcloudignore

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
*.bak
22
.DS_Store
3-
3+
*-test.txt
44
test_scripts/
55
test_*
66
sample_data/

.gitignore

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
TEMP*
44

55
test_scripts/
6-
6+
*-test.text
77
test_*
88
# Environments
99
.env

config.yaml

+15-9
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,15 @@
33

44
# Keys:
55

6-
# Change this before first deployment for added security in communication between PubSub and the Iris App on App Engine.
7-
# You could even re-generate a new token per deployment.
8-
# Note that this approach is not very secure, though it was was once recommended by Google.
9-
# However, so long as your GCP project is secure, this token protects against unwanted invocations.
10-
# Also, the worst an invocation can do is re-trigger idempotent behavior, with possible DOS.
11-
pubsub_verification_token: 2a343f4c1b76512039fe763412756c4fbb30c
6+
# projects: Only resources in these projects will get labeled.
7+
# But if the value is empty, *all* projects in the organization are included.
8+
projects:
9+
- joshua-playground
10+
11+
# plugins: Only these plugins are enabled.
12+
# For example, list some of these: bigquery, bigtable, disks, buckets, cloudsql, instances, snaphots, subscriptions, topics
13+
# But if the value is empty, *all* plugins are enabled
14+
plugins: {}
1215

1316
# iris_prefix is prefixed to the key of each label that is added.
1417
iris_prefix: iris
@@ -22,6 +25,9 @@ from_project: True
2225
# get labeled on cron.
2326
label_all_on_cron: True
2427

25-
# projects: Only resources in these projects will get labeled.
26-
# But if the value is empty, *all* projects in the organization are included.
27-
projects: {}
28+
# Change this before first deployment for added security in communication between PubSub and the Iris App on App Engine.
29+
# You could even re-generate a new token per deployment.
30+
# Note that this approach is not very secure, though it was was once recommended by Google.
31+
# However, so long as your GCP project is secure, this token protects against unwanted invocations.
32+
# Also, the worst an invocation can do is re-trigger idempotent behavior, with possible DOS.
33+
pubsub_verification_token: 2a343f4c1b76512039fe763412756c4fbb30c

config.yaml.original

+14-9
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@
33

44
# Keys:
55

6-
# Change this before first deployment for added security in communication between PubSub and the Iris App on App Engine.
7-
# You could even re-generate a new token per deployment.
8-
# Note that this approach is not very secure, though it was was once recommended by Google.
9-
# However, so long as your GCP project is secure, this token protects against unwanted invocations.
10-
# Also, the worst an invocation can do is re-trigger idempotent behavior, with possible DOS.
11-
pubsub_verification_token: 2a343f4c1b76512039fe763412756c4fbb30c
6+
# projects: Only resources in these projects will get labeled.
7+
# But if the value is empty, *all* projects in the organization are included.
8+
projects: {}
9+
10+
# plugins: Only these plugins are enabled.
11+
# For example, list some of these: bigquery, buckets, bigtable, disks, cloudsql, instances, snaphots, subscriptions, topics
12+
# But if the value is empty, *all* plugins are enabled
13+
plugins: {}
1214

1315
# iris_prefix is prefixed to the key of each label that is added.
1416
iris_prefix: iris
@@ -22,6 +24,9 @@ from_project: True
2224
# get labeled on cron.
2325
label_all_on_cron: True
2426

25-
# projects: Only resources in these projects will get labeled.
26-
# But if the value is empty, *all* projects in the organization are included.
27-
projects: {}
27+
# Change this before first deployment for added security in communication between PubSub and the Iris App on App Engine.
28+
# You could even re-generate a new token per deployment.
29+
# Note that this approach is not very secure, though it was was once recommended by Google.
30+
# However, so long as your GCP project is secure, this token protects against unwanted invocations.
31+
# Also, the worst an invocation can do is re-trigger idempotent behavior, with possible DOS.
32+
pubsub_verification_token: 2a343f4c1b76512039fe763412756c4fbb30c

config.yaml.test.template

+5-4
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
# In integration_test.sh, config.yaml.test.template is used to make a temporary config.yaml, then config.yaml is reverted at the end.
44
#
55
# Generate random token per-test-run as an additional isolation measure
6-
pubsub_verification_token: ${PUBSUB_TEST_TOKEN}
7-
iris_prefix: ${RUN_ID}
8-
from_project: True
9-
label_all_on_cron: False
106
projects:
117
- ${DEPLOYMENT_PROJECT}
128
- ${TEST_PROJECT}
9+
plugins: {}
10+
iris_prefix: ${RUN_ID}
11+
from_project: True
12+
label_all_on_cron: False
13+
pubsub_verification_token: ${PUBSUB_TEST_TOKEN}

gce_base/gce_zonal_base.py

+8-3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import util.gcp_utils
55
from gce_base.gce_base import GceBase
66

7+
from google.cloud import compute_v1
8+
79

810
class GceZonalBase(GceBase, metaclass=ABCMeta):
911
def _gcp_zone(self, gcp_object):
@@ -27,9 +29,12 @@ def _all_zones(self, project_id):
2729
"""
2830
Get all available zones.
2931
"""
30-
response = self._google_client.zones().list(project=project_id).execute()
31-
zones = [zone["description"] for zone in response["items"]]
32-
return zones
32+
zones_client = compute_v1.ZonesClient()
33+
34+
request = compute_v1.ListZonesRequest(project=project_id)
35+
zones = zones_client.list(request)
36+
ret = [z.name for z in zones]
37+
return ret
3338

3439
def block_labeling(self, gcp_object, original_labels):
3540
# goog-gke-node appears in Nodes and Disks; and goog-gke-volume appears in Disks

main.py

+30-12
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from util import pubsub_utils, gcp_utils, utils, config_utils
1515
from util.gcp_utils import detect_gae
1616

17-
from util.config_utils import iris_prefix, configured_project, get_config, pubsub_token
17+
from util.config_utils import iris_prefix, is_project_enabled, get_config, pubsub_token
1818
from util.utils import init_logging, log_time, timing
1919

2020
import googlecloudprofiler
@@ -88,7 +88,7 @@ def schedule():
8888
nonappscript_projects = [p for p in all_projects if p not in appscript_projects]
8989

9090
configured_projects = [
91-
p for p in nonappscript_projects if config_utils.configured_project(p)
91+
p for p in nonappscript_projects if config_utils.is_project_enabled(p)
9292
]
9393

9494
skipped_nonappscript_projects = [
@@ -126,7 +126,7 @@ def schedule():
126126
raise Exception(msg)
127127

128128
for project_id in configured_projects:
129-
for _, plugin_ in Plugin.instances.items():
129+
for _, plugin_ in Plugin.plugins.items():
130130
if (
131131
not plugin_.is_labeled_on_creation()
132132
or plugin_.relabel_on_cron()
@@ -175,7 +175,7 @@ def label_one():
175175
method_from_log = data["protoPayload"]["methodName"]
176176

177177
plugins_found = []
178-
for plugin_name, plugin in Plugin.instances.items():
178+
for plugin_name, plugin in Plugin.plugins.items():
179179
for supported_method in plugin.method_names():
180180
if supported_method.lower() in method_from_log.lower():
181181
if plugin.is_labeled_on_creation():
@@ -185,8 +185,19 @@ def label_one():
185185
plugin_name
186186
) # Append it even if not used due to is_labeled_on_creation False
187187

188-
if len(plugins_found) != 1:
189-
logging.error(f"Error: plugins found {plugins_found} for {method_from_log}")
188+
if not plugins_found:
189+
logging.info(
190+
"(OK if plugin is disabled.) No plugins found for %s. Enabled plugins are %s",
191+
method_from_log,
192+
config_utils.enabled_plugins(),
193+
)
194+
195+
if len(plugins_found) > 1:
196+
raise Exception(
197+
f"Error: Multiple plugins found %s for %s"
198+
% (plugins_found, method_from_log)
199+
)
200+
190201
return "OK", 200
191202
except Exception as e:
192203
project_id = data.get("resource", {}).get("labels", {}).get("project_id")
@@ -200,7 +211,7 @@ def __label_one_0(data, plugin):
200211
gcp_object = plugin.get_gcp_object(data)
201212
if gcp_object is not None:
202213
project_id = data["resource"]["labels"]["project_id"]
203-
if configured_project(project_id):
214+
if is_project_enabled(project_id):
204215
logging.info("Will label_one() in %s, %s", project_id, gcp_object)
205216
plugin.label_resource(gcp_object, project_id)
206217
plugin.do_batch()
@@ -254,13 +265,20 @@ def do_label():
254265
plugin_class_name = data["plugin"]
255266

256267
plugin = Plugin.get_plugin(plugin_class_name)
257-
project_id = data["project_id"]
258-
with timing(f"do_label {plugin_class_name} {project_id}"):
268+
if not plugin:
259269
logging.info(
260-
"do_label() for %s in %s", plugin.__class__.__name__, project_id
270+
"(OK if plugin is disabled.) No plugins found for %s. Enabled plugins are %s",
271+
plugin_class_name,
272+
config_utils.enabled_plugins(),
261273
)
262-
plugin.label_all(project_id)
263-
logging.info("OK on do_label %s %s", plugin_class_name, project_id)
274+
else:
275+
project_id = data["project_id"]
276+
with timing(f"do_label {plugin_class_name} {project_id}"):
277+
logging.info(
278+
"do_label() for %s in %s", plugin.__class__.__name__, project_id
279+
)
280+
plugin.label_all(project_id)
281+
logging.info("OK on do_label %s %s", plugin_class_name, project_id)
264282
return "OK", 200
265283
except Exception as e:
266284
logging.exception(

plugin.py

+9-8
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from googleapiclient import discovery
88
from googleapiclient import errors
99

10-
from util import gcp_utils
10+
from util import gcp_utils, config_utils
1111
from util.config_utils import is_copying_labels_from_project, iris_prefix
1212
from util.utils import methods, cls_by_name, log_time, timed_lru_cache
1313

@@ -22,8 +22,8 @@ class Plugin(object, metaclass=ABCMeta):
2222

2323
# For a class to know its subclasses and their instances is generally bad.
2424
# We could create a separate PluginManager but let's not get too Java-ish.
25-
instances: typing.Dict[str, "Plugin"]
26-
instances = {}
25+
plugins: typing.Dict[str, "Plugin"]
26+
plugins = {}
2727

2828
def __init__(self):
2929
self._google_client = discovery.build(*self.discovery_api())
@@ -139,15 +139,16 @@ def load_plugin_class(name) -> typing.Type:
139139
return plugin_cls
140140

141141
for _, module, _ in pkgutil.iter_modules([PLUGINS_MODULE]):
142-
plugin_class = load_plugin_class(module)
143-
instance = plugin_class()
144-
Plugin.instances[plugin_class.__name__] = instance
142+
if config_utils.is_plugin_enabled(module):
143+
plugin_class = load_plugin_class(module)
144+
instance = plugin_class()
145+
Plugin.plugins[plugin_class.__name__] = instance
145146

146-
assert Plugin.instances, "No plugins defined"
147+
assert Plugin.plugins, "No plugins defined"
147148

148149
@staticmethod
149150
def get_plugin(plugin_name: str) -> "Plugin":
150-
return Plugin.instances[plugin_name]
151+
return Plugin.plugins.get(plugin_name)
151152

152153
def _build_labels(self, gcp_object, project_id):
153154
"""

plugins/instances.py

+35-19
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import logging
22

3+
from google.cloud import compute_v1
34
from googleapiclient import errors
45

56
from gce_base.gce_zonal_base import GceZonalBase
@@ -24,27 +25,42 @@ def method_names(self):
2425
return ["compute.instances.insert", "compute.instances.start"]
2526

2627
def __list_instances(self, project_id, zone):
27-
instances = []
28-
page_token = None
29-
more_results = True
30-
while more_results:
31-
result = (
32-
self._google_client.instances()
33-
.list(
34-
project=project_id,
35-
zone=zone,
36-
pageToken=page_token,
28+
def list1():
29+
instances1 = []
30+
page_token = None
31+
more_results = True
32+
while more_results:
33+
result = (
34+
self._google_client.instances()
35+
.list(
36+
project=project_id,
37+
zone=zone,
38+
pageToken=page_token,
39+
)
40+
.execute()
3741
)
38-
.execute()
42+
if "items" in result:
43+
instances = instances1 + result["items"]
44+
if "nextPageToken" in result:
45+
page_token = result["nextPageToken"]
46+
else:
47+
more_results = False
48+
return instances1
49+
50+
ret1 = list1()
51+
52+
def list2():
53+
instances_client = compute_v1.InstancesClient()
54+
55+
request = compute_v1.ListInstancesRequest(
56+
project=project_id, zone="us-central1-a"
3957
)
40-
if "items" in result:
41-
instances = instances + result["items"]
42-
if "nextPageToken" in result:
43-
page_token = result["nextPageToken"]
44-
else:
45-
more_results = False
46-
47-
return instances
58+
instances2 = instances_client.list(request)
59+
60+
return list(instances2)
61+
62+
ret2 = list2()
63+
return ret1
4864

4965
def __get_instance(self, project_id, zone, name):
5066
try:

requirements.txt

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ google-cloud-pubsub==2.8.0
44
oauth2client==4.1.3
55

66
ratelimit==2.2.1
7-
#google-cloud-resource-manager==0.30.3
7+
google-cloud-compute==0.6.0
8+
google-cloud-resource-manager==1.3.0
89
black
910
google-cloud-profiler
1011
# gunicorn is only needed if you add entrypoint: gunicorn -b :$PORT --worker-class=sync --workers=1 main:app

test_scripts/test_do_label.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import sys
44

55
from test_scripts.utils_for_tests import do_local_http
6+
from util import config_utils
67

78
"""
89
This is a debugging tool useful in development.
@@ -61,7 +62,8 @@ def main():
6162
chosen_plugins = [s.strip() for s in chosen_plugins]
6263
unsupported = [p for p in chosen_plugins if p not in PLUGINS]
6364
if unsupported:
64-
raise Exception(f"Unsupported: {', '.join(unsupported)}")
65+
raise Exception(f"Error: \"{', '.join(unsupported)}\" not a legal value. "
66+
f"For this test, you can use these (comma-separated) in the env variable: {PLUGINS}")
6567
print(f"Will do_label on{msg}: {', '.join(chosen_plugins)} ")
6668
test_do_label(chosen_plugins)
6769

0 commit comments

Comments
 (0)