# 接続のエクスポート

## 概要 <a href="#overview" id="overview"></a>

本ページでは、Pythonスクリプトを使用して、接続データと接続パラメータをJSONファイルにエクスポートする方法の1つについて取り扱います。エクスポートされたファイルは、別の Keeperコネクションマネージャーのインスタンスへ接続をインポート ([こちらのページ](https://docs.keeper.io/jp/keeper-connection-manager/using-keeper-connection-manager/creating-connections/batch-import-for-connections)を参照) したり、Keeperコネクションマネージャーのクラウドバージョンへの移行に使用したりできます。

本ページに記載の例は、MySQLデータベースを使用したKeeperコネクションマネージャーの標準である[Docker自動インストール方法](https://docs.keeper.io/jp/keeper-connection-manager/installation/auto-docker-install)に基づいています。

## 1. ローカルマシンへのMySQLポートを開く <a href="#step-1-open-mysql-port-to-local-machine" id="step-1-open-mysql-port-to-local-machine"></a>

PythonファイルがDockerコンテナ内のデータベースをクエリできるようにするには、`/etc/kcm-setup/docker-compose.yml` ファイルを編集し、以下のように「ports」セクションを「db」コンテナに追加します。

```yml
db:
        image: keeper/guacamole-db-mysql :2
        restart: unless-stopped 
        environment:
            ACCEPT EULA: "Y"
            GUACAMOLE_DATABASE: "guacamole_db"
            GUACAMOLE_USERNAME: "guacamole_user"
            GUACAMOLE_PASSWORD: "XXXXXXXXXXXXXXXXXXXXXXXXX"
            GUACAMOLE_ADMIN_PASSWORD: "XXXXXXXXXXXXXXXXXXXXXXXXX"
            MYSQL_ROOT_PASSWORD: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
        LOG_LEVEL: "debug"
        ports:
            - "3306:3306"                       
```

Docker Composeの更新を適用するには、以下の手順で行います。

* **自動インストールスクリプト (auto-docker) を使用している場合**\
  `kcm-setup.run` スクリプトが保存されている場所に移動し、以下を実行します。

  ```bash
  sudo ./kcm-setup.run apply
  ```
* **Docker Compose インストールの場合**\
  `/etc/kcm-setup` フォルダに移動し、以下を実行します。

  ```bash
  docker compose up -d
  ```

## 2. Pythonモジュールをインストールする <a href="#step-2-install-python-modules" id="step-2-install-python-modules"></a>

インスタンスにすでにPython3がインストールされている前提で、必要なPythonモジュールをインストールします。

{% tabs %}
{% tab title="MySQL" %}

```bash
pip3 install mysql-connector
pip3 install pyYAML
```

{% endtab %}

{% tab title="PostgreSQL" %}

```bash
pip3 install psycopg2-binary
pip3 install pyYAML
```

{% endtab %}
{% endtabs %}

```bash
pip3 install mysql-connector-python
pip3 install pyyaml
```

お使いのオペレーティングシステムに応じて、以下のコマンドを使用して `python3-pip` をインストールしてください。

* **CentOS / RHEL 8以降の場合**

```bash
sudo dnf install python3-pip
```

* **CentOS / RHEL 7以降の場合**

```bash
sudo apt install python3-pip
```

* **CentOS / RHEL 7以降の場合**

```bash
sudo apt install python3-pip
```

## 3. エクスポートスクリプトを作成する <a href="#step-3-create-the-export-script" id="step-3-create-the-export-script"></a>

以下のコードをコピーして `export.py` というファイルに貼り付け、Keeperコネクションマネージャーの インスタンス (`kcm-setup.run` ファイルと同じ場所) に配置します。このPythonスクリプトは以下を実行します。

* 標準 `/etc/kcm-setup/` フォルダ内の `docker-compose.yml` ファイルを特定する。
* MySQLまたはPostgreSQLの認証情報を取得し、データベースに接続する。
* 接続情報をエクスポートし、同じフォルダにexport.jsonというファイルを作成する。

環境によっては、ファイルを編集する必要がある場合があります。

{% tabs %}
{% tab title="MySQLエクスポート" %}

```python
import mysql.connector
import json
import yaml

# docker-compose.ymlファイルのパス
docker_compose_file = '/etc/kcm-setup/docker-compose.yml'

# docker-compose.ymlからデータベースの認証情報を抽出する関数
def get_db_config_from_compose():
    with open(docker_compose_file, 'r') as file:
        # docker-composeのYAMLファイルを読み込む
        compose_data = yaml.safe_load(file)
        
        # 'db'サービスから必要な情報を抽出
        db_service = compose_data['services']['db']
        environment = db_service['environment']
        
        db_name = environment.get('GUACAMOLE_DATABASE', 'guacamole_db')
        db_user = environment.get('GUACAMOLE_USERNAME', 'guacamole_user')
        db_password = environment.get('GUACAMOLE_PASSWORD', 'password')  # 存在しない場合に備えたデフォルト値
        
        return {
            'host': 'localhost',  # Docker内で動作するため、データベースはローカルにある前提
            'user': db_user,
            'password': db_password,
            'database': db_name,
            'port': 3306  # MySQLのデフォルトポート
        }

def build_connection_group_paths(cursor):
    """
    親子関係を解決して、グループIDからフルパスへのマッピング辞書を作成します。
    """
    cursor.execute("SELECT connection_group_id, parent_id, connection_group_name FROM guacamole_connection_group")
    groups = cursor.fetchall()

    group_paths = {}

    def resolve_path(group_id):
        if group_id is None:
            return "ROOT"
        if group_id in group_paths:
            return group_paths[group_id]
        # グループの詳細を取得
        group = next(g for g in groups if g['connection_group_id'] == group_id)
        parent_path = resolve_path(group['parent_id'])
        full_path = f"{parent_path}/{group['connection_group_name']}"
        group_paths[group_id] = full_path
        return full_path

    # すべてのグループのパスを解決
    for group in groups:
        resolve_path(group['connection_group_id'])

    return group_paths

# すべての接続、ユーザー、グループ、属性を取得するSQLクエリ
query = """
SELECT
    c.connection_id,
    c.connection_name AS name,
    c.protocol,
    cp.parameter_name,
    cp.parameter_value,
    e.name AS entity_name,
    e.type AS entity_type,
    g.connection_group_id,
    g.parent_id,
    g.connection_group_name AS group_name,
    ca.attribute_name,
    ca.attribute_value
FROM
    guacamole_connection c
LEFT JOIN
    guacamole_connection_parameter cp ON c.connection_id = cp.connection_id
LEFT JOIN
    guacamole_connection_attribute ca ON c.connection_id = ca.connection_id
LEFT JOIN
    guacamole_connection_group g ON c.parent_id = g.connection_group_id
LEFT JOIN
    guacamole_connection_permission p ON c.connection_id = p.connection_id
LEFT JOIN
    guacamole_entity e ON p.entity_id = e.entity_id;
"""

def export_to_json(db_config):
    try:
        # データベースに接続
        conn = mysql.connector.connect(**db_config)
        cursor = conn.cursor(dictionary=True)  # 扱いやすいように辞書形式のカーソルを使用

        # 接続グループのパスを作成
        connection_group_paths = build_connection_group_paths(cursor) 

        # クエリを実行
        cursor.execute(query)
        rows = cursor.fetchall()

        # データを想定の形式に整理
        connections = {}
        for row in rows:
            conn_id = row['connection_id']
            if conn_id not in connections:
                # 接続グループのパスを解決
                group_path = connection_group_paths.get(row['connection_group_id'], "ROOT")
                connections[conn_id] = {
                    'name': row['name'],
                    'protocol': row['protocol'],
                    'parameters': {},
                    'users': [],
                    'groups': [],  # ここにユーザーグループが入ります
                    'group': group_path,  # 接続グループのパス
                    'attributes': {}
                }
            # パラメータを処理
            if row['parameter_name']:
                connections[conn_id]['parameters'][row['parameter_name']] = row['parameter_value']
            # ユーザーを処理
            if row['entity_type'] == 'USER' and row['entity_name'] not in connections[conn_id]['users']:
                connections[conn_id]['users'].append(row['entity_name'])
            # ユーザーグループを処理
            if row['entity_type'] == 'USER_GROUP' and row['entity_name'] not in connections[conn_id]['groups']:
                connections[conn_id]['groups'].append(row['entity_name'])
            # 属性を処理
            if row['attribute_name']:
                connections[conn_id]['attributes'][row['attribute_name']] = row['attribute_value']

        # リスト形式に変換
        connection_list = [conn for conn in connections.values()]

        # JSONファイルとして出力
        with open('export.json', 'w') as json_file:
            json.dump(connection_list, json_file, indent=4)

        print("Export successful! Data written to export.json")

    except mysql.connector.Error as err:
        print(f"Error: {err}")
    finally:
        # カーソルと接続を閉じる
        if cursor:
            cursor.close()
        if conn:
            conn.close()

if __name__ == '__main__':
    # docker-compose.ymlからデータベース設定を取得
    db_config = get_db_config_from_compose()
    export_to_json(db_config)
```

{% endtab %}

{% tab title="PostgreSQLエクスポート" %}

```python
import psycopg2
import psycopg2.extras
import json
import yaml

# docker-compose.ymlファイルのパス
docker_compose_file = '/etc/kcm-setup/docker-compose.yml'

# docker-compose.ymlからデータベースの認証情報を抽出する関数
def get_db_config_from_compose():
    with open(docker_compose_file, 'r') as file:
        compose_data = yaml.safe_load(file)

        db_service = compose_data['services']['db']
        env = db_service['environment']

        return {
            'host': 'localhost',
            'user': env.get('GUACAMOLE_USERNAME', 'guacamole_user'),
            'password': env.get('GUACAMOLE_PASSWORD', 'password'),
            'database': env.get('GUACAMOLE_DATABASE', 'guacamole_db'),
            'port': 5432  # PostgreSQLのデフォルトポート
        }

def build_connection_group_paths(cursor):
    cursor.execute("SELECT connection_group_id, parent_id, connection_group_name FROM guacamole_connection_group")
    groups = cursor.fetchall()

    group_paths = {}

    def resolve_path(group_id):
        if group_id is None:
            return "ROOT"
        if group_id in group_paths:
            return group_paths[group_id]
        group = next(g for g in groups if g['connection_group_id'] == group_id)
        parent_path = resolve_path(group['parent_id'])
        full_path = f"{parent_path}/{group['connection_group_name']}"
        group_paths[group_id] = full_path
        return full_path

    for group in groups:
        resolve_path(group['connection_group_id'])

    return group_paths

# SQLクエリは同じ(PostgreSQL互換の構文)
query = """
SELECT
    c.connection_id,
    c.connection_name AS name,
    c.protocol,
    cp.parameter_name,
    cp.parameter_value,
    e.name AS entity_name,
    e.type AS entity_type,
    g.connection_group_id,
    g.parent_id,
    g.connection_group_name AS group_name,
    ca.attribute_name,
    ca.attribute_value
FROM
    guacamole_connection c
LEFT JOIN
    guacamole_connection_parameter cp ON c.connection_id = cp.connection_id
LEFT JOIN
    guacamole_connection_attribute ca ON c.connection_id = ca.connection_id
LEFT JOIN
    guacamole_connection_group g ON c.parent_id = g.connection_group_id
LEFT JOIN
    guacamole_connection_permission p ON c.connection_id = p.connection_id
LEFT JOIN
    guacamole_entity e ON p.entity_id = e.entity_id;
"""

def export_to_json(db_config):
    try:
        conn = psycopg2.connect(
            host=db_config['host'],
            user=db_config['user'],
            password=db_config['password'],
            dbname=db_config['database'],
            port=db_config['port']
        )
        cursor = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor)

        connection_group_paths = build_connection_group_paths(cursor)

        cursor.execute(query)
        real_dict_rows = cursor.fetchall()
        # RealDictRowオブジェクトを通常のdictに変換
        rows = [dict(row) for row in real_dict_rows]

        # データを想定の形式に整理
        connections = {}
        for row in rows:
            conn_id = row['connection_id']
            if conn_id not in connections:
                group_path = connection_group_paths.get(row['connection_group_id'], "ROOT")
                connections[conn_id] = {
                    'name': row['name'],
                    'protocol': row['protocol'],
                    'parameters': {},
                    'users': [],
                    'groups': [],
                    'group': group_path,
                    'attributes': {}
                }
            if row['parameter_name']:
                connections[conn_id]['parameters'][row['parameter_name']] = row['parameter_value']
            if row['entity_type'] == 'USER' and row['entity_name'] not in connections[conn_id]['users']:
                connections[conn_id]['users'].append(row['entity_name'])
            if row['entity_type'] == 'USER_GROUP' and row['entity_name'] not in connections[conn_id]['groups']:
                connections[conn_id]['groups'].append(row['entity_name'])
            if row['attribute_name']:
                connections[conn_id]['attributes'][row['attribute_name']] = row['attribute_value']

        with open('export.json', 'w') as json_file:
            json.dump(list(connections.values()), json_file, indent=4)

        print("Export successful! Data written to export.json")

    except psycopg2.Error as err:
        print(f"Database Error: {err}")
    finally:
        # カーソルと接続を閉じる
        if cursor:
            cursor.close()
        if conn:
            conn.close()

if __name__ == '__main__':
    db_config = get_db_config_from_compose()
    export_to_json(db_config)
```

{% endtab %}
{% endtabs %}

スクリプトを実行するには、以下を入力します。

```bash
sudo python3 export.py
```

これにより、カレントフォルダに `export.json` というファイルが作成されます。

{% hint style="warning" %}
この `export.json` ファイルには、接続の作成方法によってはシークレット情報 (接続に関する認証情報など) が含まれている可能性があります。このファイルはKeeperボルトに保存して安全に保管してください。
{% endhint %}

## 備考

* 「groups」オブジェクトはユーザーグループを指します。
* 「group」オブジェクトはコネクショングループの場所を指します。
* このデータを別のKCMインスタンスにインポートする際、ターゲットにコネクショングループが存在している場合にのみ、接続は正常にインポートされます。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.keeper.io/jp/keeper-connection-manager/noekusupto.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
