IBM Cloud Functions の Python で外部パッケージを利用する


# はじめに
IBM Cloud Functions の Python ラインタイムに標準でインストールされているパッケージはこちらにドキュメントがあります。
https://cloud.ibm.com/docs/openwhisk?topic=cloud-functions-runtimes&locale=ja#python-

IBM Cloud Functions の Python コード内で、ここに含まれていない "SoftLayer" パッケージを import しようと試みても、「ModuleNotFoundError: No module named 'SoftLayer'」というエラーが出ます。

![Kobito.bklrgg.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/101361/6fcc10c7-a5e9-dcd1-d61f-72281ea8e41b.png "Kobito.bklrgg.png")

# IBM Cloud Functions の Python で外部パッケージを利用するには？

こちらのドキュメントにあるように、[ibmfunctions の docker image](https://hub.docker.com/u/ibmfunctions) を使って、自分で zip にパッケージ化する必要があります。

>[Python コードを仮想環境と一緒に .zip ファイル形式でパッケージ化する](https://cloud.ibm.com/docs/openwhisk?topic=cloud-functions-prep&locale=ja#prep_python_virtenv)

# 手順

## Docker イメージダウンロード

```shell:exec_command1
docker pull ibmfunctions/action-python-v3.7
```

## requirements.txt の準備
zip にパッケージ化するための作業ディレクトリを準備し、`import ` したい外部パッケージを `requirements.txt` にリストで書いておきます。

```shell:current_directory1
slapi-test
└── requirements.txt
```

```text:requirements.txt
SoftLayer
```

## virtualenv の作成
以下のコマンドで、 `virtualenv` ディレクトリが作成されます。

```shell:exec_command2
docker run --rm -v "$PWD:/tmp" ibmfunctions/action-python-v3.7 bash -c "cd /tmp && virtualenv virtualenv && source virtualenv/bin/activate && pip install -r requirements.txt"
```

```shell:result2
Using base prefix '/usr/local'
New python executable in /tmp/virtualenv/bin/python
Installing setuptools, pip, wheel...
done.
Collecting SoftLayer (from -r requirements.txt (line 1))
  Downloading https://files.pythonhosted.org/packages/54/d4/f8c70fd500f68e2cdcc6d9afb271528633398b10cdf36d94d57d4fc3ebcc/SoftLayer-5.8.0-py2.py3-none-any.whl (514kB)
Collecting prompt-toolkit>=2 (from SoftLayer->-r requirements.txt (line 1))
  Downloading https://files.pythonhosted.org/packages/f7/a7/9b1dd14ef45345f186ef69d175bdd2491c40ab1dfa4b2b3e4352df719ed7/prompt_toolkit-2.0.9-py3-none-any.whl (337kB)
Collecting pygments>=2.0.0 (from SoftLayer->-r requirements.txt (line 1))
  Downloading https://files.pythonhosted.org/packages/5c/73/1dfa428150e3ccb0fa3e68db406e5be48698f2a979ccbcec795f28f44048/Pygments-2.4.2-py2.py3-none-any.whl (883kB)
Collecting six>=1.7.0 (from SoftLayer->-r requirements.txt (line 1))
  Using cached https://files.pythonhosted.org/packages/73/fb/00a976f728d0d1fecfe898238ce23f502a721c0ac0ecfedb80e0d88c64e9/six-1.12.0-py2.py3-none-any.whl
Collecting urllib3>=1.24 (from SoftLayer->-r requirements.txt (line 1))
  Downloading https://files.pythonhosted.org/packages/e0/da/55f51ea951e1b7c63a579c09dd7db825bb730ec1fe9c0180fc77bfb31448/urllib3-1.25.6-py2.py3-none-any.whl (125kB)
Collecting ptable>=0.9.2 (from SoftLayer->-r requirements.txt (line 1))
  Downloading https://files.pythonhosted.org/packages/ab/b3/b54301811173ca94119eb474634f120a49cd370f257d1aae5a4abaf12729/PTable-0.9.2.tar.gz
Collecting click>=7 (from SoftLayer->-r requirements.txt (line 1))
  Downloading https://files.pythonhosted.org/packages/fa/37/45185cb5abbc30d7257104c434fe0b07e5a195a6847506c074527aa599ec/Click-7.0-py2.py3-none-any.whl (81kB)
Collecting requests>=2.20.0 (from SoftLayer->-r requirements.txt (line 1))
  Downloading https://files.pythonhosted.org/packages/51/bd/23c926cd341ea6b7dd0b2a00aba99ae0f828be89d72b2190f27c11d4b7fb/requests-2.22.0-py2.py3-none-any.whl (57kB)
Collecting wcwidth (from prompt-toolkit>=2->SoftLayer->-r requirements.txt (line 1))
  Downloading https://files.pythonhosted.org/packages/7e/9f/526a6947247599b084ee5232e4f9190a38f398d7300d866af3ab571a5bfe/wcwidth-0.1.7-py2.py3-none-any.whl
Collecting idna<2.9,>=2.5 (from requests>=2.20.0->SoftLayer->-r requirements.txt (line 1))
  Downloading https://files.pythonhosted.org/packages/14/2c/cd551d81dbe15200be1cf41cd03869a46fe7226e7450af7a6545bfc474c9/idna-2.8-py2.py3-none-any.whl (58kB)
Collecting certifi>=2017.4.17 (from requests>=2.20.0->SoftLayer->-r requirements.txt (line 1))
  Downloading https://files.pythonhosted.org/packages/18/b0/8146a4f8dd402f60744fa380bc73ca47303cccf8b9190fd16a827281eac2/certifi-2019.9.11-py2.py3-none-any.whl (154kB)
Collecting chardet<3.1.0,>=3.0.2 (from requests>=2.20.0->SoftLayer->-r requirements.txt (line 1))
  Using cached https://files.pythonhosted.org/packages/bc/a9/01ffebfb562e4274b6487b4bb1ddec7ca55ec7510b22e4c51f14098443b8/chardet-3.0.4-py2.py3-none-any.whl
Building wheels for collected packages: ptable
  Building wheel for ptable (setup.py): started
  Building wheel for ptable (setup.py): finished with status 'done'
  Created wheel for ptable: filename=PTable-0.9.2-cp37-none-any.whl size=22906 sha256=e852c92019f189224d5fc78ea1b5105290e9ddba0252d80a0e1ab92aec9ad76c
  Stored in directory: /root/.cache/pip/wheels/22/cc/2e/55980bfe86393df3e9896146a01f6802978d09d7ebcba5ea56
Successfully built ptable
Installing collected packages: six, wcwidth, prompt-toolkit, pygments, urllib3, ptable, click, idna, certifi, chardet, requests, SoftLayer
Successfully installed SoftLayer-5.8.0 certifi-2019.9.11 chardet-3.0.4 click-7.0 idna-2.8 prompt-toolkit-2.0.9 ptable-0.9.2 pygments-2.4.2 requests-2.22.0 six-1.12.0 urllib3-1.25.6 wcwidth-0.1.7
```

```shell:current_directory2
slapi-test
├── requirements.txt
└── virtualenv
```

## IBM Cloud Functions で実行する Python コードの準備

```python:__main__.py
#
#
# main() このアクションの実行時に呼び出されます
#
# @param Cloud Functions アクションは 1 つのパラメーターを受け入れます。このパラメーターは JSON オブジェクトでなければなりません。
#
# @return このアクションの出力。この出力は、JSON オブジェクトでなければなりません。
#

import sys
import SoftLayer

def main(dict):
    SLUSERNAME = dict['SLUSERNAME']
    SLAPIKEY = dict['SLAPIKEY']
    client = SoftLayer.create_client_from_env(username=SLUSERNAME,
                                              api_key=SLAPIKEY,
                                              endpoint_url="https://api.softlayer.com/xmlrpc/v3.1")
    pkgType = client['Product_Package_Type'].getAllObjects(mask='id,name')                                          
    return { 'message': pkgType }
```

```shell:current_directory3
slapi-test
├── requirements.txt
├── virtualenv
└── __main__.py
```

## Python コードを仮想環境と一緒に .zip ファイル形式でパッケージ化する
IBM Cloud Functions にデプロイするための zip ファイルを作成します。

```shell:exec_command3
zip -r slapi-test.zip virtualenv __main__.py
```

```shell:current_directory4
slapi-test
├── requirements.txt
├── virtualenv
├── __main__.py
└── slapi-test.zip
```

## IBM Cloud Functions にデプロイするための準備
`ibmcloud` コマンドを使って、ターゲットとなるリソースグループ、リージョン、ネームスペースを設定します。

```shell:exec_command4
ibmcloud login
ibmcloud target -g khayama -r jp-tok
ibmcloud fn namespace create khayama-fn
ibmcloud fn namespace target khayama-fn
```

## IBM Cloud Functions にデプロイ
zip ファイルをデプロイして、アクションを作成します。

```shell:exec_command5
ibmcloud fn action create slapi-test slapi-test.zip --kind python:3.7
ibmcloud fn action list
```

GUI 画面でもデプロイされたことが確認できます。

![Kobito.sYP6ZE.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/101361/5688fd9d-3219-45cb-5ef3-4d2f9eacc10a.png "Kobito.sYP6ZE.png")

## パラメータの設定
コード内には記述したくない認証情報について、以下のパラメータを設定します。
コード内でのパラメータの取り出し方は `__main__.py` を確認してください。

```shell:exec_command6
ibmcloud fn action update slapi-test --param SLUSERNAME "xxx" --param SLAPIKEY "yyy"
ibmcloud fn action get slapi-test parameters
```

## テスト
以下のコマンドで応答の確認ができます。

```shell:exec_command7
ibmcloud fn action invoke --result slapi-test
```

```shell:result7
{
    "message": [
        {
            "id": 8,
            "name": "Additional Services"
        },
        {
            "id": 446,
            "name": "Additional Services Enterprise Storage"
        },
        {
            "id": 208,
            "name": "Additional Services Iscsi Storage"
        },
        {
            "id": 207,
            "name": "Additional Services Network Attached Storage"
        },
        {
            "id": 243,
            "name": "Additional Services Performance Storage"
        },
        {
            "id": 650,
            "name": "Additional Services Portable IP Addresses"
        },
        {
            "id": 162,
            "name": "Additional Services Portable Storage"
        },
        {
            "id": 248,
            "name": "Additional Services Static IP Addresses"
        },
        {
            "id": 82,
            "name": "Additional services application delivery appliance"
        },
        {
            "id": 807,
            "name": "Additional services authentication services"
        },
        {
            "id": 204,
            "name": "Additional services cdn"
        },
        {
            "id": 232,
            "name": "Additional services domain name services"
        },
        {
            "id": 84,
            "name": "Additional services load balancer"
        },
        {
            "id": 206,
            "name": "Additional services message queue"
        },
        {
            "id": 203,
            "name": "Additional services object storage"
        },
        {
            "id": 205,
            "name": "Additional services ssl certificate"
        },
        {
            "id": 42,
            "name": "Bare Metal Gateway"
        },
        {
            "id": 209,
            "name": "Bare Metal Power Server"
        },
        {
            "id": 6,
            "name": "Bare Metal Server"
        },
        {
            "id": 691,
            "name": "Bare Metal Server for Service"
        },
        {
            "id": 695,
            "name": "Bare Metal Server for VPC"
        },
        {
            "id": 202,
            "name": "Bare metal cpu fast provision"
        },
        {
            "id": 491,
            "name": "Bluemix Service"
        },
        {
            "id": 497,
            "name": "CDN Addon"
        },
        {
            "id": 18,
            "name": "Colocation"
        },
        {
            "id": 674,
            "name": "Content Delivery Network Service"
        },
        {
            "id": 20,
            "name": "Control Panels for VPS"
        },
        {
            "id": 499,
            "name": "Dedicated Host"
        },
        {
            "id": 677,
            "name": "External Service Addons"
        },
        {
            "id": 493,
            "name": "Firewall"
        },
        {
            "id": 672,
            "name": "Firewall Appliance"
        },
        {
            "id": 122,
            "name": "Gateway Resource Group"
        },
        {
            "id": 494,
            "name": "Global IP Addresses"
        },
        {
            "id": 654,
            "name": "Hardware Security Module"
        },
        {
            "id": 662,
            "name": "Load Balancer As A Service"
        },
        {
            "id": 668,
            "name": "Mass Data Migration Service"
        },
        {
            "id": 238,
            "name": "Monitoring"
        },
        {
            "id": 667,
            "name": "Network Interconnect"
        },
        {
            "id": 240,
            "name": "Network Message Delivery"
        },
        {
            "id": 495,
            "name": "Network VLAN"
        },
        {
            "id": 673,
            "name": "Network VLAN for Service"
        },
        {
            "id": 658,
            "name": "New Customer Setup"
        },
        {
            "id": 663,
            "name": "Private Cloud"
        },
        {
            "id": 694,
            "name": "Reserved Capacity"
        },
        {
            "id": 2,
            "name": "Server"
        },
        {
            "id": 675,
            "name": "Service Addons"
        },
        {
            "id": 438,
            "name": "Software License"
        },
        {
            "id": 10,
            "name": "Solution"
        },
        {
            "id": 12,
            "name": "Solution Server"
        },
        {
            "id": 16,
            "name": "Specialty Server"
        },
        {
            "id": 659,
            "name": "Storage as a Service"
        },
        {
            "id": 484,
            "name": "Support"
        },
        {
            "id": 22,
            "name": "VHD Import"
        },
        {
            "id": 492,
            "name": "Virtual Datacenter"
        },
        {
            "id": 4,
            "name": "Virtual Server Instance"
        },
        {
            "id": 666,
            "name": "Virtual Server Instance for Service"
        },
        {
            "id": 693,
            "name": "Virtual Server Instance for VPC"
        }
    ]
}
```

# さいごに
このような手順を踏むことで IBM Cloud Functions の Python ラインタイムに標準で含まれていない任意のパッケージを利用することができます。
今回作成したアクションは、トリガーと組み合わせることで任意のタイミングでの実行が可能です。
サーバーレスな関数をぜひご活用ください。