Azure Functions の Azure Queue Storage トリガーの開発環境

- 記事が書かれているのを見て自分のやつも晒してみる

https://qiita.com/hoto17296/items/2d1707a53b9f0d21f92c

# 作るもの
WSL上で Azure Funtions を 「Nodejs」 で 「Service busトリガー」で動作し、「MongoDB」「Azurite」 と連携できる「Dev Container」の開発環境

# 参考ページ

https://learn.microsoft.com/ja-jp/azure/azure-functions/functions-run-local?tabs=macos%2Cisolated-process%2Cnode-v4%2Cpython-v2%2Cnon-http-trigger%2Ccontainer-apps&pivots=programming-language-javascript

# deccontainer系
## Dockerfile
vscodeのコマンドパレットで「Dev Containers: New Dev Container」から作った気がする

```Dockerfile:.devcontainer/Dockerfile
# Find the Dockerfile at the following URL:
# Node 14: https://github.com/Azure/azure-functions-docker/blob/dev/host/4/bullseye/amd64/node/node14/node14-core-tools.Dockerfile
# Node 16: https://github.com/Azure/azure-functions-docker/blob/dev/host/4/bullseye/amd64/node/node16/node16-core-tools.Dockerfile
ARG VARIANT=14
FROM mcr.microsoft.com/azure-functions/node:4-node18-core-tools

# [Optional] Uncomment this section to install additional OS packages.
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
#     && apt-get -y install --no-install-recommends <your-package-list-here>

# [Optional] Uncomment if you want to run your Function locally with 
# local.settings.json using `"AzureWebJobsStorage": "UseDevelopmentStorage=true"`
# RUN sudo -u node npm install -g azurite

# [Optional] Uncomment if you want to install more global node packages
# RUN sudo -u node npm install -g <your-package-list-here>

```

## docker-compose
mongo-expressから参照できるようにしている

```yaml:.devcontainer/docker-compose.yml
version: '3.1'

services:
  functions:
    build:
      context: ..
      dockerfile: .devcontainer/Dockerfile
    depends_on:
      - mongo
      - azure-storage
    user: "node:node"
    tty: true
    command: sleep infinity
    volumes:
      - ../:/workspace:cached
    environment:
      MONGO_URI: mongodb://root:example@mongo:27017/
    deploy:
      resources:
        limits:
          memory: 4GB
  mongo:
    image: mongo
    volumes:
      - mongo-data:/data/db
    environment:
      MONGO_INITDB_ROOT_USERNAME: root
      MONGO_INITDB_ROOT_PASSWORD: example
  mongo-express:
    image: mongo-express
    ports:
      - 8081:8081
    depends_on:
      - mongo
    environment:
      ME_CONFIG_MONGODB_ADMINUSERNAME: root
      ME_CONFIG_MONGODB_ADMINPASSWORD: example
      ME_CONFIG_MONGODB_URL: mongodb://root:example@mongo:27017/
  azure-storage:
    image: mcr.microsoft.com/azure-storage/azurite
    ports:
      - 10000:10000
      - 10001:10001
      - 10002:10002
    environment:
      AZURITE_ACCOUNTS: "devstoreaccount1;UseDevelopmentStorage=true"
    volumes:
      - azure-storage-data:/data
volumes:
  mongo-data:
  node_modules:
  azure-storage-data:

```

## devcontainer設定
extensions、settings、featuresはお好きなものを

```jsonc:.devcontainer/devcontainer.json
{
  "name": "Azure Functions & Node.js",
  "dockerComposeFile": "docker-compose.yml",
  "workspaceFolder": "/workspace/",
  "service": "functions",
  "forwardPorts": [10000, 10001, 10002],

  // Configure tool-specific properties.
  "customizations": {
    // Configure properties specific to VS Code.
    "vscode": {
      // Add the IDs of extensions you want installed when the container is created.
      "extensions": [
        "ms-azuretools.vscode-azurefunctions",
        "dbaeumer.vscode-eslint",
        "streetsidesoftware.code-spell-checker",
        "mongodb.mongodb-vscode",
        "biomejs.biome",
        "Azurite.azurite",
        "humao.rest-client",
        "ms-azuretools.vscode-docker"
      ]
    },
    "settings": {
      "editor.defaultFormatter": "biomejs.biome",
      "editor.formatOnSave": true,
      "[typescript]": {
        "editor.defaultFormatter": "biomejs.biome",
        "editor.formatOnSave": true
      },
      "editor.codeActionsOnSave": {
        "source.fixAll.eslint": true,
        "source.fixAll": true,
        "quickfix.biome": true,
        "source.organizeImports.biome": true
      },
      "cSpell.words": []
    }
  },

  // Use 'postCreateCommand' to run commands after the container is created.
  // "postCreateCommand": "npm install",
  "postCreateCommand": "sh .devcontainer/post-create.sh",

  // Set `remoteUser` to `root` to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
  "remoteUser": "node",
  "features": {
    "ghcr.io/devcontainers-contrib/features/zsh-plugins:0": {},
    "ghcr.io/devcontainers/features/git:1": {},
    "ghcr.io/devcontainers/features/azure-cli:1": {}
  },
  "runArgs": [
    // "--add-host=host.docker.internal:host-gateway",
    "--network=host",
    "--privileged"
  ]
}
```

## postCreateCommand
なくてもいいけど

```sh:.devcontainer/post-create.sh
#!/usr/bin/env sh

npm i
```

# Functions設定

## host.json
「serviceBus」と「durableTask」を設定している
任意の項目も多いので最小限動かすのには不要なものもある

- 参考

https://learn.microsoft.com/ja-jp/azure/azure-functions/functions-host-json


```json:host.json
{
  "version": "2.0",
  "logging": {
    "logLevel": {
      "Function": "Information",
      "durableTask": "Information",
      "default": "Warning"
    },
    "console": {
      "isEnabled": true,
      "DisableColors": true
    },
    "applicationInsights": {
      "samplingSettings": {
        "isEnabled": true,
        "excludedTypes": "Request"
      }
    }
  },
  "functions": [],
  "functionTimeout": "00:20:00",
  "extensionBundle": {
    "id": "Microsoft.Azure.Functions.ExtensionBundle",
    "version": "[4.*, 5.0.0)"
  },
  "extensions": {
    "serviceBus": {
      "prefetchCount": 100,
      "messageHandlerOptions": {
        "autoComplete": true,
        "maxConcurrentCalls": 32,
        "maxAutoRenewDuration": "00:05:00"
      },
      "sessionHandlerOptions": {
        "autoComplete": false,
        "messageWaitTimeout": "00:00:30",
        "maxAutoRenewDuration": "00:55:00",
        "maxConcurrentSessions": 16
      },
      "batchOptions": {
        "maxMessageCount": 1000,
        "operationTimeout": "00:01:00",
        "autoComplete": true
      }
    },
    "durableTask": {
      "storageProvider": {
        "connectionStringName": "AzureWebJobsStorage",
        "controlQueueVisibilityTimeout": "00:10:00",
        "workItemQueueVisibilityTimeout": "00:10:00"
      }
    }
  }
}
```

## ローカル起動時のみ有効になる設定
ローカルではhost.jsonの設定を上書きして動作する
環境変数の設定やCORSの回避などを行なっている
Valuesは定型なのでミスらないように

- 参考

https://learn.microsoft.com/ja-jp/azure/azure-functions/functions-run-local?tabs=macos%2Cisolated-process%2Cnode-v4%2Cpython-v2%2Cnon-http-trigger%2Ccontainer-apps&pivots=programming-language-javascript#local-settings

ローカル設定ファイル

https://learn.microsoft.com/ja-jp/azure/azure-functions/functions-develop-local


```jsonc:local.settings.json
{
  "IsEncrypted": false,
  "Values": {
    "FUNCTIONS_WORKER_RUNTIME": "node",
    "AzureWebJobsFeatureFlags": "EnableWorkerIndexing",
    "AzureWebJobsStorage": "UseDevelopmentStorage=true",
    "AzureWebJobsServiceBus": "XXX", // AzureのService busの本物の接続文字列を設定する
    "AzureFunctionsJobHost__logging__logLevel__Function": "Information",
    "AzureFunctionsJobHost__logging__logLevel__durableTask": "Information",
    "AzureFunctionsJobHost__logging__logLevel__Default": "Warning"
  },
  "Host": {
    "LocalHttpPort": 7071,
    "CORS": "*",
    "CORSCredentials": false
  }
}

```

# できていないこと
## ローカルからトリガーしてQueueトリガーを動かしたい
Azureの本物（AzureWebJobsServiceBus）を使えば動作させられるが、使わずにできないかどうか

- 参考
ローカル関数を実行する > HTTP トリガー以外

https://learn.microsoft.com/ja-jp/azure/azure-functions/functions-run-local?tabs=macos%2Cisolated-process%2Cnode-v4%2Cpython-v2%2Cnon-http-trigger%2Ccontainer-apps&pivots=programming-language-javascript#run-a-local-function

### 試したこと
REST Clientから送信して動かないか

結果：エラー

```local_trigger.http
POST http://localhost:7071/admin/functions/serviceBusTrigger HTTP/1.1
content-type: application/json

{
    "id": "testid",
    "action": "testaction"
}
```

```http
HTTP/1.1 202 Accepted
Content-Length: 0
Connection: close
Date: XXXXXX
Server: Kestrel
```

```log
The listener for function 'Functions.serviceBusTrigger' was unable to start.
The listener for function 'Functions.serviceBusTrigger' was unable to start. Microsoft.Azure.WebJobs.Extensions.ServiceBus: Value cannot be an empty string. (Parameter 'entityPath').
```