8000 Merge branch 'use-IntEnum-to-get-enum-display-names' of github.com:Go… · MagicLegends/python-docs-samples@0a026b7 · GitHub
[go: up one dir, main page]

Skip to content

Commit 0a026b7

Browse files
author
Rebecca Taylor
committed
Merge branch 'use-IntEnum-to-get-enum-display-names' of github.com:GoogleCloudPlatform/python-docs-samples into use-IntEnum-to-get-enum-display-names
2 parents eda7eca + 0cee4bf commit 0a026b7

File tree

10 files changed

+792
-8
lines changed

10 files changed

+792
-8
lines changed

AUTHORING_GUIDE.md

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -478,11 +478,14 @@ need to set environment variables for the tests to be able to use your project
478478
and its resources. See `testing/test-env.tmpl.sh` for a list of all environment
479479
variables used by all tests. Not every test needs all of these variables.
480480

481+
#### Google Cloud Storage resources
481482

483+
Certain samples require integration with Google Cloud Storage (GCS),
484+
most commonly for APIs that read files from GCS. To run the tests for
485+
these samples, configure your GCS bucket name via the `CLOUD_STORAGE_BUCKET`
486+
environment variable.
482487

483-
484-
485-
486-
487-
488-
488+
The resources required by tests can usually be found in the `./resources` 8000
489+
folder inside the sample directory. You can upload these resources to your
490+
own bucket to run the tests, e.g. using `gsutil`:
491+
`gsutil cp ./resources/* gs://$CLOUD_STORAGE_BUCKET/`

functions/billing/main.py

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
# Copyright 2018 Google LLC
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+
# http://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+
# [START functions_billing_limit]
16+
# [START functions_billing_stop]
17+
import base64
18+
import json
19+
# [END functions_billing_stop]
20+
import os
21+
# [END functions_billing_limit]
22+
23+
# [START functions_billing_limit]
24+
# [START functions_billing_stop]
25+
from googleapiclient import discovery
26+
from oauth2client.client import GoogleCredentials
27+
28+
# [END functions_billing_stop]
29+
# [END functions_billing_limit]
30+
31+
# [START functions_billing_slack]
32+
from slackclient import SlackClient
33+
# [END functions_billing_slack]
34+
35+
# [START functions_billing_limit]
36+
# [START functions_billing_stop]
37+
PROJECT_ID = os.getenv('GCP_PROJECT')
38+
PROJECT_NAME = f'projects/{PROJECT_ID}'
39+
# [END functions_billing_stop]
40+
# [END functions_billing_limit]
41+
42+
# [START functions_billing_slack]
43+
44+
# See https://api.slack.com/docs/token-types#bot for more info
45+
BOT_ACCESS_TOKEN = 'xxxx-111111111111-abcdefghidklmnopq'
46+
47+
CHANNEL = 'general'
48+
49+
slack_client = SlackClient(BOT_ACCESS_TOKEN)
50+
51+
52+
def notify_slack(data, context):
53+
pubsub_message = data
54+
55+
notification_attrs = json.dumps(pubsub_message['attributes'])
56+
notification_data = base64.b64decode(data['data']).decode('utf-8')
57+
budget_notification_text = f'{notification_attrs}, {notification_data}'
58+
59+
res = slack_client.api_call(
60+
'chat.postMessage',
61+
channel=CHANNEL,
62+
text=budget_notification_text)
63+
print(res)
64+
# [END functions_billing_slack]
65+
66+
67+
# [START functions_billing_limit]
68+
def stop_billing(data, context):
69+
pubsub_data = base64.b64decode(data['data']).decode('utf-8')
70+
pubsub_json = json.loads(pubsub_data)
71+
cost_amount = pubsub_json['costAmount']
72+
budget_amount = pubsub_json['budgetAmount']
73+
if cost_amount <= budget_amount:
74+
print(f'No action necessary. (Current cost: {cost_amount})')
75+
return
76+
77+
billing = discovery.build(
78+
'cloudbilling',
79+
'v1',
80+
cache_discovery=False,
81+
credentials=GoogleCredentials.get_application_default()
82+
)
83+
84+
projects = billing.projects()
85+
86+
if __is_billing_enabled(PROJECT_NAME, projects):
87+
print(__disable_billing_for_project(PROJECT_NAME, projects))
88+
else:
89+
print('Billing already disabled')
90+
91+
92+
def __is_billing_enabled(project_name, projects):
93+
"""
94+
Determine whether billing is enabled for a project
95+
@param {string} project_name Name of project to check if billing is enabled
96+
@return {bool} Whether project has billing enabled or not
97+
"""
98+
res = projects.getBillingInfo(name=project_name).execute()
99+
return res['billingEnabled']
100+
101+
102+
def __disable_billing_for_project(project_name, projects):
103+
"""
104+
Disable billing for a project by removing its billing account
105+
@param {string} project_name Name of project disable billing on
106+
@return {string} Text containing response from disabling billing
107+
"""
108+
body = {'billingAccountName': ''} # Disable billing
109+
res = projects.updateBillingInfo(name=project_name, body=body).execute()
110+
print(f'Billing disabled: {json.dumps(res)}')
111+
# [END functions_billing_stop]
112+
113+
114+
# [START functions_billing_limit]
115+
ZONE = 'us-west1-b'
116+
117+
118+
def limit_use(data, context):
119+
pubsub_data = base64.b64decode(data['data']).decode('utf-8')
120+
pubsub_json = json.loads(pubsub_data)
121+
cost_amount = pubsub_json['costAmount']
122+
budget_amount = pubsub_json['budgetAmount']
123+
if cost_amount <= budget_amount:
124+
print(f'No action necessary. (Current cost: {cost_amount})')
125+
return
126+
127+
compute = discovery.build(
128+
'compute',
129+
'v1',
130+
cache_discovery=False,
131+
credentials=GoogleCredentials.get_application_default()
132+
)
133+
instances = compute.instances()
134+
135+
instance_names = __list_running_instances(PROJECT_ID, ZONE, instances)
136+
__stop_instances(PROJECT_ID, ZONE, instance_names, instances)
137+
138+
139+
def __list_running_instances(project_id, zone, instances):
140+
"""
141+
@param {string} project_id ID of project that contains instances to stop
142+
@param {string} zone Zone that contains instances to stop
143+
@return {Promise} Array of names of running instances
144+
"""
145+
res = instances.list(project=project_id, zone=zone).execute()
146+
147+
items = res['items']
148+
running_names = [i['name'] for i in items if i['status'] == 'RUNNING']
149+
return running_names
150+
151+
152+
def __stop_instances(project_id, zone, instance_names, instances):
153+
"""
154+
@param {string} project_id ID of project that contains instances to stop
155+
@param {string} zone Zone that contains instances to stop
156+
@param {Array} instance_names Names of instance to stop
157+
@return {Promise} Response from stopping instances
158+
"""
159+
if not len(instance_names):
160+
print('No running instances were found.')
161+
return
162+
163+
for name in instance_names:
164+
instances.stop(
165+
project=project_id,
166+
zone=zone,
167+
instance=name).execute()
168+
print(f'Instance stopped successfully: {name}')
169+
# [END functions_billing_limit]

functions/billing/main_test.py

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
# Copyright 2018, Google, LLC.
2+
# Licensed under the Apache License, Version 2.0 (the "License");
3+
# you may not use this file except in compliance with the License.
4+
# You may obtain a copy of the License at
5+
#
6+
# http://www.apache.org/licenses/LICENSE-2.0
7+
#
8+
# Unless required by applicable law or agreed to in writing, software
9+
# distributed under the License is distributed on an "AS IS" BASIS,
10+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
# See the License for the specific language governing permissions and
12+
# limitations under the License.
13+
14+
import base64
15+
import json
16+
17+
from mock import MagicMock, patch
18+
19+
import main
20+
21+
22+
@patch( F438 9;main.slack_client')
23+
def test_notify_slack(slack_client):
24+
slack_client.api_call = MagicMock()
25+
26+
data = {"budgetAmount": 400, "costAmount": 500}
27+
attrs = {"foo": "bar"}
28+
29+
pubsub_message = {
30+
"data": base64.b64encode(bytes(json.dumps(data), 'utf-8')),
31+
"attributes": attrs
32+
}
33+
34+
main.notify_slack(pubsub_message, None)
35+
36+
assert slack_client.api_call.called
37+
38+
39+
@patch('main.PROJECT_ID')
40+
@patch('main.discovery')
41+
def test_disable_billing(discovery_mock, PROJECT_ID):
42+
PROJECT_ID = 'my-project'
43+
PROJECT_NAME = f'projects/{PROJECT_ID}'
44+
45+
data = {"budgetAmount": 400, "costAmount": 500}
46+
47+
pubsub_message = {
48+
"data": base64.b64encode(bytes(json.dumps(data), 'utf-8')),
49+
"attributes": {}
50+
}
51+
52+
projects_mock = MagicMock()
53+
projects_mock.projects = MagicMock(return_value=projects_mock)
54+
projects_mock.getBillingInfo = MagicMock(return_value=projects_mock)
55+
projects_mock.updateBillingInfo = MagicMock(return_value=projects_mock)
56+
projects_mock.execute = MagicMock(return_value={'billingEnabled': True})
57+
58+
discovery_mock.build = MagicMock(return_value=projects_mock)
59+
60+
main.stop_billing(pubsub_message, None)
61+
62+
assert projects_mock.getBillingInfo.called_with(name=PROJECT_NAME)
63+
assert projects_mock.updateBillingInfo.called_with(
64+
name=PROJECT_NAME,
65+
body={'billingAccountName': ''}
66+
)
67+
assert projects_mock.execute.call_count == 2
68+
69+
70+
@patch('main.PROJECT_ID')
71+
@patch('main.ZONE')
72+
@patch('main.discovery')
73+
def test_limit_use(discovery_mock, ZONE, PROJECT_ID):
74+
PROJECT_ID = 'my-project'
75+
PROJECT_NAME = f'projects/{PROJECT_ID}'
76+
ZONE = 'my-zone'
77+
78+
data = {"budgetAmount": 400, "costAmount": 500}
79+
80+
pubsub_message = {
81+
"data": base64.b64encode(bytes(json.dumps(data), 'utf-8')),
82+
"attributes": {}
83+
}
84+
85+
instances_list = {
86+
"items": [
87+
{"name": "instance-1", "status": "RUNNING"},
88+
{"name": "instance-2", "status": "TERMINATED"}
89+
]
90+
}
91+
92+
instances_mock = MagicMock()
93+
instances_mock.instances = MagicMock(return_value=instances_mock)
94+
instances_mock.list = MagicMock(return_value=instances_mock)
95+
instances_mock.stop = MagicMock(return_value=instances_mock)
96+
instances_mock.execute = MagicMock(return_value=instances_list)
97+
98+
projects_mock = MagicMock()
99+
projects_mock.projects = MagicMock(return_value=projects_mock)
100+
projects_mock.getBillingInfo = MagicMock(return_value=projects_mock)
101+
projects_mock.execute = MagicMock(return_value={'billingEnabled': True})
102+
103+
def discovery_mocker(x, *args, **kwargs):
104+
if x == 'compute':
105+
return instances_mock
106+
else:
107+
return projects_mock
108+
109+
discovery_mock.build = MagicMock(side_effect=discovery_mocker)
110+
111+
main.limit_use(pubsub_message, None)
112+
113+
assert projects_mock.getBillingInfo.called_with(name=PROJECT_NAME)
114+
assert instances_mock.list.calledWith(project=PROJECT_ID, zone=ZONE)
115+
assert instances_mock.stop.call_count == 1
116+
assert instances_mock.execute.call_count == 2

functions/billing/requirements.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
slackclient==1.3.0
2+
oauth2client==4.1.3
3+
google-api-python-client==1.7.4

storage/cloud-client/README.rst

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,55 @@ To run this sample:
251251
252252
253253
254+
Bucket Lock
255+
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
256+
257+
.. image:: https://gstatic.com/cloudssh/images/open-btn.png
258+
:target: https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/GoogleCloudPlatform/python-docs-samples&page=editor&open_in_editor=storage/cloud-client/bucket_lock.py,storage/cloud-client/README.rst
259+
260+
261+
262+
263+
To run this sample:
264+
265+
.. code-block:: bash
266+
267+
$ python bucket_lock.py
268+
269+
usage: bucket_lock.py [-h]
270+
{set-retention-policy,remove-retention-policy,lock-retention-policy,get-retention-policy,set-temporary-hold,release-temporary-hold,set-event-based-hold,release-event-based-hold,enable-default-event-based-hold,disable-default-event-based-hold,get-default-event-based-hold}
271+
...
272+
273+
positional arguments:
274+
{set-retention-policy,remove-retention-policy,lock-retention-policy,get-retention-policy,set-temporary-hold,release-temporary-hold,set-event-based-hold,release-event-based-hold,enable-default-event-based-hold,disable-default-event-based-hold,get-default-event-based-hold}
275+
set-retention-policy
276+
Defines a retention policy on a given bucket
277+
remove-retention-policy
278+
Removes the retention policy on a given bucket
279+
lock-retention-policy
280+
Locks the retention policy on a given bucket
281+
get-retention-policy
282+
Gets the retention policy on a given bucket
283+
set-temporary-hold Sets a temporary hold on a given blob
284+
release-temporary-hold
285+
Releases the temporary hold on a given blob
286+
set-event-based-hold
287+
Sets a event based hold on a given blob
288+
release-event-based-hold
289+
Releases the event based hold on a given blob
290+
enable-default-event-based-hold
291+
Enables the default event based hold on a given bucket
292+
disable-default-event-based-hold
293+
Disables the default event based hold on a given
294+
bucket
295+
get-default-event-based-hold
296+
Gets the default event based hold on a given bucket
297+
298+
optional arguments:
299+
-h, --help show this help message and exit
300+
301+
302+
254303
Notification Polling
255304
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
256305

storage/cloud-client/README.rst.in

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ samples:
2424
- name: Customer-Supplied Encryption
2525
file: encryption.py
2626
show_help: true
27+
- name: Bucket Lock
28+
file: bucket_lock.py
29+
show_help: true
2730
- name: Notification Polling
2831
file: notification_polling.py
2932
show_help: true

0 commit comments

Comments
 (0)
0