Skip to content

Commit

Permalink
Pull request update/241216
Browse files Browse the repository at this point in the history
0027a14 OS-2371. Fixed 500 on constraint_alert msg if user added to several spaces
37346f4 OS-1714. Changed error text for slacker
fb691e0 OS-3856. Fixed incorrect constraints in resource_details msg
624b375 OS-2587. Show env_properties in resource_details slack message
680a2aa OS-1663. Use short cloud_resource_id in slack message
96e35d2 OS-2372. Not send slack message to channels the bot is removed from
  • Loading branch information
stanfra authored Dec 16, 2024
2 parents 3ca3bda + 0027a14 commit a268330
Show file tree
Hide file tree
Showing 8 changed files with 162 additions and 77 deletions.
29 changes: 20 additions & 9 deletions slacker/slacker_server/controllers/send_message.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
from slacker.slacker_server.message_templates.env_alerts import (
get_property_updated_message, get_message_changed_active_state,
get_message_acquired, get_message_released)
from slacker.slacker_server.message_templates.warnings import get_archived_message_block
from slacker.slacker_server.message_templates.warnings import (
get_archived_message_block)


LOG = logging.getLogger(__name__)
Expand All @@ -39,16 +40,24 @@ def send_message(self, **kwargs):
parameters = kwargs.get('parameters', {})
warning = parameters.pop('warning', None)
warning_params = parameters.pop('warning_params', None)
teams_channels = set()
if auth_user_id:
user = self.session.query(User).filter(
users = self.session.query(User).filter(
User.auth_user_id == auth_user_id,
User.deleted.is_(False),
).one_or_none()
if not user:
).all()
if not users:
raise NotFoundException(Err.OS0016, ['auth_user_id',
auth_user_id])
team_id = user.slack_team_id
channel_id = user.slack_channel_id
for user in users:
teams_channels.add((user.slack_channel_id, user.slack_team_id))
if team_id or channel_id:
teams_channels.add((channel_id, team_id))
if channel_id and channel_id.startswith('C'):
# public or private channel, not direct message
channels = self.app.client.get_bot_conversations(team_id=team_id)
if channel_id not in [x['id'] for x in channels]:
raise NotFoundException(Err.OS0020, [channel_id])

template_func = self.MESSAGE_TEMPLATES.get(type_)
if template_func is None:
Expand All @@ -64,9 +73,11 @@ def send_message(self, **kwargs):
**warning_params) + message['blocks']

try:
self.app.client.chat_post(
channel_id=channel_id, team_id=team_id,
**message)
for data in teams_channels:
channel_id, team_id = data
self.app.client.chat_post(
channel_id=channel_id, team_id=team_id,
**message)
except TypeError as exc:
LOG.error('Failed to send message: %s', exc)
raise WrongArgumentsException(Err.OS0011, ['parameters'])
Expand Down
71 changes: 33 additions & 38 deletions slacker/slacker_server/controllers/slack.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from datetime import datetime, timedelta

from requests import HTTPError
from retrying import Retrying
from slack_sdk.errors import SlackApiError
from sqlalchemy.exc import IntegrityError

Expand All @@ -16,53 +15,32 @@
get_add_constraint_envs_alert_modal)
from slacker.slacker_server.message_templates.bookings import (
get_add_bookings_form, get_booking_details_message)
from slacker.slacker_server.message_templates.connect import get_welcome_message
from slacker.slacker_server.message_templates.connect import (
get_welcome_message)
from slacker.slacker_server.message_templates.constraints import (
get_update_ttl_form, get_constraint_updated)
from slacker.slacker_server.message_templates.disconnect import (
get_disconnect_confirmation_message, get_disconnected_message)
from slacker.slacker_server.message_templates.envs import get_envs_message
from slacker.slacker_server.message_templates.org import (
get_org_switch_message, get_org_switch_completed_message)
from slacker.slacker_server.message_templates.resources import get_resources_message
from slacker.slacker_server.message_templates.resources import (
get_resources_message)
from slacker.slacker_server.message_templates.resource_details import (
get_resource_details_message)
from slacker.slacker_server.message_templates.errors import (
get_ca_not_connected_message, get_not_have_slack_permissions_message)
from slacker.slacker_server.models.models import User
from slacker.slacker_server.utils import gen_id
from slacker.slacker_server.utils import gen_id, retry_too_many_requests
from tools.optscale_time import utcfromtimestamp, utcnow_timestamp

LOG = logging.getLogger(__name__)
TTL_LIMIT_TO_SHOW = 72
EXPENSE_LIMIT_TO_SHOW = 0.9
MS_IN_SEC = 1000
SEC_IN_HRS = 3600
MAX_MSG_ENVS_LENGTH = 10


def retry_too_many_requests(f, *args, **kwargs):
try:
return f(*args, **kwargs)
except Exception as exc:
if retriable_slack_api_error(exc):
f_retry = Retrying(
retry_on_exception=retriable_slack_api_error,
wait_fixed=int(exc.response.headers['Retry-After']) * MS_IN_SEC,
stop_max_attempt_number=5)
res = f_retry.call(f, *args, **kwargs)
return res
else:
raise exc


def retriable_slack_api_error(exc):
if (isinstance(exc, SlackApiError) and
exc.response.headers.get('Retry-After')):
return True
return False


class MetaSlackController:
"""
Using it to keep common logic between handler controllers and slack event
Expand Down Expand Up @@ -326,7 +304,8 @@ def _check_expense_limit(expense_constr):
def resource_details(self, ack, say, action, body, logger):
slack_user_id = body['user']['id']
user = self.get_user(slack_user_id)
if user is None or user.auth_user_id is None or user.organization_id is None:
if (user is None or user.auth_user_id is None
or user.organization_id is None):
ack()
return
target_resource_id = action['value']
Expand All @@ -336,20 +315,35 @@ def resource_details(self, ack, say, action, body, logger):
_, resource = rest_cl.cloud_resource_get(
target_resource_id, details=True)
_, org = rest_cl.organization_get(user.organization_id)
_, response = rest_cl.resource_limit_hits_list(target_resource_id)
limit_hits = response.get('limit_hits', [])

tel_enabled = self.total_expense_limit_enabled(user.organization_id)
constraint_types = ['ttl', 'daily_expense_limit']
if tel_enabled:
constraint_types.append('total_expense_limit')

constraints = {}
for constraint in constraint_types:
if resource['details']['constraints'].get(constraint):
constraints[constraint] = resource['details']['constraints'][
constraint]
constraints[constraint]['constraint_type'] = 'resource specific'
elif resource['details']['policies'].get(constraint, {}).get('active'):
constraints[constraint] = resource['details']['policies'][
constraint]
constraints[constraint]['constraint_type'] = 'pool policy'
for constraint_type in constraint_types:
constraint = {}
last_hit = next((x for x in limit_hits
if x['type'] == constraint_type), None)
if constraint_type in resource['details']['constraints']:
constraint = resource['details']['constraints'][
constraint_type]
constraint['constraint_type'] = 'resource specific'
if (last_hit and not last_hit['pool_id']
and last_hit['state'] == 'red'):
constraint['last_hit'] = last_hit
elif resource['details']['policies'].get(constraint_type, {}).get(
'active'):
constraint = resource['details']['policies'][constraint_type]
constraint['constraint_type'] = 'pool policy'
if (last_hit and last_hit['pool_id'] == resource['pool_id']
and last_hit['state'] == 'red'):
constraint['last_hit'] = last_hit
constraints[constraint_type] = constraint
resource['constraints'] = constraints

current_booking = None
if resource['details'].get('shareable_bookings'):
Expand All @@ -367,7 +361,8 @@ def resource_details(self, ack, say, action, body, logger):
say(get_resource_details_message(
resource=resource, org_id=user.organization_id,
public_ip=self.config_cl.public_ip(), booking=current_booking,
currency=org['currency'], total_expense_limit_enabled=tel_enabled))
currency=org['currency'], total_expense_limit_enabled=tel_enabled
))

def create_update_ttl_view(self, ack, action, client, body, say, logger):
slack_user_id = body['user']['id']
Expand Down
10 changes: 8 additions & 2 deletions slacker/slacker_server/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,10 @@ class Err(enum.Enum):
["channel_id and auth_user_id could not be provided at the same time"]
]
OS0016 = [
"User with %s %s were not found",
"User with %s %s was not found",
["auth_user_id", "02430e6b-6975-4535-8bc6-7a7b52938014"],
["User with auth_user_id 02430e6b-6975-4535-8bc6-7a7b52938014 were not found"]
["User with auth_user_id 02430e6b-6975-4535-8bc6-7a7b52938014 was not "
"found"]
]
OS0017 = [
"%s should provide only with %s",
Expand All @@ -71,3 +72,8 @@ class Err(enum.Enum):
['channel_id'],
['Target slack channel FFFFFFFFF is archived']
]
OS0020 = [
"Slack app is not added to channel %s",
['channel_id'],
['Slack app is not added to channel C000000000']
]
12 changes: 9 additions & 3 deletions slacker/slacker_server/handlers/v2/send_message.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
from tools.optscale_exceptions.http_exc import OptHTTPError
from tools.optscale_exceptions.common_exc import NotFoundException

from slacker.slacker_server.controllers.send_message import SendMessageAsyncController
from slacker.slacker_server.controllers.send_message import (
SendMessageAsyncController
)
from slacker.slacker_server.exceptions import Err
from slacker.slacker_server.handlers.v2.base import BaseHandler

Expand Down Expand Up @@ -132,7 +135,7 @@ async def post(self, **kwargs):
- OS0012: Duplicated parameters in path and body
- OS0014: channel_id with team_id or auth_user_id should be provided
- OS0015: channel_id and auth_user_id could not be provided at the same time
- OS0016: User not found
- OS0016: User with auth_user_id was not found
- OS0017: channel_id should provide only with team_id
- OS0019: Target slack channel is archived
401:
Expand All @@ -150,7 +153,10 @@ async def post(self, **kwargs):
data = self._request_body()
data.update(kwargs)
await self.validate_params(**data)
await self.controller.send_message(**data)
try:
await self.controller.send_message(**data)
except NotFoundException as exc:
raise OptHTTPError.from_opt_exception(404, exc)
self.write_json({})
self.set_status(201)

Expand Down
65 changes: 43 additions & 22 deletions slacker/slacker_server/message_templates/resource_details.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,16 +54,18 @@ def get_resource_details_block(resource, org_id, public_ip):
def _get_expense_limit_msg(c_sign, total_cost, expense):
expense_msg = "Not set"
if expense:
if expense['limit'] < total_cost:
expense_msg = ":exclamation:*{0}{1}*".format(
c_sign, expense['limit'])
elif total_cost / expense['limit'] >= EXPENSE_LIMIT_TO_SHOW:
expense_msg = ":warning:{0}{1}".format(
c_sign, expense['limit'])
elif total_cost / expense['limit'] < EXPENSE_LIMIT_TO_SHOW:
expense_msg = "{0}{1}".format(c_sign, expense['limit'])
last_hit = expense.get('last_hit', {})
if last_hit and last_hit['state'] == 'red':
if expense['limit'] < total_cost:
expense_msg = ":exclamation:*{0}{1}*".format(
c_sign, expense['limit'])
elif total_cost / expense['limit'] >= EXPENSE_LIMIT_TO_SHOW:
expense_msg = ":warning:{0}{1}".format(
c_sign, expense['limit'])
elif expense['limit'] == 0:
expense_msg = ":warning:No limit"
else:
expense_msg = "{0}{1}".format(c_sign, expense['limit'])
return expense_msg


Expand All @@ -79,21 +81,13 @@ def get_resource_details_message(
total_cost = details.get('total_cost', 0)
month_cost = details.get('cost', 0)
tags = resource.get('tags', {})
env_properties = resource.get('env_properties')

constraint_types = ['ttl', 'daily_expense_limit']
if total_expense_limit_enabled:
constraint_types.append('total_expense_limit')
constraints = {}
for constraint in constraint_types:
if details['constraints'].get(constraint):
constraints[constraint] = details['constraints'][constraint]
constraints[constraint]['constraint_type'] = '_(resource specific)_'
elif details['policies'].get(constraint, {}).get('active'):
constraints[constraint] = details['policies'][constraint]
constraints[constraint]['constraint_type'] = '_(pool policy)_'
else:
constraints[constraint] = {}

constraints = resource.get('constraints', {})
ttl = constraints.get('ttl')
if ttl:
hrs = (ttl['limit'] - utcnow_timestamp()) / SEC_IN_HRS
Expand All @@ -113,11 +107,18 @@ def get_resource_details_message(
else:
ttl_msg = 'Not set'

ttl_constraint_type = constraints.get('ttl', {}).get('constraint_type', '')
if ttl_constraint_type:
ttl_constraint_type = f"_({ttl_constraint_type})_"

daily_expense = constraints.get('daily_expense_limit')
daily_expense_msg = _get_expense_limit_msg(c_sign, total_cost,
daily_expense)
daily_constaint_type = constraints['daily_expense_limit'].get(

daily_constaint_type = constraints.get('daily_expense_limit', {}).get(
'constraint_type', '')
if daily_constaint_type:
daily_constaint_type = f"_({daily_constaint_type})_"

header_blocks = [{
"type": "section",
Expand Down Expand Up @@ -188,7 +189,7 @@ def get_resource_details_message(
"text": {
"type": "mrkdwn",
"text": f"TTL\t\t\t\t\t\t\t{ttl_msg} "
f"{constraints['ttl'].get('constraint_type', '')}"
f"{ttl_constraint_type}"
},
"accessory": {
"type": "button",
Expand All @@ -215,8 +216,10 @@ def get_resource_details_message(
total_expense = constraints.get('total_expense_limit')
total_expense_msg = _get_expense_limit_msg(c_sign, total_cost,
total_expense)
total_constaint_type = constraints['total_expense_limit'].get(
total_constaint_type = constraints.get('total_expense_limit', {}).get(
'constraint_type', '')
if total_constaint_type:
total_constaint_type = f"_({total_constaint_type})_"
resource_blocks.append(
{
"type": "section",
Expand Down Expand Up @@ -276,6 +279,24 @@ def get_resource_details_message(
}
}
]
env_prop_block = []
if env_properties:
env_prop_block = [{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "\n*Environment properties:*"
}
}]
env_prop_block.extend(
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": f"{k}: {v}"
}
} for k, v in env_properties.items()
)

footer_blocks = [{
"type": "divider"
Expand All @@ -296,6 +317,6 @@ def get_resource_details_message(
return {
"text": "Here are the details of the resource you asked",
"blocks": (header_blocks + tags_blocks + resource_blocks +
booking_blocks + footer_blocks),
env_prop_block + booking_blocks + footer_blocks),
"unfurl_links": False
}
2 changes: 1 addition & 1 deletion slacker/slacker_server/message_templates/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
def get_resource_blocks(resource_data, public_ip, org_id, currency='USD'):
c_sign = CURRENCY_MAP.get(currency, '')
r_id = resource_data['resource_id']
r_cid = resource_data['cloud_resource_id']
r_cid = resource_data['cloud_resource_id'].split('/')[-1]
short_id = r_id[:4]
r_name = resource_data.get('resource_name', '')
r_ttl_constr = resource_data.get('ttl')
Expand Down
Loading

0 comments on commit a268330

Please sign in to comment.