diff --git a/src/communication/HISTORY.rst b/src/communication/HISTORY.rst index 576d80dae2a..629cffddaa6 100644 --- a/src/communication/HISTORY.rst +++ b/src/communication/HISTORY.rst @@ -2,6 +2,11 @@ Release History =============== +1.10.0 +++++++ +* Adding new parameter waituntil to Email communication send mail. +* Adding new command get send status to Email communication. + 1.9.3 ++++++ * Update Email service create - Global as the default value for Location diff --git a/src/communication/azext_communication/manual/_help.py b/src/communication/azext_communication/manual/_help.py index 414bdec918e..308a8396643 100644 --- a/src/communication/azext_communication/manual/_help.py +++ b/src/communication/azext_communication/manual/_help.py @@ -575,3 +575,17 @@ text: |- az communication email send --sender "NoReply@contoso.com" --subject "Contoso Update" --to "user1@user1-domain.com" "user2@user2-domain.com" --text "Hello valued client. There is an update." """ + +helps['communication email status'] = """ + type: group + short-summary: Commands to get the status of emails previously sent using Azure Communication Services Email service. +""" + +helps['communication email status get'] = """ + type: command + short-summary: "Get status of an email previously sent." + examples: + - name: Get status of an email + text: |- + az communication email status get --operation-id "01234567-89ab-cdef-0123-012345678901" --connection-string "endpoint=XXXXXXXXXXXXXXXX;accesskey=XXXXXXXXXXXXXXXXXXXXXX" +""" diff --git a/src/communication/azext_communication/manual/_params.py b/src/communication/azext_communication/manual/_params.py index db24eefc282..cbd4970529e 100644 --- a/src/communication/azext_communication/manual/_params.py +++ b/src/communication/azext_communication/manual/_params.py @@ -284,3 +284,13 @@ def _load_email_arguments(self): ' Required for each attachment. Known values are: avi, bmp, doc, docm,' ' docx, gif, jpeg, mp3, one, pdf, png, ppsm, ppsx, ppt, pptm, pptx,' ' pub, rpmsg, rtf, tif, txt, vsd, wav, wma, xls, xlsb, xlsm, and xlsx') + c.argument('waitUntil', options_list=['--wait-until'], + arg_type=get_enum_type(['started', 'completed', '1', '0']), + help='Indicates whether to wait until the server operation is started or completed. ' + 'Accepted values are: started, completed, 1, 0.') + + with self.argument_context('communication email status get') as c: + c.argument('operation_id', options_list=['--operation-id'], type=str, + help='System generated message id (GUID) returned from a previous call to send email') + c.argument('connection_string', options_list=['--connection-string'], type=str, + help='Connection string for Azure Communication Service. Must be provided.') diff --git a/src/communication/azext_communication/manual/commands.py b/src/communication/azext_communication/manual/commands.py index 5b1578bb654..63dbdac1282 100644 --- a/src/communication/azext_communication/manual/commands.py +++ b/src/communication/azext_communication/manual/commands.py @@ -124,3 +124,5 @@ def _load_email_command_table(self): with self.command_group('communication email', client_factory=cf_communication_email) as g: g.communication_custom_command('send', 'communication_email_send', email_arguments) + with self.command_group('communication email', is_preview=True) as g: + g.communication_custom_command('status get', 'communication_email_get_status', email_arguments) diff --git a/src/communication/azext_communication/manual/custom.py b/src/communication/azext_communication/manual/custom.py index 6c5d919e445..bc31124bfe2 100644 --- a/src/communication/azext_communication/manual/custom.py +++ b/src/communication/azext_communication/manual/custom.py @@ -9,6 +9,12 @@ import sys import ast from azure.core.exceptions import HttpResponseError +import base64 +import hashlib +import hmac +import requests +from datetime import datetime +from urllib.parse import urlparse def communication_identity_create_user(client): @@ -315,7 +321,7 @@ def communication_rooms_remove_participants(client, room_id, participants): def __get_attachment_content(filename, filetype): - import base64 + import json import os @@ -334,6 +340,25 @@ def __get_attachment_content(filename, filetype): return json.dumps(attachment) +def prepare_attachments(attachments, attachment_types): + from knack.util import CLIError + + attachments_list = [] + if attachments is None and attachment_types is None: + attachments_list = None + elif attachments is None or attachment_types is None: + raise CLIError('Number of attachments and attachment-types should match.') + elif len(attachments) != len(attachment_types): + raise CLIError('Number of attachments and attachment-types should match.') + else: + all_attachments = attachments[0].split(',') + all_attachment_types = attachment_types[0].split(',') + for i, attachment in enumerate(all_attachments): + attachments_list.append(__get_attachment_content(attachment, all_attachment_types[i])) + + return attachments_list + + def communication_email_send(client, subject, sender, @@ -346,10 +371,12 @@ def communication_email_send(client, recipients_bcc=None, reply_to=None, attachments=None, - attachment_types=None): + attachment_types=None, + waitUntil='completed'): import json from knack.util import CLIError + import uuid try: @@ -363,18 +390,7 @@ def communication_email_send(client, else: priority = '3' - attachments_list = [] - if attachments is None and attachment_types is None: - attachments_list = None - elif attachments is None or attachment_types is None: - raise CLIError('Number of attachments and attachment-types should match.') - elif len(attachments) != len(attachment_types): - raise CLIError('Number of attachments and attachment-types should match.') - else: - all_attachments = attachments[0].split(',') - all_attachment_types = attachment_types[0].split(',') - for i, attachment in enumerate(all_attachments): - attachments_list.append(__get_attachment_content(attachment, all_attachment_types[i])) + attachments_list = prepare_attachments(attachments, attachment_types) message = { "content": { @@ -400,7 +416,84 @@ def communication_email_send(client, } } - return client.begin_send(message) + operationId = str(uuid.uuid4()) + + poller = client.begin_send(message, operation_id=operationId) + + if waitUntil == 'started' or waitUntil == '1': + print("Email send started") + print(f"Operation id : {operationId}, status : {poller.status()} ") + elif waitUntil == 'completed' or waitUntil == '0': + # Wait until the email is sent and get the result + return poller + else: + raise ValueError("Invalid value for waitUntil. Expected 'started' or 'completed'.") + + except HttpResponseError: + raise + except Exception as ex: + sys.exit(str(ex)) + + +def parse_connection_string(connection_string): + """ + Parse the connection string to extract the endpoint and API key. + """ + params = {} + for item in connection_string.split(';'): + key, value = item.split('=', 1) + params[key.strip()] = value.strip() + + api_endpoint = params.get('endpoint') + api_key = params.get('accesskey') + + if not api_endpoint or not api_key: + raise ValueError("Connection string is missing required parameters.") + + return api_endpoint, api_key + + +def create_signature_header(method, url, host, api_key): + + date_str = datetime.utcnow().strftime('%a, %d %b %Y %H:%M:%S GMT') + + hashed_body = hashlib.sha256(b'').digest() + hashed_body_base64 = base64.b64encode(hashed_body).decode('utf-8') + + string_to_sign = f"{method}\n{url}\n{date_str};{host};{hashed_body_base64}" + signing_key = base64.b64decode(api_key) + signature = hmac.new(signing_key, string_to_sign.encode('utf-8'), hashlib.sha256).digest() + signature_base64 = base64.b64encode(signature).decode('utf-8') + + headers = { + 'x-ms-date': date_str, + 'x-ms-content-sha256': hashed_body_base64, + 'Authorization': f"HMAC-SHA256 SignedHeaders=x-ms-date;host;x-ms-content-sha256&Signature={signature_base64}" + } + + return headers + + +def communication_email_get_status(connection_string, operation_id): + try: + api_endpoint, api_key = parse_connection_string(connection_string) + + status_endpoint = f"{api_endpoint}emails/operations/{operation_id}?api-version=2023-03-31" + + method = "GET" + url = f'/emails/operations/{operation_id}?api-version=2023-03-31' + parsed_url = urlparse(api_endpoint) + host = parsed_url.netloc + + headers = create_signature_header(method, url, host, api_key) + + response = requests.get(status_endpoint, headers=headers) + + if response.status_code == 200: + return response.json() + + response.raise_for_status() + except HttpResponseError: raise except Exception as ex: diff --git a/src/communication/azext_communication/version.py b/src/communication/azext_communication/version.py index 1b098418f28..7c3db6e4a43 100644 --- a/src/communication/azext_communication/version.py +++ b/src/communication/azext_communication/version.py @@ -4,7 +4,7 @@ # -------------------------------------------------------------------------------------------- -VERSION = '1.9.3' +VERSION = '1.10.0' def cli_application_id():