# Azure Functions

Load KeyEnv secrets in Azure Functions.

Source: https://keyenv.dev/docs/sdks/azure-functions/

Load secrets from KeyEnv in your Azure Functions.

## Prerequisites

- Azure account with a Function App
- [Azure CLI](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli) installed
- KeyEnv service token with read access

## Quick Sync

Sync secrets to Function App settings:

```bash
export KEYENV_TOKEN=your-service-token

FUNCTION_APP="my-function-app"
RESOURCE_GROUP="my-resource-group"

# Export and sync
keyenv export -p YOUR_PROJECT_ID -e production --format json | \
  jq -r '.secrets[] | "\(.key)=\(.value)"' | \
  xargs -I {} az functionapp config appsettings set \
    --name "$FUNCTION_APP" \
    --resource-group "$RESOURCE_GROUP" \
    --settings "{}"
```

## Sync Script

Create a reusable sync script:

```bash
#!/bin/bash
# sync-to-azure-functions.sh

set -euo pipefail

: "${KEYENV_TOKEN:?KEYENV_TOKEN is required}"

PROJECT_ID="${KEYENV_PROJECT:-YOUR_PROJECT_ID}"
ENVIRONMENT="${KEYENV_ENV:-production}"
FUNCTION_APP="${AZURE_FUNCTION_APP:-my-function-app}"
RESOURCE_GROUP="${AZURE_RESOURCE_GROUP:-my-resource-group}"

echo "Syncing KeyEnv ($ENVIRONMENT) to Azure Functions ($FUNCTION_APP)..."

# Export secrets and sync one by one
keyenv export -p "$PROJECT_ID" -e "$ENVIRONMENT" --format json | \
  jq -r '.secrets[] | @base64' | while read -r secret; do
    key=$(echo "$secret" | base64 -d | jq -r '.key')
    value=$(echo "$secret" | base64 -d | jq -r '.value')

    az functionapp config appsettings set \
      --name "$FUNCTION_APP" \
      --resource-group "$RESOURCE_GROUP" \
      --settings "$key=$value" \
      --output none

    echo "Set: $key"
done

echo "Done!"
```

## Using SDK at Runtime

For dynamic secret loading, use the Node.js or Python SDK in your function.

### Node.js Function

```typescript
// src/functions/httpTrigger.ts
import { app, HttpRequest, HttpResponseInit, InvocationContext } from "@azure/functions";
import { KeyEnv } from '@keyenv/node';

// Initialize once, outside the handler
const keyenv = new KeyEnv({
  token: process.env.KEYENV_TOKEN!,
  cacheTtl: 300, // Cache for 5 minutes
});

let secretsLoaded = false;

export async function httpTrigger(request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> {
  // Load secrets on first invocation
  if (!secretsLoaded) {
    await keyenv.loadEnv(process.env.KEYENV_PROJECT!, 'production');
    secretsLoaded = true;
  }

  // Use secrets
  const apiKey = process.env.API_KEY;

  return {
    body: JSON.stringify({ status: 'ok' }),
    headers: { 'Content-Type': 'application/json' },
  };
}

app.http('httpTrigger', {
  methods: ['GET', 'POST'],
  handler: httpTrigger,
});
```

### Python Function

```python
# function_app.py
import azure.functions as func
import os
from keyenv import KeyEnv

app = func.FunctionApp()

# Initialize client
keyenv = KeyEnv(
    token=os.environ["KEYENV_TOKEN"],
    cache_ttl=300,
)

secrets_loaded = False

@app.route(route="hello")
def hello(req: func.HttpRequest) -> func.HttpResponse:
    global secrets_loaded

    if not secrets_loaded:
        keyenv.load_env(os.environ["KEYENV_PROJECT"], "production")
        secrets_loaded = True

    api_key = os.environ.get("API_KEY")

    return func.HttpResponse(f"Hello!")
```

## CI/CD Integration

### GitHub Actions

```yaml
name: Deploy Azure Functions

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install KeyEnv CLI
        run: curl -fsSL https://keyenv.dev/install.sh | bash

      - name: Login to Azure
        uses: azure/login@v1
        with:
          creds: ${{ secrets.AZURE_CREDENTIALS }}

      - name: Sync secrets
        run: |
          export PATH="$HOME/.keyenv/bin:$PATH"

          keyenv export -p ${{ vars.KEYENV_PROJECT }} -e production --format json | \
            jq -r '.secrets[] | "\(.key)=\(.value)"' | while IFS='=' read -r key value; do
              az functionapp config appsettings set \
                --name ${{ vars.AZURE_FUNCTION_APP }} \
                --resource-group ${{ vars.AZURE_RESOURCE_GROUP }} \
                --settings "$key=$value" \
                --output none
            done
        env:
          KEYENV_TOKEN: ${{ secrets.KEYENV_TOKEN }}

      - name: Deploy Function App
        uses: Azure/functions-action@v1
        with:
          app-name: ${{ vars.AZURE_FUNCTION_APP }}
          package: .
```

### Azure DevOps Pipeline

```yaml
trigger:
  - main

pool:
  vmImage: 'ubuntu-latest'

steps:
  - script: curl -fsSL https://keyenv.dev/install.sh | bash
    displayName: 'Install KeyEnv CLI'

  - task: AzureCLI@2
    displayName: 'Sync secrets'
    inputs:
      azureSubscription: 'your-subscription'
      scriptType: 'bash'
      scriptLocation: 'inlineScript'
      inlineScript: |
        export PATH="$HOME/.keyenv/bin:$PATH"

        keyenv export -p $(KEYENV_PROJECT) -e production --format json | \
          jq -r '.secrets[] | "\(.key)=\(.value)"' | while IFS='=' read -r key value; do
            az functionapp config appsettings set \
              --name $(AZURE_FUNCTION_APP) \
              --resource-group $(AZURE_RESOURCE_GROUP) \
              --settings "$key=$value" \
              --output none
          done
    env:
      KEYENV_TOKEN: $(KEYENV_TOKEN)
```

## Using Key Vault (Recommended)

For production, store secrets in Azure Key Vault:

### 1. Sync to Key Vault

```bash
#!/bin/bash

VAULT_NAME="my-key-vault"

keyenv export -p YOUR_PROJECT_ID -e production --format json | \
  jq -r '.secrets[] | "\(.key)=\(.value)"' | while IFS='=' read -r key value; do
    # Key Vault doesn't allow underscores, replace with dashes
    vault_key=$(echo "$key" | tr '_' '-')

    az keyvault secret set \
      --vault-name "$VAULT_NAME" \
      --name "$vault_key" \
      --value "$value" \
      --output none

    echo "Set: $vault_key"
done
```

### 2. Reference in Function App

```bash
# Add Key Vault reference
az functionapp config appsettings set \
  --name my-function-app \
  --resource-group my-resource-group \
  --settings "DATABASE_URL=@Microsoft.KeyVault(VaultName=my-key-vault;SecretName=DATABASE-URL)"
```

## Deployment Slots

Sync different environments to different slots:

```bash
# Production slot
keyenv export -p YOUR_PROJECT_ID -e production --format json | \
  jq -r '.secrets[]' | sync_to_slot "production"

# Staging slot
keyenv export -p YOUR_PROJECT_ID -e staging --format json | \
  jq -r '.secrets[]' | sync_to_slot "staging"

# Helper function
sync_to_slot() {
  local slot=$1
  while IFS='=' read -r key value; do
    if [ "$slot" == "production" ]; then
      az functionapp config appsettings set \
        --name "$FUNCTION_APP" \
        --resource-group "$RESOURCE_GROUP" \
        --settings "$key=$value"
    else
      az functionapp config appsettings set \
        --name "$FUNCTION_APP" \
        --resource-group "$RESOURCE_GROUP" \
        --slot "$slot" \
        --settings "$key=$value"
    fi
  done
}
```

## Best Practices

1. **Use Key Vault for production** - Better security and secret rotation
2. **Enable managed identity** - Let Functions access Key Vault without credentials
3. **Use deployment slots** - Test with staging secrets before production
4. **Cache secrets** - Use SDK caching to reduce API calls

## Troubleshooting

### Settings Not Updating

Restart the Function App:

```bash
az functionapp restart \
  --name my-function-app \
  --resource-group my-resource-group
```

### Key Vault Access Denied

Grant the Function App access:

```bash
az keyvault set-policy \
  --name my-key-vault \
  --object-id $(az functionapp identity show --name my-function-app --resource-group my-resource-group --query principalId -o tsv) \
  --secret-permissions get list
```

### Environment Variables Not Loading

Check current settings:

```bash
az functionapp config appsettings list \
  --name my-function-app \
  --resource-group my-resource-group
```
