diff --git a/samples/ci-cd-api-validation/.devproxy/api-specs/api.contoso.com.json b/samples/ci-cd-api-validation/.devproxy/api-specs/api.contoso.com.json new file mode 100644 index 0000000..e7d152a --- /dev/null +++ b/samples/ci-cd-api-validation/.devproxy/api-specs/api.contoso.com.json @@ -0,0 +1,272 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "Contoso API", + "description": "Sample Contoso API for demonstrating permission validation in CI/CD pipelines", + "version": "v1.0" + }, + "servers": [ + { + "url": "https://api.contoso.com" + } + ], + "components": { + "securitySchemes": { + "OAuth2": { + "type": "oauth2", + "flows": { + "authorizationCode": { + "authorizationUrl": "https://login.microsoftonline.com/common/oauth2/authorize", + "tokenUrl": "https://login.microsoftonline.com/common/oauth2/token", + "scopes": { + "customer.read": "Grants access to read customer info", + "customer.readwrite": "Grants access to read and write customer info", + "order.read": "Grants access to read order info", + "order.readwrite": "Grants access to read and write order info" + } + } + } + } + }, + "schemas": { + "Customer": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "email": { + "type": "string" + } + } + }, + "Order": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "customerId": { + "type": "string" + }, + "total": { + "type": "number" + }, + "status": { + "type": "string" + } + } + } + } + }, + "paths": { + "/customers": { + "get": { + "summary": "Get all customers", + "operationId": "getCustomers", + "security": [ + { + "OAuth2": [ + "customer.read" + ] + }, + { + "OAuth2": [ + "customer.readwrite" + ] + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Customer" + } + } + } + } + } + } + }, + "post": { + "summary": "Create a customer", + "operationId": "createCustomer", + "security": [ + { + "OAuth2": [ + "customer.readwrite" + ] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Customer" + } + } + } + }, + "responses": { + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Customer" + } + } + } + } + } + } + }, + "/customers/{customer-id}": { + "get": { + "summary": "Get customer by ID", + "operationId": "getCustomerById", + "security": [ + { + "OAuth2": [ + "customer.read" + ] + }, + { + "OAuth2": [ + "customer.readwrite" + ] + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Customer" + } + } + } + } + } + }, + "patch": { + "summary": "Update customer by ID", + "operationId": "updateCustomerById", + "security": [ + { + "OAuth2": [ + "customer.readwrite" + ] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Customer" + } + } + } + }, + "responses": { + "204": { + "description": "No Content" + } + } + }, + "parameters": [ + { + "name": "customer-id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] + }, + "/orders": { + "get": { + "summary": "Get all orders", + "operationId": "getOrders", + "security": [ + { + "OAuth2": [ + "order.read" + ] + }, + { + "OAuth2": [ + "order.readwrite" + ] + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Order" + } + } + } + } + } + } + } + }, + "/orders/{order-id}": { + "get": { + "summary": "Get order by ID", + "operationId": "getOrderById", + "security": [ + { + "OAuth2": [ + "order.read" + ] + }, + { + "OAuth2": [ + "order.readwrite" + ] + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Order" + } + } + } + } + } + }, + "parameters": [ + { + "name": "order-id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] + } + } +} diff --git a/samples/ci-cd-api-validation/.devproxy/devproxyrc.json b/samples/ci-cd-api-validation/.devproxy/devproxyrc.json new file mode 100644 index 0000000..0cfb396 --- /dev/null +++ b/samples/ci-cd-api-validation/.devproxy/devproxyrc.json @@ -0,0 +1,51 @@ +{ + "$schema": "https://raw.githubusercontent.com/dotnet/dev-proxy/main/schemas/v2.0.0/rc.schema.json", + "plugins": [ + { + "name": "ApiCenterOnboardingPlugin", + "enabled": true, + "pluginPath": "~appFolder/plugins/DevProxy.Plugins.dll", + "configSection": "apiCenterOnboardingPlugin" + }, + { + "name": "MinimalPermissionsPlugin", + "enabled": true, + "pluginPath": "~appFolder/plugins/DevProxy.Plugins.dll", + "configSection": "minimalPermissionsPlugin" + }, + { + "name": "OpenApiSpecGeneratorPlugin", + "enabled": true, + "pluginPath": "~appFolder/plugins/DevProxy.Plugins.dll" + }, + { + "name": "JsonReporter", + "enabled": true, + "pluginPath": "~appFolder/plugins/DevProxy.Plugins.dll" + }, + { + "name": "MarkdownReporter", + "enabled": true, + "pluginPath": "~appFolder/plugins/DevProxy.Plugins.dll" + }, + { + "name": "PlainTextReporter", + "enabled": true, + "pluginPath": "~appFolder/plugins/DevProxy.Plugins.dll" + } + ], + "urlsToWatch": [ + "https://api.contoso.com/*" + ], + "apiCenterOnboardingPlugin": { + "$schema": "https://raw.githubusercontent.com/dotnet/dev-proxy/main/schemas/v2.0.0/apicenteronboardingplugin.schema.json", + "subscriptionId": "@AZURE_SUBSCRIPTION_ID", + "resourceGroupName": "@AZURE_RESOURCE_GROUP_NAME", + "serviceName": "@AZURE_APIC_INSTANCE_NAME", + "createApicEntryForNewApis": false + }, + "minimalPermissionsPlugin": { + "$schema": "https://raw.githubusercontent.com/dotnet/dev-proxy/main/schemas/v2.0.0/minimalpermissionsplugin.schema.json", + "apiSpecsFolderPath": "./api-specs" + } +} diff --git a/samples/ci-cd-api-validation/.devproxy/openapi-generation.json b/samples/ci-cd-api-validation/.devproxy/openapi-generation.json new file mode 100644 index 0000000..1e6d074 --- /dev/null +++ b/samples/ci-cd-api-validation/.devproxy/openapi-generation.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://raw.githubusercontent.com/dotnet/dev-proxy/main/schemas/v2.0.0/rc.schema.json", + "plugins": [ + { + "name": "OpenApiSpecGeneratorPlugin", + "enabled": true, + "pluginPath": "~appFolder/plugins/DevProxy.Plugins.dll" + } + ], + "urlsToWatch": [ + "https://api.contoso.com/*" + ] +} diff --git a/samples/ci-cd-api-validation/.devproxy/permission-validation.json b/samples/ci-cd-api-validation/.devproxy/permission-validation.json new file mode 100644 index 0000000..3a76050 --- /dev/null +++ b/samples/ci-cd-api-validation/.devproxy/permission-validation.json @@ -0,0 +1,28 @@ +{ + "$schema": "https://raw.githubusercontent.com/dotnet/dev-proxy/main/schemas/v2.0.0/rc.schema.json", + "plugins": [ + { + "name": "MinimalPermissionsPlugin", + "enabled": true, + "pluginPath": "~appFolder/plugins/DevProxy.Plugins.dll", + "configSection": "minimalPermissionsPlugin" + }, + { + "name": "JsonReporter", + "enabled": true, + "pluginPath": "~appFolder/plugins/DevProxy.Plugins.dll" + }, + { + "name": "MarkdownReporter", + "enabled": true, + "pluginPath": "~appFolder/plugins/DevProxy.Plugins.dll" + } + ], + "urlsToWatch": [ + "https://api.contoso.com/*" + ], + "minimalPermissionsPlugin": { + "$schema": "https://raw.githubusercontent.com/dotnet/dev-proxy/main/schemas/v2.0.0/minimalpermissionsplugin.schema.json", + "apiSpecsFolderPath": "./api-specs" + } +} diff --git a/samples/ci-cd-api-validation/.devproxy/shadow-api-detection.json b/samples/ci-cd-api-validation/.devproxy/shadow-api-detection.json new file mode 100644 index 0000000..edfd0ee --- /dev/null +++ b/samples/ci-cd-api-validation/.devproxy/shadow-api-detection.json @@ -0,0 +1,31 @@ +{ + "$schema": "https://raw.githubusercontent.com/dotnet/dev-proxy/main/schemas/v2.0.0/rc.schema.json", + "plugins": [ + { + "name": "ApiCenterOnboardingPlugin", + "enabled": true, + "pluginPath": "~appFolder/plugins/DevProxy.Plugins.dll", + "configSection": "apiCenterOnboardingPlugin" + }, + { + "name": "JsonReporter", + "enabled": true, + "pluginPath": "~appFolder/plugins/DevProxy.Plugins.dll" + }, + { + "name": "MarkdownReporter", + "enabled": true, + "pluginPath": "~appFolder/plugins/DevProxy.Plugins.dll" + } + ], + "urlsToWatch": [ + "https://api.contoso.com/*" + ], + "apiCenterOnboardingPlugin": { + "$schema": "https://raw.githubusercontent.com/dotnet/dev-proxy/main/schemas/v2.0.0/apicenteronboardingplugin.schema.json", + "subscriptionId": "@AZURE_SUBSCRIPTION_ID", + "resourceGroupName": "@AZURE_RESOURCE_GROUP_NAME", + "serviceName": "@AZURE_APIC_INSTANCE_NAME", + "createApicEntryForNewApis": false + } +} diff --git a/samples/ci-cd-api-validation/.github/workflows/api-validation.yml b/samples/ci-cd-api-validation/.github/workflows/api-validation.yml new file mode 100644 index 0000000..3dc18e9 --- /dev/null +++ b/samples/ci-cd-api-validation/.github/workflows/api-validation.yml @@ -0,0 +1,143 @@ +name: API validation + +on: + push: + branches: + - main + pull_request: + branches: + - main + workflow_dispatch: + +jobs: + shadow-api-detection: + name: Shadow API detection + timeout-minutes: 30 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Dev Proxy + uses: dev-proxy-tools/actions/setup@v1 + + - name: Start Dev Proxy + uses: dev-proxy-tools/actions/start@v1 + with: + config-file: .devproxy/shadow-api-detection.json + auto-record: true + + # Replace with your test commands + - name: Run API tests + run: | + # Example: make requests through Dev Proxy + curl -ikx http://127.0.0.1:8000 https://api.contoso.com/customers + curl -ikx http://127.0.0.1:8000 https://api.contoso.com/orders + env: + AZURE_SUBSCRIPTION_ID: ${{ vars.AZURE_SUBSCRIPTION_ID }} + AZURE_RESOURCE_GROUP_NAME: ${{ vars.AZURE_RESOURCE_GROUP_NAME }} + AZURE_APIC_INSTANCE_NAME: ${{ vars.AZURE_APIC_INSTANCE_NAME }} + + - name: Stop Dev Proxy + uses: dev-proxy-tools/actions/stop@v1 + + - name: Upload reports + uses: actions/upload-artifact@v4 + with: + name: shadow-api-reports + path: ./*Reporter* + + - name: Write summary + run: | + if [ -f "ApiCenterOnboardingPlugin_MarkdownReporter.md" ]; then + cat ApiCenterOnboardingPlugin_MarkdownReporter.md >> $GITHUB_STEP_SUMMARY + fi + + permission-validation: + name: Permission validation + timeout-minutes: 30 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Dev Proxy + uses: dev-proxy-tools/actions/setup@v1 + + - name: Start Dev Proxy + uses: dev-proxy-tools/actions/start@v1 + with: + config-file: .devproxy/permission-validation.json + auto-record: true + + # Replace with your test commands + - name: Run API tests + run: | + # Example: make requests through Dev Proxy + curl -ikx http://127.0.0.1:8000 https://api.contoso.com/customers + curl -ikx http://127.0.0.1:8000 https://api.contoso.com/customers/1 + + - name: Stop Dev Proxy + uses: dev-proxy-tools/actions/stop@v1 + + - name: Upload reports + uses: actions/upload-artifact@v4 + with: + name: permission-reports + path: ./*Reporter* + + - name: Write summary + run: | + if [ -f "MinimalPermissionsPlugin_MarkdownReporter.md" ]; then + cat MinimalPermissionsPlugin_MarkdownReporter.md >> $GITHUB_STEP_SUMMARY + fi + + openapi-generation: + name: Generate OpenAPI spec + timeout-minutes: 30 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Dev Proxy + uses: dev-proxy-tools/actions/setup@v1 + + - name: Start Dev Proxy + uses: dev-proxy-tools/actions/start@v1 + with: + config-file: .devproxy/openapi-generation.json + auto-record: true + + # Replace with your test commands + - name: Run API tests + run: | + # Example: make requests through Dev Proxy + curl -ikx http://127.0.0.1:8000 https://api.contoso.com/customers + curl -ikx http://127.0.0.1:8000 https://api.contoso.com/customers/1 + curl -ikx http://127.0.0.1:8000 https://api.contoso.com/orders + curl -ikx http://127.0.0.1:8000 https://api.contoso.com/orders/1 + + - name: Stop Dev Proxy + uses: dev-proxy-tools/actions/stop@v1 + + - name: Collect OpenAPI specs + run: | + mkdir -p generated-specs + for file in *.json; do + if [ -f "$file" ]; then + cp "$file" generated-specs/ + fi + done + + - name: Upload OpenAPI spec + uses: actions/upload-artifact@v4 + with: + name: openapi-spec + path: generated-specs/ + + # Optional: Commit generated spec to repository + # - name: Commit OpenAPI spec + # run: | + # git config user.name "GitHub Actions" + # git config user.email "actions@github.com" + # git add ./*.json + # git commit -m "chore: update generated OpenAPI spec" || echo "No changes to commit" + # git push diff --git a/samples/ci-cd-api-validation/README.md b/samples/ci-cd-api-validation/README.md new file mode 100644 index 0000000..3099e9a --- /dev/null +++ b/samples/ci-cd-api-validation/README.md @@ -0,0 +1,167 @@ +# Integrate Dev Proxy in CI/CD pipelines for API validation + +## Summary + +This sample demonstrates how to integrate Dev Proxy in CI/CD pipelines for API validation. It includes configurations for shadow API detection, permission validation, and OpenAPI spec generation, along with example GitHub Actions workflows and Azure Pipelines YAML files. + +![Dev Proxy CI/CD API validation showing GitHub Actions workflow results](./assets/screenshot.png) + +## Compatibility + +![Dev Proxy v2.0.0](https://aka.ms/devproxy/badge/v2.0.0) + +## Contributors + +- [Waldek Mastykarz](https://github.com/waldekmastykarz) + +## Version history + +Version|Date|Comments +-------|----|-------- +1.0|January 10, 2026|Initial release + +## Prerequisites + +- [Azure API Center](https://learn.microsoft.com/azure/api-center/) (for shadow API detection) +- Azure CLI or Azure PowerShell authenticated for API Center access +- CI/CD platform (GitHub Actions or Azure Pipelines) + +## Minimal path to awesome + +- Clone this repository (or [download this solution as a .ZIP file](https://pnp.github.io/download-partial/?url=https://github.com/pnp/proxy-samples/tree/main/samples/ci-cd-api-validation) then unzip it) +- Update the configuration files with your API URLs: + - In the configuration files in `.devproxy/` folder, update the `urlsToWatch` property to match your API endpoints +- For shadow API detection with Azure API Center: + - Create an Azure API Center instance + - Set up the required environment variables/secrets (see CI/CD configuration sections below) +- For permission validation: + - Add your OpenAPI specs to the `.devproxy/api-specs` folder +- Copy the workflow/pipeline files to your repository and customize as needed + +## Features + +Using this sample you can use the Dev Proxy to: + +1. **Detect shadow APIs** - Find APIs that your application uses but aren't registered in Azure API Center +2. **Validate API permissions** - Check if your app uses minimal permissions to call APIs +3. **Generate OpenAPI specs** - Automatically generate OpenAPI specifications from recorded API requests + +## Configuration Files + +This sample includes several configuration files for different validation scenarios: + +| File | Description | +|------|-------------| +| `.devproxy/devproxyrc.json` | Combined configuration with all plugins enabled | +| `.devproxy/shadow-api-detection.json` | Configuration for detecting unregistered APIs using Azure API Center | +| `.devproxy/permission-validation.json` | Configuration for validating API permissions | +| `.devproxy/openapi-generation.json` | Configuration for generating OpenAPI specifications | + +## GitHub Actions + +The sample includes a GitHub Actions workflow (`.github/workflows/api-validation.yml`) that runs three parallel jobs: + +1. **Shadow API detection** - Detects unregistered APIs +2. **Permission validation** - Validates API permissions +3. **OpenAPI generation** - Generates OpenAPI specs from requests + +### Required secrets and variables + +To use the workflow with Azure API Center, configure the following: + +| Type | Name | Description | +|------|------|-------------| +| Variable | `AZURE_SUBSCRIPTION_ID` | Azure subscription ID containing API Center | +| Variable | `AZURE_RESOURCE_GROUP_NAME` | Resource group name containing API Center | +| Variable | `AZURE_APIC_INSTANCE_NAME` | API Center instance name | + +See [Creating configuration variables for a repository](https://docs.github.com/actions/learn-github-actions/variables#creating-configuration-variables-for-a-repository) for instructions on setting up variables. + +## Azure Pipelines + +The sample includes an Azure Pipelines configuration (`azure-pipelines.yml`) with three parallel stages: + +1. **Shadow API detection** - Detects unregistered APIs +2. **Permission validation** - Validates API permissions +3. **OpenAPI generation** - Generates OpenAPI specs from requests + +### Required variables + +To use the pipeline with Azure API Center, configure the following pipeline variables: + +| Name | Description | +|------|-------------| +| `AZURE_SUBSCRIPTION_ID` | Azure subscription ID containing API Center | +| `AZURE_RESOURCE_GROUP_NAME` | Resource group name containing API Center | +| `AZURE_APIC_INSTANCE_NAME` | API Center instance name | + +See [Set variables in a pipeline](https://learn.microsoft.com/azure/devops/pipelines/process/variables?view=azure-devops&tabs=yaml%2Cbash) for instructions on setting up variables. + +## Local testing + +To test the configurations locally: + +1. Start Dev Proxy with the desired configuration: + + ```bash + devproxy --config-file .devproxy/shadow-api-detection.json --record + ``` + +2. Make API requests using curl through Dev Proxy: + + ```bash + curl -ikx http://127.0.0.1:8000 https://api.contoso.com/customers + ``` + +3. Stop recording by pressing S +4. Stop Dev Proxy by pressing Ctrl + C +5. Review the generated reports + +## Customization + +### Adding your APIs + +1. Update the `urlsToWatch` in each configuration file to match your API endpoints +2. If using permission validation, add your OpenAPI specs to `.devproxy/api-specs` + +### Integrating with your tests + +Replace the example curl commands in the workflow files and `run.sh` script with your actual test commands: + +```yaml +- name: Run API tests + run: | + npm test + # or + dotnet test + # or + python -m pytest +``` + +### Failing the pipeline on issues + +To fail the pipeline when shadow APIs or permission issues are found, add validation logic after Dev Proxy completes: + +```bash +# Check for shadow APIs +if grep -q "New APIs" ApiCenterOnboardingPlugin_MarkdownReporter.md; then + echo "Shadow APIs detected!" + exit 1 +fi +``` + +## Help + +We do not support samples, but this community is always willing to help, and we want to improve these samples. We use GitHub to track issues, which makes it easy for community members to volunteer their time and help resolve issues. + +You can try looking at [issues related to this sample](https://github.com/pnp/proxy-samples/issues?q=label%3A%22sample%3A%20ci-cd-api-validation%22) to see if anybody else is having the same issues. + +If you encounter any issues using this sample, [create a new issue](https://github.com/pnp/proxy-samples/issues/new). + +Finally, if you have an idea for improvement, [make a suggestion](https://github.com/pnp/proxy-samples/issues/new). + +## Disclaimer + +**THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.** + +![](https://m365-visitor-stats.azurewebsites.net/SamplesGallery/pnp-devproxy-ci-cd-api-validation) diff --git a/samples/ci-cd-api-validation/assets/README.md b/samples/ci-cd-api-validation/assets/README.md new file mode 100644 index 0000000..d9d4b9a --- /dev/null +++ b/samples/ci-cd-api-validation/assets/README.md @@ -0,0 +1,12 @@ +# Assets + +This folder contains assets for the ci-cd-api-validation sample. + +## screenshot.png + +A screenshot showing the Dev Proxy CI/CD API validation terminal output, including: +- Shadow API detection results +- Registered vs. unregistered API analysis +- Generated report file names + +The screenshot demonstrates the output when running Dev Proxy with the `ApiCenterOnboardingPlugin` for shadow API detection in a CI/CD pipeline. diff --git a/samples/ci-cd-api-validation/assets/sample.json b/samples/ci-cd-api-validation/assets/sample.json new file mode 100644 index 0000000..c670721 --- /dev/null +++ b/samples/ci-cd-api-validation/assets/sample.json @@ -0,0 +1,87 @@ +[ + { + "name": "pnp-devproxy-ci-cd-api-validation", + "source": "pnp", + "title": "Integrate Dev Proxy in CI/CD pipelines for API validation", + "shortDescription": "This sample demonstrates how to integrate Dev Proxy in CI/CD pipelines for API validation. It includes configurations for shadow API detection, permission validation, and OpenAPI spec generation, along with example GitHub Actions workflows and Azure Pipelines YAML files.", + "url": "https://github.com/pnp/proxy-samples/tree/main/samples/ci-cd-api-validation", + "downloadUrl": "https://pnp.github.io/download-partial/?url=https://github.com/pnp/proxy-samples/tree/main/samples/ci-cd-api-validation", + "longDescription": [ + "This sample demonstrates how to integrate Dev Proxy in CI/CD pipelines for API validation. It includes configurations for shadow API detection, permission validation, and OpenAPI spec generation, along with example GitHub Actions workflows and Azure Pipelines YAML files." + ], + "creationDateTime": "2026-01-10", + "updateDateTime": "2026-01-10", + "products": [ + "Dev Proxy" + ], + "metadata": [ + { + "key": "SAMPLE ID", + "value": "ci-cd-api-validation" + }, + { + "key": "PRESET", + "value": "No" + }, + { + "key": "MOCKS", + "value": "No" + }, + { + "key": "PLUGIN", + "value": "No" + }, + { + "key": "PROXY VERSION", + "value": "v2.0.0" + } + ], + "thumbnails": [ + { + "type": "image", + "order": 100, + "url": "https://github.com/pnp/proxy-samples/raw/main/samples/ci-cd-api-validation/assets/screenshot.png", + "alt": "Dev Proxy CI/CD API validation showing GitHub Actions workflow results" + } + ], + "authors": [ + { + "gitHubAccount": "waldekmastykarz", + "pictureUrl": "https://github.com/waldekmastykarz.png", + "name": "Waldek Mastykarz" + } + ], + "references": [ + { + "name": "Get started with the Dev Proxy", + "description": "The tutorial will introduce you to the Dev Proxy and show you how to use its features.", + "url": "https://learn.microsoft.com/microsoft-cloud/dev/dev-proxy/get-started" + }, + { + "name": "Use Dev Proxy in CI/CD scenarios", + "description": "Learn how to use Dev Proxy with GitHub Actions and Azure Pipelines.", + "url": "https://learn.microsoft.com/microsoft-cloud/dev/dev-proxy/how-to/use-dev-proxy-in-ci-cd-overview" + }, + { + "name": "Use Dev Proxy with GitHub Actions", + "description": "Instructions on how to integrate Dev Proxy into your GitHub Actions workflows.", + "url": "https://learn.microsoft.com/microsoft-cloud/dev/dev-proxy/how-to/use-dev-proxy-with-github-actions" + }, + { + "name": "Use Dev Proxy with Azure Pipelines", + "description": "Instructions on how to use Dev Proxy with Azure Pipelines.", + "url": "https://learn.microsoft.com/microsoft-cloud/dev/dev-proxy/how-to/use-dev-proxy-with-azure-pipelines" + }, + { + "name": "ApiCenterOnboardingPlugin reference", + "description": "Learn more about the ApiCenterOnboardingPlugin for shadow API detection.", + "url": "https://learn.microsoft.com/microsoft-cloud/dev/dev-proxy/technical-reference/apicenteronboardingplugin" + }, + { + "name": "MinimalPermissionsPlugin reference", + "description": "Learn more about the MinimalPermissionsPlugin for permission validation.", + "url": "https://learn.microsoft.com/microsoft-cloud/dev/dev-proxy/technical-reference/minimalpermissionsplugin" + } + ] + } +] diff --git a/samples/ci-cd-api-validation/assets/screenshot.png b/samples/ci-cd-api-validation/assets/screenshot.png new file mode 100644 index 0000000..9010ede Binary files /dev/null and b/samples/ci-cd-api-validation/assets/screenshot.png differ diff --git a/samples/ci-cd-api-validation/azure-pipelines.yml b/samples/ci-cd-api-validation/azure-pipelines.yml new file mode 100644 index 0000000..7e1e487 --- /dev/null +++ b/samples/ci-cd-api-validation/azure-pipelines.yml @@ -0,0 +1,147 @@ +trigger: +- main +- dev + +pr: +- main + +pool: + vmImage: ubuntu-latest + +variables: +- name: LOG_FILE + value: devproxy.log +- name: DEV_PROXY_VERSION + value: v2.0.0 +- name: DEV_PROXY_CACHE_RESTORED + value: 'false' + +stages: +- stage: ShadowApiDetection + displayName: 'Shadow API detection' + jobs: + - job: DetectShadowApis + displayName: 'Check for shadow APIs' + steps: + - task: Cache@2 + inputs: + key: '"dev-proxy-$(DEV_PROXY_VERSION)"' + path: ./devproxy + cacheHitVar: DEV_PROXY_CACHE_RESTORED + displayName: 'Cache Dev Proxy' + + - script: bash -c "$(curl -sL https://aka.ms/devproxy/setup.sh)" -- $DEV_PROXY_VERSION + displayName: 'Install Dev Proxy' + condition: ne(variables.DEV_PROXY_CACHE_RESTORED, 'true') + + - script: bash ./run.sh .devproxy/shadow-api-detection.json + displayName: 'Run API validation' + env: + AZURE_SUBSCRIPTION_ID: $(AZURE_SUBSCRIPTION_ID) + AZURE_RESOURCE_GROUP_NAME: $(AZURE_RESOURCE_GROUP_NAME) + AZURE_APIC_INSTANCE_NAME: $(AZURE_APIC_INSTANCE_NAME) + + - task: PublishPipelineArtifact@1 + displayName: 'Upload Dev Proxy logs' + inputs: + targetPath: $(LOG_FILE) + artifact: shadow-api-logs + + - script: | + mkdir -p $(Build.ArtifactStagingDirectory)/ShadowApiReports + for file in *Reporter*; do + if [ -f "$file" ]; then + cp "$file" $(Build.ArtifactStagingDirectory)/ShadowApiReports + fi + done + displayName: 'Copy reports to artifact directory' + + - task: PublishPipelineArtifact@1 + displayName: 'Upload shadow API reports' + inputs: + targetPath: '$(Build.ArtifactStagingDirectory)/ShadowApiReports' + artifact: 'ShadowApiReports' + +- stage: PermissionValidation + displayName: 'Permission validation' + dependsOn: [] + jobs: + - job: ValidatePermissions + displayName: 'Validate API permissions' + steps: + - task: Cache@2 + inputs: + key: '"dev-proxy-$(DEV_PROXY_VERSION)"' + path: ./devproxy + cacheHitVar: DEV_PROXY_CACHE_RESTORED + displayName: 'Cache Dev Proxy' + + - script: bash -c "$(curl -sL https://aka.ms/devproxy/setup.sh)" -- $DEV_PROXY_VERSION + displayName: 'Install Dev Proxy' + condition: ne(variables.DEV_PROXY_CACHE_RESTORED, 'true') + + - script: bash ./run.sh .devproxy/permission-validation.json + displayName: 'Run permission validation' + + - task: PublishPipelineArtifact@1 + displayName: 'Upload Dev Proxy logs' + inputs: + targetPath: $(LOG_FILE) + artifact: permission-logs + + - script: | + mkdir -p $(Build.ArtifactStagingDirectory)/PermissionReports + for file in *Reporter*; do + if [ -f "$file" ]; then + cp "$file" $(Build.ArtifactStagingDirectory)/PermissionReports + fi + done + displayName: 'Copy reports to artifact directory' + + - task: PublishPipelineArtifact@1 + displayName: 'Upload permission reports' + inputs: + targetPath: '$(Build.ArtifactStagingDirectory)/PermissionReports' + artifact: 'PermissionReports' + +- stage: OpenApiGeneration + displayName: 'OpenAPI spec generation' + dependsOn: [] + jobs: + - job: GenerateOpenApiSpec + displayName: 'Generate OpenAPI spec' + steps: + - task: Cache@2 + inputs: + key: '"dev-proxy-$(DEV_PROXY_VERSION)"' + path: ./devproxy + cacheHitVar: DEV_PROXY_CACHE_RESTORED + displayName: 'Cache Dev Proxy' + + - script: bash -c "$(curl -sL https://aka.ms/devproxy/setup.sh)" -- $DEV_PROXY_VERSION + displayName: 'Install Dev Proxy' + condition: ne(variables.DEV_PROXY_CACHE_RESTORED, 'true') + + - script: bash ./run.sh .devproxy/openapi-generation.json + displayName: 'Run OpenAPI generation' + + - task: PublishPipelineArtifact@1 + displayName: 'Upload Dev Proxy logs' + inputs: + targetPath: $(LOG_FILE) + artifact: openapi-logs + + - script: | + mkdir -p $(Build.ArtifactStagingDirectory)/OpenApiSpecs + for file in *.json; do + if [ -f "$file" ]; then + cp "$file" $(Build.ArtifactStagingDirectory)/OpenApiSpecs + fi + done + displayName: 'Copy OpenAPI specs to artifact directory' + + - task: PublishPipelineArtifact@1 + displayName: 'Upload generated OpenAPI specs' + inputs: + targetPath: '$(Build.ArtifactStagingDirectory)/OpenApiSpecs' + artifact: 'OpenApiSpecs' diff --git a/samples/ci-cd-api-validation/run.sh b/samples/ci-cd-api-validation/run.sh new file mode 100644 index 0000000..6be5f19 --- /dev/null +++ b/samples/ci-cd-api-validation/run.sh @@ -0,0 +1,95 @@ +#!/bin/bash + +# enable job control so that we can send SIGINT to Dev Proxy +set -m + +log_file=devproxy.log +config_file=${1:-.devproxy/devproxyrc.json} +start_time=$SECONDS + +echo "Using config file: $config_file" + +echo "Ensuring Dev Proxy rootCert folder..." +mkdir -p ~/.config/dev-proxy/rootCert + +echo "Starting Dev Proxy..." + +# start Dev Proxy in the background +# log Dev Proxy output to the log file +# log stdout and stderr to the file +./devproxy/devproxy --config-file "$config_file" --record > $log_file 2>&1 & + +proxy_pid=$! + +echo "Waiting for Dev Proxy to start..." +while true; do + if grep -q "Listening on 127.0.0.1:8000" $log_file; then + break + fi + if grep -q "Recording" $log_file; then + break + fi + # timeout after 60 seconds + if [ $((SECONDS - start_time)) -gt 60 ]; then + echo "Timeout waiting for Dev Proxy to start" + cat $log_file + exit 1 + fi + sleep 1 +done + +echo "Dev Proxy started successfully!" + +# export the Dev Proxy's Root Certificate +echo "Exporting the Dev Proxy's Root Certificate..." +openssl pkcs12 -in ~/.config/dev-proxy/rootCert.pfx -clcerts -nokeys -out dev-proxy-ca.crt -passin pass:"" + +# install root certificate +echo "Installing the Dev Proxy's Root Certificate..." +sudo cp dev-proxy-ca.crt /usr/local/share/ca-certificates/ + +# update CA certificates +echo "Updating the CA certificates..." +sudo update-ca-certificates + +# set proxy environment variables +export http_proxy=http://127.0.0.1:8000 +export https_proxy=http://127.0.0.1:8000 + +echo "Running API tests..." + +# Add your test commands here +# Example: +# npm test +# dotnet test +# python -m pytest + +# For demonstration, making sample curl requests +curl -ikx http://127.0.0.1:8000 https://api.contoso.com/customers || true +curl -ikx http://127.0.0.1:8000 https://api.contoso.com/customers/1 || true +curl -ikx http://127.0.0.1:8000 https://api.contoso.com/orders || true +curl -ikx http://127.0.0.1:8000 https://api.contoso.com/orders/1 || true + +# stop Dev Proxy +echo "Stopping Dev Proxy..." +curl -s -X POST http://localhost:8897/proxy/stop || kill -INT $proxy_pid + +stop_time=$SECONDS +echo "Waiting for Dev Proxy to complete..." +while true; do + if grep -q -e "DONE" -e "No requests to process" -e "An error occurred in a plugin" $log_file; then + break + fi + # timeout after 60 seconds + if [ $((SECONDS - stop_time)) -gt 60 ]; then + echo "Timeout waiting for Dev Proxy to complete" + cat $log_file + exit 1 + fi + sleep 1 +done + +echo "Dev Proxy completed!" +echo "" +echo "Generated reports:" +ls -la *Reporter* 2>/dev/null || echo "No reports generated"