PythonでDNSの正引きと逆引き
諸般の事情からスクリプトでホスト名とIPアドレスの対応を取りたいことがありますのでメモしておきます。
socket
socket --- 低水準ネットワークインターフェイス — Python 3.8.3 ドキュメント
標準ライブラリで実現できるシンプルな方法です。
import socket # 正引き socket.gethostbyname("www.google.com") #=> '172.217.31.164' # 逆引き socket.gethostbyaddr("172.217.31.164") #=> ('nrt12s22-in-f4.1e100.net', ['164.31.217.172.in-addr.arpa'], ['172.217.31.164'])
逆引きの結果はタプルになっていて、最初の値がホスト名です。
dnspython
高度な機能を使うにはこちらのようなライブラリを使うとよさそうです。本稿執筆時点のバージョンは1.16.0です。
$ pip install dnspython
socketと同じことをやってみます。
from dns import resolver, reversename # 正引き answers = resolver.query("www.google.com") #=> <dns.resolver.Answer object at 0x104829050> [answer.to_text() for answer in answers] #=> ['172.217.174.100'] # 逆引き answers = resolver.query(reversename.from_address("172.217.174.100"), "PTR") #=> <dns.resolver.Answer object at 0x104eebfd0> [answer.to_text() for answer in answers] #=> ['nrt12s28-in-f4.1e100.net.']
クエリの結果は複数の場合があるのでリストとして扱っています。to_text
関数を使って値を文字列として取り出すことができます。
逆引きするには、IPアドレスを逆向きにしたようなホスト名をPTRレコードとして問い合わせます。reversename.from_address
が逆向きのホスト名を作る関数です。次のような値になっています。
reversename.from_address("172.217.174.100") #=> <DNS name 100.174.217.172.in-addr.arpa.>
さらに様々な理由によって、問い合わせ先のネームサーバーの指定が必要だとします。これは(おそらく)socketでは素直にできないです。
新しいresolverを作ってそれにネームサーバーを設定します。あとはそれを使って先ほどと同じようにquery
関数を呼ぶだけです。
res = resolver.Resolver() res.nameservers = ["8.8.8.8"] answers = res.query("www.google.com")
xonshことはじめ
最近、xonshを使い始めました。
xonshrc
xonshの設定ファイルは~/.config/xonsh/rc.xshや~/.xonshrcが読み込まれるようですが、私は設定ファイルを何でも~/.configに集めたいので前者のファイルを作っています。
$COMPLETIONS_CONFIRM = True $HISTCONTROL = "ignoredups" $XONSH_AUTOPAIR = True $DYNAMIC_CWD_WIDTH = (30, "c") $PROMPT = ( "{BOLD_GREEN}{user}@{hostname} {BOLD_BLUE}{cwd}{NO_COLOR} {gitstatus}\n" "{env_name}{prompt_end} " ) @events.on_ptk_create def custom_keybindings(bindings, **kw): @bindings.add("c-@") def _change_repository(event): repository = $(ghq list -p | peco).strip() if repository: cd @(repository) event.current_buffer.validate_and_handle()
環境変数による設定やプロンプトの設定はあまり凝ったことはせずシンプルなものです。重要なのはzshのときからずっと使っているghq+pecoの操作をショートカットキーで呼び出せるようにすることです。
c-@のキーバインドはc-spaceのエイリアスになっていて*1、実際にはc-spaceで入力しています。
$(ghq list -p | peco).strip()
はシェルで実行されたghq+pecoの結果をPython文字列として処理しています(改行文字が末尾についているのでstrip)。
cd @(repository)
はPython文字列をシェルのコマンドラインに渡すための記法です。シェルとPythonを気軽に行ったり来たりできるのは便利ですね。
最後の event.current_buffer.validate_and_handle()
*2 は現在のプロンプトにENTERキーを入力するようなもので、cdした結果がすぐにプロンプトに反映されるようにしています。
- Environment Variables — xonsh 0.9.18 documentation
- Tutorial — xonsh 0.9.18 documentation
- Tutorial: prompt_toolkit custom keybindings — xonsh 0.9.18 documentation
xonshの起動方法
なんとなくログインシェルに設定するには抵抗があるので、zshから起動して使います。
具体的には、zshenvにPATHなどの最低限の設定を書いておいて、zshrcからtmuxを起動し、tmuxのデフォルトシェルとしてxonshが起動するようにしています。
if [[ $TERM_PROGRAM != vscode && -z $XONSH_VERSION && -z $TMUX ]]; then run-tmux fi
条件 $TERM_PROGRAM != vscode
はVS Codeのターミナルで起動していないことを確認しています。VS CodeでPythonを書いているときにターミナルを開くと、Python仮想環境のactivateが自動的に実行されますが、それがxonshではうまく動作しません。そのため、VS Codeのターミナルではtmuxも使わず素のzshを使うようにしています。
条件 -z $XONSH_VERSION
はxonshからzshが実行されていないことを確認しています。直接zshを実行した場合以外にも、xonshのsource-zshを実行した場合も該当します。
条件 -z $TMUX
はtmuxが実行中でないことを確認しています。
スクリプト run-tmux
はすでに起動中のtmuxセッションがあればそれに接続し、なければ新規のセッションにつなぐラッパースクリプトです。
ちなみに次のようなBashスクリプトになっています。
#!/usr/bin/env bash session=${1:-main} if [[ -z $TMUX ]]; then tmux new-session -A -s "$session" else if ! tmux has-session -t "$session" &>/dev/null; then TMUX='' tmux new-session -d -s "$session" fi tmux switch-client -t "$session" fi
これでひとまずは不自由なくxonsh生活が送れそうです。
*1:xonshが使っているprompt_toolkitのドキュメントに記載があります
*2:prompt_toolkitのBufferクラスからそれっぽい関数を見つけました
Visual Studio CodeでPython仮想環境
かれこれ10年以上ぐらい使っていたEmacsからVisual Studio Code(以下、VS Code)に改宗する可能性を感じて、ひとまず最近よく書いているPython環境をVS Codeで整えようという話です。
Pythonはプロジェクトごとの仮想環境の扱いがうまくいけばとりあえず何とかなるだろうという感じです。
私のPython仮想環境の作り方
私のPython仮想環境の作り方はこちらです。
sample
はプロジェクトの名前などです。プロジェクトのディレクトリ以下の.venv
の下に仮想環境を作ります。
python3 -m venv .venv/sample
ところが、巷でよく見かける仮想環境の作り方は単に次のようにしているようです。
python3 -m venv .venv
この方法で私がイマイチだなぁと思っているのは、仮想環境をactivateしたときに表示される名前が.venv
になってしまうことです。
speg03@speg03-mbp sample % python3 -m venv .venv speg03@speg03-mbp sample % . .venv/bin/activate (.venv) speg03@speg03-mbp sample %
そのため最初に書いたように仮想環境を作っています。
speg03@speg03-mbp sample % python3 -m venv .venv/sample speg03@speg03-mbp sample % . .venv/sample/bin/activate (sample) speg03@speg03-mbp sample %
こうなっていると複数の仮想環境を作ったときでも、今どの仮想環境を使っているのかわかりやすくなります。
VS Codeの設定
VS CodeでPythonのファイルを開いたときに、どのPythonを使うか選択することができます。しかし、.venv
に仮想環境を作っていた場合はその中のPythonを見つけてくれるようですが、.venv/sample
のように作っていた場合は見つけてくれないようでした。
VS Codeの設定ではpython.venvPath
に仮想環境を作っているディレクトリを指定してあげればよいようです。プロジェクトのワークスペース直下というのは${workspaceFolder}
で表現できます。
{ "python.venvPath": "${workspaceFolder}/.venv" }
一度、この中にある仮想環境を選択しておけば、ワークスペース設定として、.vscode/settings.json
にそのPythonのパスが書き込まれているので、次回以降はこの仮想環境が自動的に選ばれるようになります。
pytestでコンソールアプリケーションのテストを書く
テスト対象のアプリケーション
コンソールアプリケーションの題材として、ここでは以下のようなアプリケーションを考えます。
次のように実装しました。
import argparse import sys def main(): parser = argparse.ArgumentParser() parser.add_argument("--name") args = parser.parse_args() template = sys.stdin.read() print(template.format(args.name)) if __name__ == "__main__": main()
実行結果は以下のようになります。
echo "hello, {}" | python3 application.py --name=world
hello, world
テスト
pytestを使ってこのアプリケーションのテストを書いてみます。ポイントとなるのはコマンドライン引数、標準入力、標準出力をどのように扱えばよいかという点です。
import io from application import main def test_main(monkeypatch): argv = ["application.py", "--name=world"] stdin = io.StringIO("hello, {}") stdout = io.StringIO() with monkeypatch.context() as m: m.setattr("sys.argv", argv) m.setattr("sys.stdin", stdin) m.setattr("sys.stdout", stdout) main() assert stdout.getvalue() == "hello, world\n"
テストが成功することを確認します。
pytest test_application.py
このテストコードについて、もう少し見ていきましょう。
コマンドライン引数
args = parser.parse_args()
この部分で内部的にsys.argv
の値を参照しています。sys.argv
はコマンドライン引数の配列です。最初の要素はスクリプトの名前になっており、その後ろに実際のコマンドライン引数が続きます。
標準入力
template = sys.stdin.read()
sys.stdin
から標準入力の文字列を取得しています。sys.stdin
はファイルオブジェクトです。
標準出力
print(template.format(args.name))
print
が実行されるとsys.stdout
に対して文字列の書き込みが発生します。sys.stdout
はsys.stdin
と同様にファイルオブジェクトです。
Amazon Linuxの最新AMIを取得する
概要
Amazon Linuxの最新AMIを取得する方法について説明します。AMIのIDはリージョンごとに異なるため、利用しているリージョンで有効なAMIが取得できる方法を考えます。
事前準備
- Python 3.6.4
- boto3 1.5.35
次のようなコードでEC2のAPIにアクセスできる状態とします。
import boto3 session = boto3.session.Session() ec2 = session.client('ec2')
実験
現時点で、Amazon Linuxの最新AMIはamzn-ami-hvm-2017.09.1.20180115-x86_64-gp2
という名前のようです。(東京リージョンではID ami-ceafcba8
です)
query = 'amzn-ami-hvm-2017.09.1.20180115-x86_64-gp2' response = ec2.describe_images(Filters=[{'Name': 'name', 'Values': [query]}]) response['Images'][0]
{'Architecture': 'x86_64', 'BlockDeviceMappings': [{'DeviceName': '/dev/xvda', 'Ebs': {'DeleteOnTermination': True, 'Encrypted': False, 'SnapshotId': 'snap-0d38721ac6a1fdcc9', 'VolumeSize': 8, 'VolumeType': 'gp2'}}], 'CreationDate': '2018-01-15T19:13:56.000Z', 'Description': 'Amazon Linux AMI 2017.09.1.20180115 x86_64 HVM GP2', 'EnaSupport': True, 'Hypervisor': 'xen', 'ImageId': 'ami-ceafcba8', 'ImageLocation': 'amazon/amzn-ami-hvm-2017.09.1.20180115-x86_64-gp2', 'ImageOwnerAlias': 'amazon', 'ImageType': 'machine', 'Name': 'amzn-ami-hvm-2017.09.1.20180115-x86_64-gp2', 'OwnerId': '137112412989', 'Public': True, 'RootDeviceName': '/dev/xvda', 'RootDeviceType': 'ebs', 'SriovNetSupport': 'simple', 'State': 'available', 'VirtualizationType': 'hvm'}
フィルタリングを使って検索してみます。 https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/Using_Filtering.html
query = 'amzn-ami-hvm-????.??.?.????????-x86_64-gp2' response = ec2.describe_images( Filters=[{'Name': 'name', 'Values': [query]}], Owners=['amazon']) # 作成日の降順でソート images = sorted(response['Images'], key=lambda img: img['CreationDate'], reverse=True) # AMI名だけのリストで表示 [img['Name'] for img in images]
['amzn-ami-hvm-2017.09.1.20180115-x86_64-gp2', 'amzn-ami-hvm-2017.09.1.20180108-x86_64-gp2', 'amzn-ami-hvm-2017.09.1.20180103-x86_64-gp2', 'amzn-ami-hvm-2017.09.1.20171120-x86_64-gp2', 'amzn-ami-hvm-2017.09.1.20171103-x86_64-gp2', 'amzn-ami-hvm-2017.09.0.20170930-x86_64-gp2', 'amzn-ami-hvm-2017.03.1.20170812-x86_64-gp2', 'amzn-ami-hvm-2017.03.1.20170623-x86_64-gp2', 'amzn-ami-hvm-2017.03.1.20170617-x86_64-gp2', 'amzn-ami-hvm-2017.03.0.20170417-x86_64-gp2', 'amzn-ami-hvm-2017.03.0.20170401-x86_64-gp2', 'amzn-ami-hvm-2016.09.1.20170119-x86_64-gp2', 'amzn-ami-hvm-2016.09.1.20161221-x86_64-gp2', 'amzn-ami-hvm-2016.09.0.20161028-x86_64-gp2', 'amzn-ami-hvm-2016.09.0.20160923-x86_64-gp2']
botocoreを使ったAWS設定情報の取得
概要
AWSのcredentialsファイルやconfigファイルの設定情報をbotocoreを使って取得する方法を説明します。また、それらの設定ファイルには独自の項目を追加できます。
AWS設定情報の取得
AWSのcredentials, configが次のような内容だったとします。
[default] aws_access_key_id = AKIAIOSFODNN7EXAMPLE aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
[default] region = ap-northeast-1 [profile prodaccess] region = ap-northeast-1 role_arn = arn:aws:iam::123456789012:role/ProductionAccessRole source_profile = default
botocoreを使って設定情報を簡単に取得することができます。get_scoped_config
は関数ですが、full_config
は属性であることに注意してください。
from botocore.session import Session # prodaccessプロファイルのセッションを作成 # (profile引数を省略するとdefaultプロファイルを使用) session = Session(profile='prodaccess') # 指定したプロファイルの設定を取得 scoped_config = session.get_scoped_config() # すべてのプロファイルの設定を取得 full_config = session.full_config
scoped_config
は次のような辞書オブジェクトになっています。
{ "region": "ap-northeast-1", "role_arn": "arn:aws:iam::123456789012:role/ProductionAccessRole", "source_profile": "default" }
full_config
は次のような辞書オブジェクトになっています。指定したプロファイルの設定だけではなく、すべてのプロファイルの設定が取得できています。
{ "profiles": { "default": { "region": "ap-northeast-1", "aws_access_key_id": "AKIAIOSFODNN7EXAMPLE", "aws_secret_access_key": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" }, "prodaccess": { "region": "ap-northeast-1", "role_arn": "arn:aws:iam::123456789012:role/ProductionAccessRole", "source_profile": "default" } } }
独自項目の追加
credentials, configファイルには独自の項目を設定できます。
[profile custom] region = ap-northeast-1 role_arn = arn:aws:iam::123456789012:role/ProductionAccessRole source_profile = default my_custom_parameter = custom value
独自に設定した項目も同じように取得できます。
{ "region": "ap-northeast-1", "role_arn": "arn:aws:iam::123456789012:role/ProductionAccessRole", "source_profile": "default", "my_custom_parameter": "custom value" }
AWSを利用した独自ツールの設定情報として使うとよいかもしれません。
もともとconfigファイルに設定できる既存の項目については次のページに記載されています。独自の項目を設定する場合には、既存の項目と同名にならないように気をつけたほうがよいでしょう。
AWS CLI Configuration Variables - AWS CLI Command Reference https://docs.aws.amazon.com/cli/latest/topic/config-vars.html
boto3を使った一時的なAWS認証情報の取得
概要
IAMロールの切り替えを利用している場合の一時的なAWS認証情報の取得方法について説明します。boto3を使うと、AWS CLIのプロファイル設定をもとに認証情報を簡単に取得することができます。
IAMロールの切り替え
AWS CLIでIAMロールの切り替えを行う場合は、以下のようなプロファイル設定をします。
[profile prodaccess] role_arn = arn:aws:iam::123456789012:role/ProductionAccessRole source_profile = default
設定内容の詳細については以下のページが参考になります。
IAM ロールの切り替え(AWS Command Line Interface) - AWS Identity and Access Management https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/id_roles_use_switch-role-cli.html
また、IAMロールの切り替え自体については以下のページが参考になります。
チュートリアル: AWS アカウント間の IAM ロールを使用したアクセスの委任 - AWS Identity and Access Management https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/tutorial_cross-account-with-roles.html
boto3を使った一時的なAWS認証情報の取得
使用したバージョンは以下の通りです。 * Python 3.6.4 * boto3 1.5.21
boto3は明示的にIAMロールの切り替えを行わなくても、プロファイル設定を見て必要があれば自動的にIAMロールの切り替えを行ってくれます。そのため、以下のような簡単なPythonコードで一時的なAWS認証情報を取得できます。
import boto3 session = boto3.session.Session(profile_name='prodaccess') credentials = session.get_credentials() print('export AWS_ACCESS_KEY_ID={}'.format(credentials.access_key)) print('export AWS_SECRET_ACCESS_KEY={}'.format(credentials.secret_key)) print('export AWS_SESSION_TOKEN={}'.format(credentials.token))
このコードを実行すると以下のような出力が得られます。この出力をシェルで評価すると一時的なAWS認証情報として利用することができます。
$ python3 credentials.py export AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE export AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY export AWS_SESSION_TOKEN=AQoDYXdzEGcaEXAMPLE2gsYULo+Im5ZEXAMPLEeYjs1M2FUIgIJx9tQqNMBEXAMPLECvSRyh0FW7jEXAMPLEW+vE/7s1HRpXviG7b+qYf4nD00EXAMPLEmj4wxS04L/uZEXAMPLECihzFB5lTYLto9dyBgSDyEXAMPLEKEY9/g7QRUhZp4bqbEXAMPLENwGPyOj59pFA4lNKCIkVgkREXAMPLEjlzxQ7y52gekeVEXAMPLEDiB9ST3UusKdEXAMPLE1TVastU1A0SKFEXAMPLEiywCC/Cs8EXAMPLEpZgOs+6hz4AP4KEXAMPLERbASP+4eZScEXAMPLENhykxiHenDHq6ikBQ==
ただし、この方法では認証情報の有効時間を指定できないため、デフォルトの1時間が適用されます。
認証情報の有効時間(DurationSeconds)については以下に記載があります。
AssumeRole - AWS Security Token Service https://docs.aws.amazon.com/ja_jp/STS/latest/APIReference/API_AssumeRole.html