iControl RESTでData Group Listを更新する

#はじめに
BIG-IPのData Group Listの更新を行なっている人には、このテンプレ的な作業をiCRで行いたい（例えば自動化したい）、という需要があると思いますが、ちょっと思ったようにはいかないので。

なお、ここで取り扱うData Group Listは、BIG-IP内にレコードを登録する**Internal**タイプのものであり、外部ファイルを取り込む**external**タイプのData Group Listではありません。

#参照情報
このページの内容は、DevCentralの以下のURLで説明されています。F5の技術者によるサンプルコードもあるので、この問題に取り組む人は参照するのが良いかと思います。

[DevCentral: add value in data group via REST API](https://devcentral.f5.com/questions/add-value-in-data-group-via-rest-api)


# 免責事項
本ページの内容に誤り等があり、参考にされた方がなんらかの損害を被った場合、一切の責任は負いません。

#問題
例えば、下記のような「test-data-group」という名前のData Group Listがあるとします。すでにレコードが３件入っています。

```text
(tmos)# list ltm data-group internal test-data-group 
ltm data-group internal test-data-group {
    records {
        www1.example.co.jp {
            data Pool_www1
        }
        www2.example.co.jp {
            data Pool_www2
        }
        www3.example.co.jp {
            data Pool_www3
        }
    }
    type string
}
```

ここに新規レコードとして、**name="www3.example.co.jp", value="Pool_www4"** を追加するとします。

この追加作業をtmshで行う場合、以下のようなコマンドになります。

```text
(tmos)# modify ltm data-group internal test-class records add { "www4.example.co.jp" { data "Pool_www4" } }
```

iCRではどうなるでしょう。上記のtmshコマンドを移植しようと考えた場合、modifyコマンドなので、PATCHメソッドを使えば移植できる気がするので、次のようなHTTPリクエストが考えられます。

```bash
curl -sku admin:admin -H "Content-Type: application/json" \
  -X PATCH \
  -d '{"records":[{"name":"www4.example.co.jp", "data":"Pool_www4"}]}' \
  https://bigip-ve/mgmt/tm/ltm/data-group/internal/test-data-group
```

しかし、上記のようなリクエストを実行して、変更後のData Group Listを確認してみると、

```text
(tmos)# list ltm data-group internal test-data-group
ltm data-group internal test-data-group {
    records {
        www4.example.co.jp {
            data Pool_www4
        }
    }
    type string
}
```

このとおり、Data Group Listのレコードは、元々存在していたレコードが全て消えてしまい、追加しようとした一件だけになってしまいます。

# 解説
iCRにおいて、Data Group Listのレコードは、一つの大きな配列として扱われます（少なくともv12では）。iCRでData Group listの更新を行う場合、レコード全体が一つの設定値になるというイメージで、**全部のレコードを送信する**必要があります。上記の例では追加したいレコードのみを送信した為に、そのレコードでData Group Listが上書きされてしまったわけです。

つまり、Data Group Listにレコードを追加する手順は、次の３ステップになります。

1. 変更前のData Group Listの全レコードを受け取る
2. そこに新規追加のレコードを追加する
3. 変更後となる全レコードを送信する

# サンプルコード
Pythonで、上記の３ステップを行うサンプルコードを作成しました。

```python3
# （準備）iControlのセッションを用意
import requests
import json

b = requests.session()
b.auth = ('admin', 'admin')
b.verify = False
b.headers.update({'Content-Type':'application/json'})

# 1. 変更前のData Group Listの全レコードを取得
data_group_str = b.get('https://bigip-ve/mgmt/tm/ltm/data-group/internal/test-data-group')
data_group = json.loads(data_group_str.text)
records = data_group['records']
```

このタイミングでは、recordsは次のようにになっています。

```text
[{'data': 'Pool_www1', 'name': 'www1.example.co.jp'},
 {'data': 'Pool_www2', 'name': 'www2.example.co.jp'},
 {'data': 'Pool_www3', 'name': 'www3.example.co.jp'}]
```

recordsに新規レコードを追加します。

```python3
new_record = {'name':'www4.example.co.jp', 'data':'Pool_www4'}
records.append(new_record)
```

これにより、recordsは次のようになります。

```text
[{'data': 'Pool_www1', 'name': 'www1.example.co.jp'},
 {'data': 'Pool_www2', 'name': 'www2.example.co.jp'},
 {'data': 'Pool_www3', 'name': 'www3.example.co.jp'},
 {'data': 'Pool_www4', 'name': 'www4.example.co.jp'}]
```

最後に、recordsを、JSONに変換してiControlで送信します。

```
payload['records'] = records
res = b.patch('https://bigip-ve/mgmt/tm/ltm/data-group/internal/test-data-group', data=json.dumps(payload))
```

tmshコマンドで確認してみます。

```text
(tmos)# list ltm data-group internal test-data-group
ltm data-group internal test-data-group {
    records {
        www1.example.co.jp {
            data Pool_www1
        }
        www2.example.co.jp {
            data Pool_www2
        }
        www3.example.co.jp {
            data Pool_www3
        }
        www4.example.co.jp {
            data Pool_www4
        }
    }
    type string
}
```

無事、元のレコード３件に、新しいレコード１件が追加されました。

このページの内容は、以上です。