Python はじめから勉強 Hour10：アプリケーションを作ってみる

# Python はじめから勉強 Hour10：アプリケーションを作ってみる

* Pythonで何かしようとしたときに、まずサンプルスクリプトを探してなんとなく実行してた私が、
* 自動実行でREST API叩いて、結果の確認、VM操作までやってみたいと思う7時間
* と思ってたら7時間を超えて10回になりました。
* 今回はアプリケーションを作ってみます。もともと考えてたREST APIは叩いてないけどまぁいいでしょう。自動実行でコマンドの結果をもってくるという、運用や構築フェーズでありがちなアプリを作りました。



## 学習資料

* たった 1日で基本が身に付く！ Python超入門 
  * https://a.r10.to/hbHMiv



## 過去の投稿

* [Python はじめから勉強 Hour1：Hello World](https://qiita.com/matsumo2019/items/5a69381f44456e6e6a5d)
* [Python はじめから勉強 Hour2：制御文](https://qiita.com/matsumo2019/items/6daa862fd34c8929fb65)
* [Python はじめから勉強 Hour3：関数](https://qiita.com/matsumo2019/items/6c63c737c27e51000f79)
* [Python はじめから勉強 Hour4：オブジェクト指向①](https://qiita.com/matsumo2019/items/b4e083b804590fcdf0d1)
* [Python はじめから勉強 Hour5：オブジェクト指向②](https://qiita.com/matsumo2019/items/5d429147ae44d81a69da)
* [Python はじめから勉強 Hour6：よく使うデータ型：タプル型・セット型・辞書型](https://qiita.com/matsumo2019/items/378009f0d65521c083df)
* [Python はじめから勉強 Hour7：クラスの使い方](https://qiita.com/matsumo2019/items/366bfd7e1dfbc06c89ab)
* [Python はじめから勉強 Hour8：パッケージの利用](https://qiita.com/matsumo2019/items/c820eab730f4bcad9b8f)
* [Python はじめから勉強 Hour9：ファイルの読み書き](https://qiita.com/matsumo2019/items/4092b4a39e6b8013d1a4)
* Python はじめから勉強 Hour10：アプリケーションの作成

# get-cvm.py

## applicationの概要

### 目的

* 特定のサーバ（今回はNutanix CVM）へアクセスして指定したコマンドの実行結果を表示する
* 対象のサーバのIPアドレス/ユーザ名/パスワードを指定したら、指定したコマンドの結果を出力する
* 対象のサーバやコマンドは変更することが多いため設定ファイルファイルとして別ファイルで指定する



### 想定する環境

* スクリプト実行端末と実行結果が欲しいサーバ
* サーバはNutanix CVM（Cluster IP)を想定 ※他のLinuxで動かなくても仕方なし
* 当然2台の関係はreachable
* Qiitaってフローチャート書けないの？画像貼るのめんどくさい

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/543420/49829854-56a9-9b39-19cf-fb6ed6da7109.png)


### プログラムの流れ

* 設定ファイルを読み込む
  * common.txt
  * command_list.txt
* サーバにsshしてしたコマンドを実行する
* command_list.txtがなくなるまで繰り返す（サーバで取得したいコマンドが複数ある場合も想定した作りにしたい）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/543420/410f7f02-7f7a-cb34-0a8d-7796448bfe98.png)


## 重要個所をテスト実装する

* まずは完璧を目指さずに主要な機能ができるか試していきます

### サーバに接続してコマンドを実行する

* これはparamikoの出番です。

* ParamikoとはPythonでSSHを使うためのライブラリです。

  `'python ssh`でぐぐるといろんな実装がヒットするでしょう。真似していきましょう

* Paramikoのインストール

  * Pythonインストール後、コマンドプロンプトから下記を実行すると、paramikoがインストールできます
  * `pip install paramiko`

#### get-cvm.py

* 細かいことは置いといて、拾ってきたスクリプトを加工してやってみましょう

```python
import paramiko

ip = 'X.X.X.1'
user_name = 'nutanix'
password = 'nutanix/4u'
command = '/usr/local/nutanix/bin/acli host.list'

client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(ip, username=user_name, password=password, timeout=3.0)
(stdin, stdout, stderr) = client.exec_command(command)
output = stdout.read().decode()
client.close()
print(output)
```

#### 実行結果

* 結果中身は期待した通り
* ひとまずベーシックな処理はできそうです

```python
C:\script\get-cvm>get-cvm.py
Hypervisor IP  Hypervisor DNS Name  Host UUID                             Compute Only  Schedulable  Hypervisor Type  Hypervisor Name
x.x.x.5  x.x.x.5        8dc18013-8ce0-4337-ac60-132e655f1560  False         True         kKvm             AHV
x.x.x.6  x.x.x.6        5817f793-b820-46ed-9faf-de6162a4d2ac  False         True         kKvm             AHV
x.x.x.7  x.x.x.7        716216d9-fff6-4137-9e1b-36a0d2812836  False         True         kKvm             AHV
x.x.x.8  x.x.x.8        3fe758b3-6376-4526-b7cd-118e852d492c  False         True         kKvm             AHV


C:\script\get-cvm>
```



## Paramikoについて

* Paramikoの使い方はググって欲しいですが（詳しくないので...）、ここで使ったメソッドなどを記載
* ParamikoとはPythonでSSHを使うためのライブラリです。
* 先ほどのコードにコメントを入れてみました。

```python
import paramiko # paramikoのモジュールをインポートし、このプログラムで使えるようにします

# 接続するための準備
client = paramiko.SSHClient()	# SSHClientクラスのインスタンスを作成

# hostname hostキーの登録を省略するため（sshでの接続しますか?yes/noの短縮）
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())

# SSHClientクラスのconnectメソッドを使って接続する
client.connect(ip, username=user_name, password=password, timeout=3.0)

# exec_commandメソッドでコマンドを実行し戻り値に入れる
(stdin, stdout, stderr) = client.exec_command(command)

# ファイル読み込みなどと同じようにreadメソッドが使える
# Readメソッドはバイナリを返すのでデコード(デフォルトUTF-8)して文字列へ
output = stdout.read().decode()

# closeメソッドで切断
client.close()

# 読み込んだ結果を出力
print(output)
```



## 本番プログラムを作る

* 先ほどの1ファイルでのスクリプトでもやりたいことは満たすのですが、下記のような欠点があります。

  * IPやIDなどがプログラムに直書き：サーバが変わるとプログラムを直接編集する必要があり不要なミスを引き起こす可能性あり
  * コマンドも変更する可能性が高い。複数コマンド実行したい

* 上記のことを解決するため

  * 設定値は外出しにする：別ファイルで定義（common.json, command_list.txt）
    * ※コマンドリストはテキストファイルで1行1コマンドが分かりやすいのかと思いjsonにしていません（違うかな?）
  * クラスを作成する
    * 処理と振る舞いを分けるためにメイン処理、Paramikoにお任せするところは分けて実装する

* 作るファイルはこちら

  * cvm_get.py		：メイン処理（設定ファイルの読み込みと、Paramikoの実行）

  * remote_command.py：paramikoを使った処理（名前思いつかん）

  * common.json    ：設定ファイル1（サーバ情報を定義）

  * command_list.txt ：設定ファイル2（コマンド一覧を定義）

    

### 設定ファイルの読み込み

* 今までJSONファイル読み込んだことがなかったので、調べてみる

#### common.json

* なんかNutanix専用の変数名にしてしまった...

```python
{
	"cluster_address": "1.1.1.1",
	"user_name": "nutanix",
	"cvm_password": "hogehoge"
}
```



#### get-cvm.py

* jsonモジュールにお任せすれば良さそう

```python
import json

json_open = open('common.json', 'r')
sv_info = json.load(json_open)

print(sv_info)

```



#### get-cvm.py実行結果

* おお、なんかそれっぽい。

```python
C:\script\get-cvm>print_json.py
{'prism_address': '1.1.1.1', 'user_name': 'nutanix', 'cvm_password': 'hogehoge'}

```

* 値も取り出してみましょう

```python
import json

json_open = open('common.json', 'r')
sv_info = json.load(json_open)

print(sv_info)

print(sv_info['cluster_address'])
print(sv_info['user_name'])
print(sv_info['cvm_password'])
```

* おお、いい感じ。jsonファイルから設定を読み込めそうです。

```python
C:\script\get-cvm>print_json.py
{'cluster_address': '1.1.1.1', 'user_name': 'nutanix', 'cvm_password': 'hogehoge'}
1.1.1.1
nutanix
hogehoge

```





## クラス作成（remote_command.py）

* 良く分からないが関数にしてみた

```python
import paramiko

class RemoteCommand:
	def get_df(ip, user, password, command):
  		client = paramiko.SSHClient()
  		client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
  		client.connect(ip, username=user, password=password, timeout=3.0)
  		(stdin, stdout, stderr) = client.exec_command(command)
  		output = stdout.read().decode()
  		client.close()

	  	return output

```



## メイン処理の作成

```python
import json
import remote_command

# ファイルの読み込み
fin = open('command_list.txt', 'r')
json_open = open('common.json', 'r')

# コマンドリストの読み込み
fin = open('command_list.txt', 'r')
command = fin.read()
fin.close()

# サーバ情報の読み込み
sv_info = json.load(json_open)
cluster_ip = sv_info['cluster_address']
user_name = sv_info['user_name']
password = sv_info['cvm_password']

## リモート実行。メイン処理
def main():
	remote_obj = remote_command.RemoteCommand()
	ret = remote_obj.get_df(cluster_ip, user_name, password, command)
	print(ret)

# 分からん。おまじないっぽい
if __name__ == '__main__':
  main()
```

* 実行結果

```
C:\script\get-cvm>get-cvm.py
Hypervisor IP  Hypervisor DNS Name  Host UUID                             Compute Only  Schedulable  Hypervisor Type  Hypervisor Name
x.x.x.5  x.x.x.5        8dc18013-8ce0-4337-ac60-132e655f1560  False         True         kKvm             AHV
x.x.x.6  x.x.x.6        5817f793-b820-46ed-9faf-de6162a4d2ac  False         True         kKvm             AHV
x.x.x.7  x.x.x.7        716216d9-fff6-4137-9e1b-36a0d2812836  False         True         kKvm             AHV
x.x.x.8  x.x.x.8        3fe758b3-6376-4526-b7cd-118e852d492c  False         True         kKvm             AHV

```

* おお、動いた。
* 今回の勉強やら、ネットやら調べてここまでできました。
* メイン処理がもっとコンパクトに書けるかと思ったけど、まぁいいでしょう。



## 改修

* そういえばcommand list って言っときながら、1コマンドだけだったので改修

* ファイルを読み込んでlistメソッド取り込む

* 結構インデントに厳しいな

* 変数名むずい。センスいるな

  

### メイン処理：get-cvm.py

```python
import json
import remote_command

# ファイルの読み込み
fin = open('command_list.txt', 'r')
json_open = open('common.json', 'r')

# コマンドリストの読み込み
fin = open('command_list.txt', 'r')
command = fin.readlines()
fin.close()

# サーバ情報の読み込み
sv_info = json.load(json_open)
cluster_ip = sv_info['cluster_address']
user_name = sv_info['user_name']
password = sv_info['cvm_password']

# リモート実行
def main():
# 2行追加。リストの中身がなくなるまで処理する。
	for name in command:
		print('===========<', name)

		remote_obj = remote_command.RemoteCommand()
		ret = remote_obj.get_df(cluster_ip, user_name, password, name)
		print(ret)

if __name__ == '__main__':
  main()
```

### 実行結果

* 複数のコマンドを一度に取ってくる。実行したコマンドもわかるようにした。

```
C:\script\get-cvm>get-cvm.py
===========< /usr/local/nutanix/bin/acli host.list

Hypervisor IP  Hypervisor DNS Name  Host UUID                             Compute Only  Schedulable  Hypervisor Type  Hypervisor Name
x.x.x.5  x.x.x.5        8dc18013-8ce0-4337-ac60-132e655f1560  False         True         kKvm             AHV
x.x.x.6  x.x.x.6        5817f793-b820-46ed-9faf-de6162a4d2ac  False         True         kKvm             AHV
x.x.x.7  x.x.x.7        716216d9-fff6-4137-9e1b-36a0d2812836  False         True         kKvm             AHV
x.x.x.8  x.x.x.8        3fe758b3-6376-4526-b7cd-118e852d492c  False         True         kKvm             AHV

===========< /usr/local/nutanix/cluster/bin/svmips

x.x.x.9 x.x.x.10 x.x.x.11 x.x.x.12

===========< ~/prism/cli/ncli cluster info

    Cluster Id                : 0005979e-89ec-3815-0000-000000017833::96307
    Cluster Uuid              : 0005979e-89ec-3815-0000-000000017833
    Cluster Name              : hogehoge
    Cluster Version           : 5.15
    Cluster Full Version      : el7.3-release-euphrates-5.15-stable-4fbdd4d9de331230bb468b3549f530e80ab53bb9
    External IP address       : x.x.x.13
    Node Count                : 4
    Block Count               : 1
    Shadow Clones Status      : Enabled
    Has Self Encrypting Disk  : no
    Cluster Masquerading I... :
    Cluster Masquerading PORT :
    Is registered to PC       : true
    Is LTS                    : true
    External Data Services... : x.x.x.14
    Support Verbosity Level   : BASIC_COREDUMP
    Lock Down Status          : Disabled
    Password Remote Login ... : Enabled
    Timezone                  : Asia/Tokyo
    NCC Version               : ncc-3.9.4.1
    Common Criteria Mode      : Disabled
    Degraded Node Monitoring  : Enabled

```

* おお、っぽくなった。



## まとめ

* 今回作ったものは、参考となるスクリプトがありましたが、1から勉強することでかなり理解度が高まりました。
* 理解度が高まったことで、自分が行いたいことも、わりとすんなり実装できました。
* これをアプリケーションと呼んでいいか分かりませんが、再利用しやすい形にできたと思います
* まだお作法などが分からず、変数名もイケてないし、書き方も変ですが、自分的には大満足



## まとめのまとめ

* 基本めっちゃ大事

* Python、奥が深いけど簡単！！（と、言い聞かせる）

* 手を動かしながら勉強するのめっちゃ大事

  

* [たった 1日で基本が身に付く！ Python超入門]([https://a.r10.to/hbHMiv)を基に勉強しながら作ってみました。

* さっと読むと3時間ぐらいで読めますが、じっくり Hello Worldから行うと20時間ぐらいかかりました。

* が、たった20時間でここまでできるならやった方がいいですね。私のやり方はこんな感じ

  1. 本を見る
  2. サンプルのスクリプトを動かす（コピペでも）
  3. 考えながら2のスクリプトを修正してみる（自分がやってみたいことを書く。背伸びしすぎない）
  4. ネットでも調べる（言い回しや、メソッドは他のサイトでどんなか見てみる）