pythonでLDAPにデータの移動変更削除をする(WriterとReader編)

# はじめに
[前回](https://qiita.com/mink0212/items/e60deb342d48d6cb9cda)と[前々回](https://qiita.com/mink0212/items/03c653dad49d05811a2e)はLDAPの追加と取得を行いました。今回は、削除やデータ移動、名前の変更など他の機能をまとめます。

# 名前の変更
## コネクションのみ使用した名前の変更
cnのみ変更したい場合はConnectionの```modify_dn()```を使用して変更できます。変更前のdnと変更後のcnを指定することでcnのみ変更することができます。以下の例は前々回でまとめたのでConnectionの接続は端折っています。

``` main.py

# 更新前に表示する
obj_cn_name = ObjectDef('inetOrgPerson', conn)
data_reader = Reader(conn, obj_cn_name, 'ou=sample-unit,dc=sample-component,dc=sample-ldap')
print(data_reader.search())
print('=======================')

# modify_dnに移動対象のdnと変更後のcnを指定する
conn.modify_dn('cn=sample-name,ou=sample-unit,dc=sample-component,dc=sample-ldap', 'cn=sample-rename')

# 更新後に表示する
data_reader2 = Reader(conn, obj_cn_name, 'ou=sample-unit,dc=sample-component,dc=sample-ldap')
print(data_reader2.search())
```

結果

```

[DN: cn=sample-name,ou=sample-unit,dc=sample-component,dc=sample-ldap - STATUS: Read - READ TIME: 2020-03-29T19:24:03.368406
    cn: sample-name
    objectClass: inetOrgPerson
    sn: sample
]
=======================
[DN: cn=sample-rename,ou=sample-unit,dc=sample-component,dc=sample-ldap - STATUS: Read - READ TIME: 2020-03-29T19:24:03.448482
    cn: sample-rename
    objectClass: inetOrgPerson
    sn: sample
]
```

変更前後のLDAPの値を見るとcnがsample-nameからsample-renameに変更されていることがわかります。また、中の値もそのまま移動されていることがわかります。


## Writerを使用した名前の変更
Writerを使用した名前の変更はWriterの```entry_rename()```を使用して変更できます。Connectionと異なり、Writer読み込み時に変更前のパスを指定して```entry_rename()```には変更後の名前をフルパスで与えます。

``` main.py

# 更新前に表示する
obj_cn_name = ObjectDef('inetOrgPerson', conn)
data_reader = Reader(conn, obj_cn_name, 'ou=sample-unit,dc=sample-component,dc=sample-ldap')
print(data_reader.search())
print('=======================')

# 移動対象をWriterに読み込ませる
data_reader = Reader(conn, obj_cn_name, 'cn=sample-name,ou=sample-unit,dc=sample-component,dc=sample-ldap')
data_reader.search()
data_writer = Writer.from_cursor(data_reader)

# 変更後のパスをフルで指定
data_writer[0].entry_rename('cn=sample-rename,ou=sample-unit,dc=sample-component,dc=sample-ldap')

# 変更結果の反映
data_writer.commit()

# 更新後に表示する
data_reader2 = Reader(conn, obj_cn_name, 'ou=sample-unit,dc=sample-component,dc=sample-ldap')
print(data_reader2.search())
```

結果

```

[DN: cn=sample-name,ou=sample-unit,dc=sample-component,dc=sample-ldap - STATUS: Read - READ TIME: 2020-03-29T19:40:09.898199
    cn: sample-name
    objectClass: inetOrgPerson
    sn: sample
]
=======================
[DN: cn=sample-rename,ou=sample-unit,dc=sample-component,dc=sample-ldap - STATUS: Read - READ TIME: 2020-03-29T19:40:10.017186
    cn: sample-rename
    objectClass: inetOrgPerson
    sn: sample
]
```

変更前後のLDAPの値を見るとConnectionの変更と同様に、cnがsample-nameからsample-renameに変更されて中の値もそのまま移動されていることがわかります。

# エンティティの移動
## コネクションのみ使用したエンティティの移動
エンティティを他のパスに移動させたい場合も上と同様Connectionの```modify_dn()```を使用して変更できます。移動対象のdnと変更後のcn、変更後のdnを指定することでエンティティを移動させることができます。以下の例は前々回でまとめたのでConnectionの接続は端折っています。

``` main.py

from ldap3 import Server, Connection, ObjectDef, Reader, Writer

server = Server('localhost')

conn = Connection(server, 'cn=admin,dc=sample-ldap',  password='LdapPass')
conn.bind()

# 更新前に表示する
obj_cn_name = ObjectDef('inetOrgPerson', conn)
data_reader = Reader(conn, obj_cn_name, 'dc=sample-component,dc=sample-ldap')
print(data_reader.search())
print('=======================')

# 移動する
conn.modify_dn('cn=sample-name,ou=sample-unit,dc=sample-component,dc=sample-ldap', 'cn=sample-name', new_superior='ou=sample-unit-move,dc=sample-component,dc=sample-ldap')

# 更新後に表示する
data_reader2 = Reader(conn, obj_cn_name, 'dc=sample-component,dc=sample-ldap')
print(data_reader2.search())
```

結果

```

[DN: cn=sample-name,ou=sample-unit,dc=sample-component,dc=sample-ldap - STATUS: Read - READ TIME: 2020-03-29T20:01:02.190680
    cn: sample-name
    objectClass: inetOrgPerson
    sn: test
    st: sample
, DN: cn=sample-name2,ou=sample-unit,dc=sample-component,dc=sample-ldap - STATUS: Read - READ TIME: 2020-03-29T20:01:02.194679
    cn: sample-name2
    objectClass: inetOrgPerson
    sn: test
    st: sample
]
=======================
[DN: cn=sample-name,ou=sample-unit-move,dc=sample-component,dc=sample-ldap - STATUS: Read - READ TIME: 2020-03-29T20:01:02.233686
    cn: sample-name
    objectClass: inetOrgPerson
    sn: test
    st: sample
, DN: cn=sample-name2,ou=sample-unit,dc=sample-component,dc=sample-ldap - STATUS: Read - READ TIME: 2020-03-29T20:01:02.236675
    cn: sample-name2
    objectClass: inetOrgPerson
    sn: test
    st: sample
]
```

cnがsample-nameのouがsample-unitからsample-unit-moveに移動していることが分かります。さらに先ほどと同様に中の属性も一緒に移動されています。

## Writerを使用したエンティティの移動
Writerを使用したエンティティの移動はWriterの```entry_move()```を使用して変更できます。```entry_rename()```と同じ使い方で、Writer読み込み時に変更前のパスを指定して```entry_move()```には変更後のパスをフルパスで与えます。ここでは、移動後のエンティティの名前はパスに入れてはいけないこと、存在しないパスには移動できないことに気をつけてください。

``` main.py

# 更新前に表示する
obj_cn_name = ObjectDef('inetOrgPerson', conn)
data_reader = Reader(conn, obj_cn_name, 'dc=sample-component,dc=sample-ldap')
print(data_reader.search())
print('=======================')

# 移動対象をWriterに読み込ませる
data_reader = Reader(conn, obj_cn_name, 'cn=sample-name,ou=sample-unit,dc=sample-component,dc=sample-ldap')
data_reader.search()
data_writer = Writer.from_cursor(data_reader)

# 値を移動する
data_writer[0].entry_move('cn=sample-name,ou=sample-unit-move,dc=sample-component,dc=sample-ldap')

# 変更結果の反映
data_writer.commit()

# 更新後に表示する
data_reader2 = Reader(conn, obj_cn_name, 'dc=sample-component,dc=sample-ldap')
print(data_reader2.search())
```

結果

```

[DN: cn=sample-name,ou=sample-unit,dc=sample-component,dc=sample-ldap - STATUS: Read - READ TIME: 2020-03-29T20:08:33.946805
    cn: sample-name
    objectClass: inetOrgPerson
    sn: test
    st: sample
, DN: cn=sample-name2,ou=sample-unit,dc=sample-component,dc=sample-ldap - STATUS: Read - READ TIME: 2020-03-29T20:08:33.952774
    cn: sample-name2
    objectClass: inetOrgPerson
    sn: test
    st: sample
]
=======================
[DN: cn=sample-name,ou=sample-unit-move,dc=sample-component,dc=sample-ldap - STATUS: Read - READ TIME: 2020-03-29T20:08:34.045188
    cn: sample-name
    objectClass: inetOrgPerson
    sn: test
    st: sample
, DN: cn=sample-name2,ou=sample-unit,dc=sample-component,dc=sample-ldap - STATUS: Read - READ TIME: 2020-03-29T20:08:34.051225
    cn: sample-name2
    objectClass: inetOrgPerson
    sn: test
    st: sample
]

```

cnがsample-nameのouがsample-unitからsample-unit-moveに移動していることが分かります。さらに先ほどと同様に中の属性も一緒に移動されています。



# 削除
## コネクションのみ使用した削除
エンティティを削除したい場合はConnectionの```delete()```を使用します。この関数にdnを指定すると削除できます。以下の例は前々回でまとめたのでConnectionの接続は端折っています。

``` main.py

# 削除前に表示する
conn.search('ou=sample-unit,dc=sample-component,dc=sample-ldap', '(objectclass=inetOrgPerson)')
print(conn.entries)
print('=======================')

# パスを指定して削除
ou_result = conn.delete('cn=sample-name2,ou=sample-unit,dc=sample-component,dc=sample-ldap')
print(ou_result)

# 削除後に表示する
conn.search('ou=sample-unit,dc=sample-component,dc=sample-ldap', '(objectclass=inetOrgPerson)')
print(conn.entries)
```

結果

```

[DN: cn=sample-name,ou=sample-unit,dc=sample-component,dc=sample-ldap - STATUS: Read - READ TIME: 2020-03-29T20:19:59.281937
, DN: cn=sample-name2,ou=sample-unit,dc=sample-component,dc=sample-ldap - STATUS: Read - READ TIME: 2020-03-29T20:19:59.281937
]
=======================
True
[DN: cn=sample-name,ou=sample-unit,dc=sample-component,dc=sample-ldap - STATUS: Read - READ TIME: 2020-03-29T20:19:59.322233
]
```

削除後にsample2のcnが消えていることがわかります。

取得と合わせることで全削除もできます。

``` main.py

conn.search('dc=sample-component,dc=sample-ldap', '(objectclass=inetOrgPerson)')
for entry in conn.entries:
    del_result = conn.delete(entry.entry_dn)
    print(del_result)
```

## Writerを使用した削除
エンティティを削除したい場合はWriterの```entry_delete()```を使用します。今までのWriterを使用した操作と同様にWriterを生成した後にエンティティの```entry_delete()```を呼ぶだけです。以下の例は前々回でまとめたのでConnectionの接続は端折っています。

``` main.py

# 更新前に表示する
obj_cn_name = ObjectDef('inetOrgPerson', conn)
data_reader = Reader(conn, obj_cn_name, 'ou=sample-unit,dc=sample-component,dc=sample-ldap')
print(data_reader.search())
print('=======================')

# 移動対象をWriterに読み込ませる
data_reader = Reader(conn, obj_cn_name, 'cn=sample-name,ou=sample-unit,dc=sample-component,dc=sample-ldap')
data_reader.search()
data_writer = Writer.from_cursor(data_reader)

# 値を削除する
data_writer[0].entry_delete()
data_writer.commit()

# 更新後に表示する
data_reader2 = Reader(conn, obj_cn_name, 'ou=sample-unit,dc=sample-component,dc=sample-ldap')
print(data_reader2.search())
```

結果

```

[DN: cn=sample-name,ou=sample-unit,dc=sample-component,dc=sample-ldap - STATUS: Read - READ TIME: 2020-03-29T20:30:26.155112
    cn: sample-name
    objectClass: inetOrgPerson
    sn: test
    st: sample
, DN: cn=sample-name2,ou=sample-unit,dc=sample-component,dc=sample-ldap - STATUS: Read - READ TIME: 2020-03-29T20:30:26.160111
    cn: sample-name2
    objectClass: inetOrgPerson
    sn: test
    st: sample
]
=======================
[DN: cn=sample-name2,ou=sample-unit,dc=sample-component,dc=sample-ldap - STATUS: Read - READ TIME: 2020-03-29T20:30:26.264725
    cn: sample-name2
    objectClass: inetOrgPerson
    sn: test
    st: sample
]
```

削除後の結果を見るとsample-nameのcnがなくなっていることがわかります。

これも検索条件を変えると全削除ができます

``` main.py

# 上位のパスをWriterに読み込ませる
data_reader = Reader(conn, obj_cn_name, 'dc=sample-component,dc=sample-ldap')
data_reader.search()
data_writer = Writer.from_cursor(data_reader)

# 値をすべて削除する
for data_entity in data_writer:
    data_entity.entry_delete()

```

# おわりに
LDAPを操作する上で必要な追加・検索・削除・変更をまとめることができました。さらにRDBには無い移動であったり名前の変更であったりとディレクトリ操作特有のものも出てきました。このように似たようにデータを保存する機能を持っていたとしてもRDBとLDAPは明確に違う点があるためうまく使い分けるとより便利になるのではないかと思いました。