0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Cloud DevOps Cookbook Part 4 - REST APIを使ってPythonでDevOpsのDirectMailを探る

Posted at

今回は、PythonREST APIを利用したアリババクラウドDevOpsDirectMailについて探っていきます。

本ブログは英語版からの翻訳です。オリジナルはこちらからご確認いただけます。一部機械翻訳を使用しております。翻訳の間違いがありましたら、ご指摘いただけると幸いです。

Alibaba Cloud Tech Share、著者 John Hanley。 Tech Shareは、技術的な知識やベストプラクティスをクラウドコミュニティ内で共有することを奨励するAlibaba Cloudのインセンティブプログラムです。

ラピッドプロトタイピングや新しいクラウドサービスを学ぶための私のお気に入りの言語はPythonです。Pythonを使うと、ほとんどのタスクがとてもシンプルになるので、新しいAPIを数分でテストすることができます。これにより、特定の言語でAPIがどのように動作するかなどの細かなことを気にすることなく、学習に集中することができます。しかし、ミスをすると何百万ドルもかかってしまうようなエンタープライズレベルのコードには、C++を使っています。

Alibaba の DirectMailはPythonをサポートしていません。PHP、Java、C#はサポートしています。DirectMailの非常に一般的なユースケースは、動的なウェブサイトと静的なウェブサイトにメールを配信することです。動的なウェブサイトでは、PHPが最適です。静的なウェブサイトでは、DirectMailFunction Computeを統合することは完璧です。しかし、Function ComputeはPHPをサポートしていません。これは、デスクトップでは1つの言語で開発し、クラウドでは別の言語で開発することを意味します。

そこで、DirectMailのREST APIを使うというアイデアが出てきました。それは、Pythonを使用して、REST APIを使用する方法と、特にあまりドキュメント化されていない署名を作成する方法を学ぶことができることを意味しています。

DirectMailは3種類のインターフェースをサポートしています(全ての言語に対応しているわけではありません)。

1、Alibaba REST API
2、Alibaba Cloud SDK
3、Alibaba SMTP Interface
前回の記事では、PythonでSDK(Function Compute)を利用した例を紹介しました。今回はDirectMail REST APIに焦点を当て、Pythonでの実際の動作例を紹介します。

アリババクラウドDirectMail REST API

REST APIを使う理由はいくつかあります。

1、低レベルのAPI、パラメータ、エラーを理解している。
2、コードスペースが小さい。
3、ロードと実行時間の短縮。
4、依存関係が少ない。
5、ターゲットシステムにSDKライブラリをインストールする必要がない。
6、SDKはターゲット言語では利用できません。

REST APIの必要条件
1、HTTPS、HTTPヘッダ、HTTPボディを理解していること。
2、HTTP HEAD、DELETE、GET、POST、PUTメソッドの違いを理解していること。
3、各HTTPメソッドのデータ送信方法を理解していること。
4、各 HTTP メソッドに含まれている必要がある API パラメータを理解していること。
5、HTTP リクエストの署名方法を理解していること。

アリババクラウドの公開パラメータ

DirectMail Publicのパラメータに関するアリババのドキュメントへのリンクです。

名前 タイプ 必須か 説明
Format String No 応答値のタイプ。JSONとXMLがサポートされています。XMLがデフォルトのフォーマットです。この例ではJSONを使用します。
Version String Yes APIのバージョン番号。形式はYYYY-MM-DDです。RegionIDがcn-hangzhouの場合、バージョン番号は2015-11-23。RegionIDがcn-hangzhouでない場合のバージョン番号は、ap-southeast-1などの場合は2017-06-22です。例では2017-06-22を使用します。
AccessKeyId String Yes Alibaba Cloudがサービスにアクセスするためにユーザーに発行するAccessKeyId。
SecurityToken String Depends ユーザーに対して定義されたアクセス・キーを使用している場合は、このパラメータは必要ありません。ロールを使用している場合は、context.credentialsオブジェクトの一部として関数に渡されるセキュリティ・トークンです。
Signature String Yes 署名結果の文字列です。署名の計算方法については「Signature」を参照してください。
SignatureMethod String Yes 署名方法。HMAC-SHA1 は現在サポートされています。
Timestamp String Yes リクエストのタイムスタンプ。日付のフォーマットはISO8601標準に準拠し、UTC時間を採用します。形式は以下の通りです。YYYY-MM-DDThh:mm:ssZ。例:2015-11-23T04:00:00Z(北京時間2015年11月23日12:00:00の場合)。
SignatureVersion String Yes 署名アルゴリズムのバージョン。現在のバージョンは1.0です。
SignatureNonce String Yes 固有の乱数。リプレイ攻撃を防ぐために使用します。リクエストごとに異なる乱数を使用する必要があります。
RegionId String Yes データセンター情報。cn-hangzhou、ap-southeast-1、ap-southeast-2は現在対応しています。

上記のパラメータについてのコメント:

SignatureNonce: このパラメータにはしばらくの間、悩まされました。最初はこれが HMAC_SHA1 署名の際に使用されるソルト値だと思っていました。しかし、これはuuid.uuid4()によって生成され、ヘッダに含まれる文字列であることがわかりました。後続のコマンドでこの文字列の値を繰り返すと、そのコマンドは拒否されます。

AccessKeyId:DirectMail専用の権限を持つ新しいRAMユーザを作成します。この場合、REST API 用の Access Key と Access Key Secret の両方が必要になります。

Timestamp:システムの日付と時刻が正しいことが重要です。この時刻がアリババのサービスと異なる場合、リクエストは拒否されます。可能であれば、システムにNTPなどのタイムサービスを使用してください。

Alibaba Cloud DirectMailリクエストパラメータ

DirectMail Requestのパラメータに関するアリババのドキュメントへのリンクです。

名前 タイプ 必須か 説明
Action String Required オペレーションインターフェース名、システム必須パラメータです。値を指定します。SingleSendMail。
AccountName String Required コンソールで構成されている送信者アドレスです。
ReplyToAddress Boolean Required コンソールで構成されている返信先アドレス(ステータスが「検証済み」である必要があります)。
AddressType Number Required 値の範囲:0-1。 0 はランダムなアカウントを示し、1 は送信者アドレスを示します。
ToAddress String Required 受信者のアドレス。複数のアドレスはカンマで区切ることができ、最大 100 個のアドレスをサポートします。
FromAlias String オプション 送信者のニックネーム。ニックネームの長さは 15 文字以下にする必要があります。たとえば、送信者のニックネームは Daisy に設定され、送信者のアドレスは test@example.com です。受信者は、Daisy test@example.com のアドレスを参照します。
Subject String Optional Subject (推奨)。
HtmlBody String Optional HTMLでメール本文を表示します。
TextBody String Optional メール本文をテキストで表示します。
ClickTrace String Optional 値の範囲。0-1。 1 は受信者追跡を有効にすることを示します。0 は受信者追跡を有効にしないことを示します。このパラメータの既定値は 0 です。

正しい日付の計算

# Correctly formatted date and time
now = datetime.datetime.utcnow()

# Date used by the HTTP "Date" header
date = now.strftime("%a, %d %b %Y %H:%M:%S GMT")

# Date used by the API parameter "Timestamp"
utc = now.isoformat(timespec='seconds') + 'Z'

この例のホストとエンドポイント (Singapore --> ap-southeast-1)

# HTTP Host header
host = "dm.ap-southeast-1.aliyuncs.com"

# URL for POST
url = "https://dm.ap-southeast-1.aliyuncs.com/"

パブリックパラメータのパラメータリストの構築

parameters = {}

# Add the DirectMail public request parameters
parameters["Format"] = "json"
parameters["AccessKeyId"] = credentials['AccessKey']
parameters["SignatureMethod"] = "HMAC-SHA1"
parameters["SignatureType"] = ""
parameters["SignatureVersion"] = "1.0"
parameters["SignatureNonce"] = get_uuid()
parameters["Timestamp"] = utc
parameters["Version"] = "2017-06-22"
parameters["RegionId"] = "ap-southeast-1"

リクエストパラメータのパラメータリストの構築

# Add parameters that are always set
parameters["Action"] = "SingleSendMail"
parameters["AddressType"] = "1"
parameters["ReplyToAddress"] = "true"

# Add the DirectMail API parameters
parameters["AccountName"] = dm_account
parameters["FromAlias"] = dm_alias
parameters["ToAddress"] = to_list
parameters["Subject"] = subject
parameters["HtmlBody"] = body
parameters["textBody"] = body_text

署名処理のためのリクエスト文字列を構築

def build_request_string(table):
    """ Build canonical list """
    items = sorted(iter(table.items()), key=lambda d: d[0])
    enc = my_urlencode(items)
    return enc

リクエスト文字列に関する注意事項

リクエスト文字列のパラメータは最初にソートされなければなりません。それから文字列は url エンコードされます。これは、各キー/値のペアに & 文字が付加されることを意味します。

最終的な結果は以下の例のようになりますが、もっと長くなります。

AccessKeyId=LTAIQlgy6erobert&AccountName=test%40test.com&Action=SingleSendMail …

リクエスト文字列から署名を作成

DirectMail Signatureに関するアリババのドキュメントへのリンクです。

# Build the request string for the signing process
params = build_request_string(parameters)

# Create the actual string to sign (method = "POST")
stringToSign = method + "&%2F&" + percentEncode(params)

Signature = sign(stringToSign, credentials['AccessKeySecret'])

DirectMail REST APIを呼び出してメールを送信するプログラム

sendEmail.zipをダウンロードします。

############################################################
# Version 1.00
# Date Created: 2018-05-26
# Last Update:  2018-05-27
# https://www.neoprime.io
# Copyright (c) 2018, NeoPrime, LLC
############################################################

""" Alibaba Cloud DirectMail REST API With Signing """

import base64
import datetime
import hmac
import hashlib
import urllib
import uuid
import json
import requests

# My library for processing Alibaba Cloud Services (ACS) credentials
import mycred_acs

# From the DirectMail Console
dm_account = "<enter your value here>"
dm_alias = "<enter your value here>"

debug = 0

def set_connection_logging():
    """ Enable HTTP connection logging """
    if debug is 0:
        return

    import logging
    import http.client as http_client

    http_client.HTTPConnection.debuglevel = 1

    # You must initialize logging, otherwise you'll not see debug output.
    logging.basicConfig()
    logging.getLogger().setLevel(logging.DEBUG)
    requests_log = logging.getLogger("requests.packages.urllib3")
    requests_log.setLevel(logging.DEBUG)
    requests_log.propagate = True
    return

def get_uuid():
    """ return a uuid as a signing nonce """
    return str(uuid.uuid4())

def percentEncode(path):
    """ Encode a URL """
    res = urllib.parse.quote(path)
    res = res.replace('+', '%20')
    res = res.replace('*', '%2A')
    res = res.replace('%7E', '~')
    return res

def my_urlencode(query):
    """ Encode a Query """
    res = urllib.parse.urlencode(query)
    res = res.replace('+', '%20')
    res = res.replace('*', '%2A')
    res = res.replace('%7E', '~')
    return res

def build_request_string(table):
    """ Build canonical list """
    items = sorted(iter(table.items()), key=lambda d: d[0])
    enc = my_urlencode(items)
    return enc

def sign(string, secret):
    """ Sign REST API Request """
    nsecret = secret + '&'

    h = hmac.new(
        bytes(nsecret, "utf-8"),
        bytes(string, "utf-8"),
        hashlib.sha1)

    #sig = base64.b64encode(h.digest())
    sig = str(base64.encodebytes(h.digest()).strip(), "utf-8")
    return sig

def sendEmail(credentials, subject, body, body_text, to_list):
    """ Send an email using Alibaba DirectMail """
    # HTTP Method
    method = "POST"

    # Correctly formatted date and time
    now = datetime.datetime.utcnow()
    date = now.strftime("%a, %d %b %Y %H:%M:%S GMT")
    utc = now.isoformat(timespec='seconds') + 'Z'

    # HTTP Host header
    host = "dm.ap-southeast-1.aliyuncs.com"

    # URL for POST
    url = "https://dm.ap-southeast-1.aliyuncs.com/"

    parameters = {}

    # Add the DirectMail public request parameters
    parameters["Format"] = "json"
    parameters["AccessKeyId"] = credentials['AccessKey']
    parameters["SignatureMethod"] = "HMAC-SHA1"
    parameters["SignatureType"] = ""
    parameters["SignatureVersion"] = "1.0"
    parameters["SignatureNonce"] = get_uuid()
    parameters["Timestamp"] = utc
    #parameters["Version"] = "2015-11-23"
    parameters["Version"] = "2017-06-22"
    parameters["RegionId"] = "ap-southeast-1"

    # Add parameters that are always set
    parameters["Action"] = "SingleSendMail"
    parameters["AddressType"] = "1"
    parameters["ReplyToAddress"] = "true"

    # Add the DirectMail API parameters
    parameters["AccountName"] = dm_account
    parameters["FromAlias"] = dm_alias
    parameters["ToAddress"] = to_list
    parameters["Subject"] = subject
    parameters["HtmlBody"] = body
    parameters["textBody"] = body_text

    # Build the request string for the signing process
    params = build_request_string(parameters)

    # Create the actual string to sign
    stringToSign = method + "&%2F&" + percentEncode(params)

    #print("String to Sign")
    #print(stringToSign)

    Signature = sign(stringToSign, credentials['AccessKeySecret'])
    #print("Signature", Signature)

    parameters["Signature"] = Signature

    headers = {
        'Date': date,
        'Host': host
    }

    set_connection_logging()

    print("Sending Email to", parameters["ToAddress"])
    r = requests.post(url, data=parameters, headers=headers)

    if r.status_code != 200:
        print("Error: Email Send Failed:", r.status_code)
        print(r.text)
        return 1

    #print(r.text)

    result = json.loads(r.text)
    print("Success: Request ID:", result['RequestId'])

    return 0

# Load the Alibaba Cloud Credentials (AccessKey)
cred = mycred_acs.LoadCredentials()

dm_subject = "Welcome to Alibaba Cloud DirectMail"

dm_body = "<h2>Welcome to Alibaba Cloud DirectMail<h2>You are receiving this email as part of a test program.<br /><br />Click for <a href='https://www.neoprime.io/info/alibaba/'>more information<a>.<br /><br /><a href='https://www.alibabacloud.com/'><img src='https://www.neoprime.io/info/alibaba/img/alibaba-600x263.png' alt='Alibaba' width='700'><a>"

dm_body_text = "Welcome to Alibaba Cloud DirectMail\nYou are receiving this email as part of a test program."

dm_to_list = "test@test.com, test2@test.com

sendEmail(cred, dm_subject, dm_body, dm_body_text, dm_to_list)

Python 3.xで実行する: python sendEmail.py

アリババ文書

Alibaba DirectMail Product Page
Alibaba DirectMail Documentation
Alibaba DirectMail Public parameters
Alibaba DirectMail Request parameters
Alibaba DirectMail Signature

アリババクラウドは日本に2つのデータセンターを有し、世界で60を超えるアベラビリティーゾーンを有するアジア太平洋地域No.1(2019ガートナー)のクラウドインフラ事業者です。
アリババクラウドの詳細は、こちらからご覧ください。
アリババクラウドジャパン公式ページ

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?