Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Google Cloud Support Slackbot rearchitecture for Cloud Run #772

Merged
merged 11 commits into from
Feb 25, 2022
11 changes: 11 additions & 0 deletions tools/google-cloud-support-slackbot/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Dockerfile
error.log
env.list
freeze.txt
.dockerignore
archive/*
archive
google_cloud_support_slackbot_icon_big.png
google_cloud_support_slackbot_icon_small.png
google_cloud_support_slackbot_icon.svg
README.md
50 changes: 50 additions & 0 deletions tools/google-cloud-support-slackbot/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Copyright 2022 Google LLC

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

FROM debian:10
boredabdel marked this conversation as resolved.
Show resolved Hide resolved

LABEL author="Damian Lance"
LABEL created="2022-01-19"
LABEL last_updated="2022-01-19"

# Essential environment variables
ENV ORG_ID="YOUR NUMERIC ORG ID"
ENV SLACK_TOKEN="YOUR SLACK TOKEN"
ENV SIGNING_SECRET="YOUR SLACK SIGNING SECRET"
ENV API_KEY="YOUR API KEY FOR GOOGLE CLOUD SUPPORT AND CLOUD FIRESTORE"

# Testing environment variables
ENV TEST_CHANNEL_ID="SLACK CHANNEL ID FOR TESTING"
ENV TEST_CHANNEL_NAME="SLACK CHANNEL NAME FOR TESTING"
ENV TEST_USER_ID="SLACK USER_ID FOR TESTING"
ENV TEST_USER_NAME="SLACK USER_NAME FOR TESTING"
ENV PROJECT_ID="GOOGLE CLOUD PROJECT ID FOR SUPPORT CASES"
ENV PROJECT_NUMBER="GOOGLE CLOUD PROJECT NUMBER FOR SUPPORT CASES"

WORKDIR /google-cloud-support-slackbot

COPY . ./

RUN apt-get update; \
apt-get -y install \
python3-pip \
curl \
libffi-dev \
libssl-dev; \
pip3 install -r requirements.txt;

EXPOSE 80

ENTRYPOINT /google-cloud-support-slackbot/main.py

247 changes: 124 additions & 123 deletions tools/google-cloud-support-slackbot/README.md

Large diffs are not rendered by default.

110 changes: 110 additions & 0 deletions tools/google-cloud-support-slackbot/SupportCase.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
#!/usr/bin/env python3
boredabdel marked this conversation as resolved.
Show resolved Hide resolved

# Copyright 2021 Google LLC

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import os
import re
import logging
import time
import requests
from datetime import datetime
from googleapiclient.discovery import build_from_document

logger = logging.getLogger(__name__)


class SupportCase:
"""
Represent a Google Cloud Support Case.

Attributes
----------
case_number : str
a unique string of numbers that is the id for the case
resource_name : str
a unique string including the org or project id and the case id, examples:
organizations/12345/cases/67890
projects/12345/cases/67890
case_title : str
the title the user gave the case when they created it
description : str
the user's description of the case as provided in the support ticket
escalated : bool
whether or not a case has been escalated. This field doesn't exist in
the response until after a case has been escalated. True means the case
is escalated
case_creator : str
name of the user that opened the support case
create_time : str
timestamp of when the case was created
update_time : str
timestamp of the last update made to the case
priority : str
the current priority of the case, represented as S0, S1, S2, S3, or S4
state : str
the status of the support ticket. Can be NEW, IN_PROGRESS_GOOGLE_SUPPORT,
ACTION_REQUIRED, SOLUTION_PROVIDED, or CLOSED
comment_list : list
all public comments made on the case as strings. Comments are sorted
with newest comments at the top
"""

def __init__(self, caseobj):
"""
Parameters
----------
caseobj : json
json for an individual case
"""
MAX_RETRIES = 3
API_KEY = os.environ.get('API_KEY')

# Get our discovery doc and build our service
r = requests.get('https://cloudsupport.googleapis.com/$discovery'
'/rest?key={}&labels=V2_TRUSTED_TESTER&version=v2beta'
.format(API_KEY))
r.raise_for_status()
support_service = build_from_document(r.json())

self.case_number = re.search('(?:cases/)([0-9]+)', caseobj['name'])[1]
self.resource_name = caseobj['name']
self.case_title = caseobj['displayName']
self.description = caseobj['description']
if 'escalated' in caseobj:
self.escalated = caseobj['escalated']
else:
self.escalated = False
self.case_creator = caseobj['creator']['displayName']
self.create_time = str(datetime.fromisoformat(
caseobj['createTime'].replace('Z', '+00:00')))
self.update_time = str(datetime.fromisoformat(
caseobj['updateTime'].replace('Z', '+00:00')))
self.priority = caseobj['severity'].replace('S', 'P')
self.state = caseobj['state']
self.comment_list = []
case_comments = support_service.cases().comments()
request = case_comments.list(parent=self.resource_name)
while request is not None:
try:
comments = request.execute(num_retries=MAX_RETRIES)
except BrokenPipeError as e:
error_message = str(e) + ' : {}'.format(datetime.now())
logger.error(error_message)
time.sleep(1)
else:
if "comments" in comments:
for comment in comments['comments']:
self.comment_list.append(comment)
request = case_comments.list_next(request, comments)
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2021 Google LLC
# Copyright 2022 Google LLC

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env python3

# Copyright 2021 Google LLC
# Copyright 2022 Google LLC

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
aiohttp==3.7.4.post0
asn1crypto==0.24.0
async-timeout==3.0.1
attrs==21.2.0
cachetools==4.2.2
certifi==2021.5.30
click==7.1.2
crcmod==1.7
cryptography==2.6.1
distro-info==0.21
entrypoints==0.3
Flask==1.1.4
gevent==21.8.0
google-api-core==1.31.0
google-api-python-client==2.12.0
google-auth==1.32.1
google-auth-httplib2==0.1.0
googleapis-common-protos==1.53.0
greenlet==1.1.1
httplib2==0.19.1
idna==2.10
importlib-metadata==4.5.0
itsdangerous==1.1.0
Jinja2==2.11.3
keyring==17.1.1
keyrings.alt==3.1.1
MarkupSafe==1.1.1
multidict==5.1.0
packaging==21.0
protobuf==3.17.3
pyasn1==0.4.8
pyasn1-modules==0.2.8
pycrypto==2.6.1
pyee==7.0.4
PyGObject==3.30.4
pyparsing==2.4.7
python-apt==1.8.4.3
python-dotenv==0.18.0
pytz==2021.1
pyxdg==0.25
requests==2.25.1
rsa==4.7.2
SecretStorage==2.3.1
six==1.16.0
slackclient==2.9.3
slackeventsapi==2.2.1
typing-extensions==3.10.0.0
unattended-upgrades==0.1
uritemplate==3.0.1
urllib3==1.26.6
Werkzeug==1.0.1
yarl==1.6.3
zipp==3.4.1
zope.event==4.5.0
zope.interface==5.4.0
64 changes: 64 additions & 0 deletions tools/google-cloud-support-slackbot/case_details.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#!/usr/bin/env python3

# Copyright 2022 Google LLC

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import os
import slack
import json
import logging
from case_not_found import case_not_found
from get_firestore_cases import get_firestore_cases

logger = logging.getLogger(__name__)


def case_details(channel_id, case, user_id):
"""
Sends the data of a single case as json to the channel where the request originated.

Parameters
----------
channel_id : str
unique string used to idenify a Slack channel. Used to send messages to the channel
case : str
unique id of the case
user_id : str
the Slack user_id of the user who submitted the request. Used to send ephemeral
messages to the user
"""
client = slack.WebClient(token=os.environ.get('SLACK_TOKEN'))
cases = get_firestore_cases()
break_flag = False

for fs_case in cases:
if case == fs_case['case_number']:
pretty_json = json.dumps(fs_case, indent=4, sort_keys=True)
client.chat_postMessage(
channel=channel_id,
text=f"Here are the details on case {case}: \n{pretty_json}")
break_flag = True
break

if break_flag is False:
case_not_found(channel_id, user_id, case)


if __name__ == "__main__":
channel_id = os.environ.get('TEST_CHANNEL_ID')
case = 'xxxxxxxx'
user_id = os.environ.get('TEST_USER_ID')
case_details(channel_id, case, user_id)
case = os.environ.get('TEST_CASE')
case_details(channel_id, case, user_id)
57 changes: 57 additions & 0 deletions tools/google-cloud-support-slackbot/case_not_found.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#!/usr/bin/env python3

# Copyright 2022 Google LLC

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import os
import slack
import logging
from datetime import datetime

logger = logging.getLogger(__name__)


def case_not_found(channel_id, user_id, case):
"""
Informs the user of their case could not be found.

Parameters
----------
channel_id : str
unique string used to idenify a Slack channel. Used to send messages to the channel
user_id : str
the Slack user_id of the user who submitted the request. Used to send ephemeral
messages to the user
case : str
unique id of the case
"""
client = slack.WebClient(token=os.environ.get('SLACK_TOKEN'))
try:
client.chat_postEphemeral(
channel=channel_id,
user=user_id,
text=f"Case {case} could not be found in your org. If this case was recently"
" created, please give the system 60 seconds to fetch it. Otherwise,"
" double check your case number or confirm the org being tracked"
" with your Slack admin.")
except slack.errors.SlackApiError as e:
error_message = str(e) + ' : {}'.format(datetime.now())
logger.error(error_message)


if __name__ == "__main__":
channel_id = os.environ.get('TEST_CHANNEL_ID')
user_id = os.environ.get('TEST_USER_ID')
case = "xxxxxxxx"
case_not_found(channel_id, user_id, case)
Loading