@polymorphism-tech/morph-spec 4.3.4 → 4.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.morph/.morphversion +5 -0
- package/.morph/config/agents.json +948 -0
- package/.morph/config/config.json +9 -9
- package/.morph/project/context/README.md +17 -0
- package/.morph/project/context/detection-log.md +16 -0
- package/.morph/project/standards/inferred.md +59 -0
- package/.morph/standards/ai-agents/blazor-ui.md +364 -0
- package/.morph/standards/ai-agents/production.md +415 -0
- package/.morph/standards/ai-agents/setup.md +418 -0
- package/.morph/standards/ai-agents/team-orchestration.md +479 -0
- package/.morph/standards/ai-agents/workflows.md +354 -0
- package/.morph/standards/architecture/ddd/aggregates.md +120 -0
- package/.morph/standards/architecture/ddd/entities.md +99 -0
- package/.morph/standards/architecture/ddd/value-objects.md +124 -0
- package/.morph/standards/backend/api/minimal-api.md +494 -0
- package/.morph/standards/backend/api/rest.md +492 -0
- package/.morph/standards/backend/api/validation.md +88 -0
- package/.morph/standards/backend/authentication/passkeys.md +428 -0
- package/.morph/standards/backend/database/ef-core.md +199 -0
- package/.morph/standards/backend/database/migrations.md +393 -0
- package/.morph/standards/backend/database/postgresql/database.md +352 -0
- package/.morph/standards/backend/database/repository-patterns.md +528 -0
- package/.morph/standards/backend/database/vector-search-rag.md +541 -0
- package/.morph/standards/backend/dotnet/async.md +366 -0
- package/.morph/standards/backend/dotnet/core.md +117 -0
- package/.morph/standards/backend/dotnet/di.md +439 -0
- package/.morph/standards/backend/dotnet/program-cs-checklist.md +92 -0
- package/.morph/standards/backend/integrations/asaas/asaas-api.md +216 -0
- package/.morph/standards/backend/integrations/clerk/clerk-auth.md +290 -0
- package/.morph/standards/backend/integrations/hangfire/hangfire-jobs.md +350 -0
- package/.morph/standards/backend/integrations/resend/resend-email.md +385 -0
- package/.morph/standards/context/analytics.md +96 -0
- package/.morph/standards/context/bundles.md +110 -0
- package/.morph/standards/context/priming.md +78 -0
- package/.morph/standards/core/architecture.md +185 -0
- package/.morph/standards/core/coding.md +214 -0
- package/.morph/standards/core/git-branching-strategy.md +403 -0
- package/.morph/standards/core/git.md +185 -0
- package/.morph/standards/core/testing.md +295 -0
- package/.morph/standards/data/nosql/blob-storage.md +102 -0
- package/.morph/standards/data/nosql/cache/redis.md +97 -0
- package/.morph/standards/data/nosql/cosmos-db.md +118 -0
- package/.morph/standards/data/vector-search/azure-ai-search.md +121 -0
- package/.morph/standards/data/vector-search/rag-chunking.md +104 -0
- package/.morph/standards/frontend/blazor/design-checklist.md +222 -0
- package/.morph/standards/frontend/blazor/fluent-ui-setup.md +595 -0
- package/.morph/standards/frontend/blazor/fluent-ui.md +137 -0
- package/.morph/standards/frontend/blazor/html-conversion.md +184 -0
- package/.morph/standards/frontend/blazor/lifecycle.md +195 -0
- package/.morph/standards/frontend/blazor/pitfalls.md +198 -0
- package/.morph/standards/frontend/blazor/state.md +191 -0
- package/.morph/standards/frontend/design-system/animations.md +151 -0
- package/.morph/standards/frontend/design-system/naming.md +64 -0
- package/.morph/standards/frontend/nextjs/nextjs-patterns.md +198 -0
- package/.morph/standards/infrastructure/azure/azure.md +624 -0
- package/.morph/standards/infrastructure/azure/bicep/bicep-patterns.md +422 -0
- package/.morph/standards/infrastructure/azure/devops/azure-devops-setup.md +516 -0
- package/.morph/standards/infrastructure/azure/devops/local-development.md +520 -0
- package/.morph/standards/infrastructure/azure/services/functions.md +486 -0
- package/.morph/standards/infrastructure/azure/services/service-bus.md +459 -0
- package/.morph/standards/infrastructure/azure/services/storage.md +407 -0
- package/.morph/standards/infrastructure/docker/easypanel-deploy.md +196 -0
- package/.morph/standards/infrastructure/supabase/mcp-setup.md +252 -0
- package/.morph/standards/infrastructure/supabase/supabase-auth.md +176 -0
- package/.morph/standards/infrastructure/supabase/supabase-pgvector.md +169 -0
- package/.morph/standards/infrastructure/supabase/supabase-rls.md +184 -0
- package/.morph/standards/infrastructure/supabase/supabase-storage.md +153 -0
- package/.morph/standards/integration/api/graphql.md +91 -0
- package/.morph/standards/integration/api/grpc.md +114 -0
- package/.morph/standards/integration/api/rest-design.md +95 -0
- package/.morph/standards/integration/event-driven/cqrs.md +101 -0
- package/.morph/standards/integration/event-driven/event-sourcing.md +124 -0
- package/.morph/standards/integration/event-driven/service-bus.md +95 -0
- package/.morph/standards/observability/logging.md +131 -0
- package/.morph/standards/observability/metrics.md +121 -0
- package/.morph/standards/observability/monitoring.md +114 -0
- package/.morph/standards/observability/tracing.md +132 -0
- package/.morph/standards/workflows/parallel-execution.md +112 -0
- package/.morph/standards/workflows/thread-management.md +113 -0
- package/.morph/templates/.idea/morph-templates.xml +92 -0
- package/.morph/templates/.vscode/morph-templates.code-snippets +186 -0
- package/.morph/templates/IDE-SNIPPETS.md +266 -0
- package/.morph/templates/README.md +814 -0
- package/.morph/templates/REGISTRY.json +1677 -0
- package/.morph/templates/code/dotnet/backend/repository.cs +141 -0
- package/.morph/templates/code/dotnet/backend/service.cs +139 -0
- package/.morph/templates/code/dotnet/contracts/Commands.cs +74 -0
- package/.morph/templates/code/dotnet/contracts/Entities.cs +25 -0
- package/.morph/templates/code/dotnet/contracts/Queries.cs +74 -0
- package/.morph/templates/code/dotnet/contracts/README.md +74 -0
- package/.morph/templates/code/dotnet/contracts/api-contracts.cs +173 -0
- package/.morph/templates/code/dotnet/contracts/contracts.cs +217 -0
- package/.morph/templates/code/dotnet/database/migration.cs +83 -0
- package/.morph/templates/code/dotnet/frontend/component.razor +239 -0
- package/.morph/templates/code/dotnet/jobs/agent.cs +163 -0
- package/.morph/templates/code/dotnet/jobs/job.cs +171 -0
- package/.morph/templates/code/dotnet/test.cs +239 -0
- package/.morph/templates/code/sql/rls-policy.sql +57 -0
- package/.morph/templates/code/sql/supabase-migration.sql +100 -0
- package/.morph/templates/code/sql/supabase-migration.template.sql +113 -0
- package/.morph/templates/code/typescript/contracts.ts +168 -0
- package/.morph/templates/context/CONTEXT-FEATURE.md +276 -0
- package/.morph/templates/context/CONTEXT.md +181 -0
- package/.morph/templates/docs/proposal.md +182 -0
- package/.morph/templates/docs/spec.md +149 -0
- package/.morph/templates/examples/design-system-examples.md +357 -0
- package/.morph/templates/examples/spec-examples.md +90 -0
- package/.morph/templates/feature/decisions.md +187 -0
- package/.morph/templates/feature/recap.md +146 -0
- package/.morph/templates/feature/tasks.md +199 -0
- package/.morph/templates/infrastructure/azure/Dockerfile.example +82 -0
- package/.morph/templates/infrastructure/azure/README.md +286 -0
- package/.morph/templates/infrastructure/azure/app-insights.bicep +63 -0
- package/.morph/templates/infrastructure/azure/app-service.bicep +164 -0
- package/.morph/templates/infrastructure/azure/container-app-env.bicep +49 -0
- package/.morph/templates/infrastructure/azure/container-app.bicep +156 -0
- package/.morph/templates/infrastructure/azure/deploy-checklist.md +426 -0
- package/.morph/templates/infrastructure/azure/deploy.ps1 +229 -0
- package/.morph/templates/infrastructure/azure/deploy.sh +208 -0
- package/.morph/templates/infrastructure/azure/key-vault.bicep +91 -0
- package/.morph/templates/infrastructure/azure/main.bicep +189 -0
- package/.morph/templates/infrastructure/azure/parameters.dev.json +29 -0
- package/.morph/templates/infrastructure/azure/parameters.prod.json +29 -0
- package/.morph/templates/infrastructure/azure/parameters.staging.json +29 -0
- package/.morph/templates/infrastructure/azure/sql-database.bicep +103 -0
- package/.morph/templates/infrastructure/azure/storage.bicep +106 -0
- package/.morph/templates/infrastructure/docker/Dockerfile.template +58 -0
- package/.morph/templates/infrastructure/docker/docker-compose.template.yml +67 -0
- package/.morph/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -0
- package/.morph/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -0
- package/.morph/templates/infrastructure/docker/easypanel.template.json +54 -0
- package/.morph/templates/infrastructure/github/README.md +593 -0
- package/.morph/templates/infrastructure/github/actions/azure-auth/action.yml.hbs +22 -0
- package/.morph/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +45 -0
- package/.morph/templates/infrastructure/github/actions/health-check/action.yml.hbs +27 -0
- package/.morph/templates/infrastructure/github/workflows/deploy-azure-app-service.yml.hbs +61 -0
- package/.morph/templates/infrastructure/github/workflows/deploy-easypanel.yml.hbs +31 -0
- package/.morph/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +59 -0
- package/.morph/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +39 -0
- package/.morph/templates/integrations/asaas-client.cs +387 -0
- package/.morph/templates/integrations/asaas-webhook.cs +351 -0
- package/.morph/templates/integrations/azure-identity-config.cs +288 -0
- package/.morph/templates/integrations/clerk-config.cs +258 -0
- package/.morph/templates/meta-prompts/fusion/fusion-agent.md +76 -0
- package/.morph/templates/meta-prompts/fusion/fusion-aggregator.md +100 -0
- package/.morph/templates/meta-prompts/hops/hop-retry.md +78 -0
- package/.morph/templates/meta-prompts/hops/hop-validation.md +97 -0
- package/.morph/templates/meta-prompts/hops/hop-wrapper.md +36 -0
- package/.morph/templates/meta-prompts/parallel-workers/parallel-coordinator.md +113 -0
- package/.morph/templates/meta-prompts/parallel-workers/parallel-worker.md +80 -0
- package/.morph/templates/meta-prompts/squad-leaders/backend-squad.md +90 -0
- package/.morph/templates/meta-prompts/squad-leaders/frontend-squad.md +126 -0
- package/.morph/templates/meta-prompts/squad-leaders/squad-leader.md +43 -0
- package/.morph/templates/meta-prompts/validators/checkpoint-validator.md +107 -0
- package/.morph/templates/meta-prompts/validators/pre-commit-validator.md +95 -0
- package/.morph/templates/saas/subscription.cs +347 -0
- package/.morph/templates/saas/tenant.cs +338 -0
- package/.morph/templates/state.template.json +17 -0
- package/.morph/templates/ui/FluentDesignTheme.cs +149 -0
- package/.morph/templates/ui/MudTheme.cs +281 -0
- package/.morph/templates/ui/design-system.css +226 -0
- package/bin/morph-spec.js +1 -1
- package/package.json +1 -1
- package/src/commands/project/update.js +185 -46
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# ==============================================================================
|
|
3
|
+
# MORPH-SPEC - Deploy Script
|
|
4
|
+
# Automated deployment of Azure infrastructure
|
|
5
|
+
# ==============================================================================
|
|
6
|
+
|
|
7
|
+
set -e # Exit on error
|
|
8
|
+
|
|
9
|
+
# ==============================================================================
|
|
10
|
+
# CONFIGURATION
|
|
11
|
+
# ==============================================================================
|
|
12
|
+
|
|
13
|
+
# Required variables (override via environment)
|
|
14
|
+
APP_NAME="${APP_NAME:-myapp}"
|
|
15
|
+
ENVIRONMENT="${ENVIRONMENT:-dev}"
|
|
16
|
+
LOCATION="${LOCATION:-eastus}"
|
|
17
|
+
SUBSCRIPTION_ID="${SUBSCRIPTION_ID:-}"
|
|
18
|
+
|
|
19
|
+
# Optional variables
|
|
20
|
+
HOSTING_TYPE="${HOSTING_TYPE:-appservice}" # appservice or containerapp
|
|
21
|
+
APP_SERVICE_SKU="${APP_SERVICE_SKU:-F1}"
|
|
22
|
+
CONTAINER_IMAGE="${CONTAINER_IMAGE:-mcr.microsoft.com/hello-world:latest}"
|
|
23
|
+
|
|
24
|
+
# Derived variables
|
|
25
|
+
RESOURCE_GROUP="rg-${APP_NAME}-${ENVIRONMENT}"
|
|
26
|
+
DEPLOYMENT_NAME="deploy-${APP_NAME}-$(date +%Y%m%d-%H%M%S)"
|
|
27
|
+
|
|
28
|
+
# ==============================================================================
|
|
29
|
+
# COLORS
|
|
30
|
+
# ==============================================================================
|
|
31
|
+
|
|
32
|
+
RED='\033[0;31m'
|
|
33
|
+
GREEN='\033[0;32m'
|
|
34
|
+
YELLOW='\033[1;33m'
|
|
35
|
+
BLUE='\033[0;34m'
|
|
36
|
+
NC='\033[0m' # No Color
|
|
37
|
+
|
|
38
|
+
# ==============================================================================
|
|
39
|
+
# FUNCTIONS
|
|
40
|
+
# ==============================================================================
|
|
41
|
+
|
|
42
|
+
log_info() {
|
|
43
|
+
echo -e "${BLUE}ℹ️ $1${NC}"
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
log_success() {
|
|
47
|
+
echo -e "${GREEN}✅ $1${NC}"
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
log_warning() {
|
|
51
|
+
echo -e "${YELLOW}⚠️ $1${NC}"
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
log_error() {
|
|
55
|
+
echo -e "${RED}❌ $1${NC}"
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
check_prerequisites() {
|
|
59
|
+
log_info "Checking prerequisites..."
|
|
60
|
+
|
|
61
|
+
# Check Azure CLI
|
|
62
|
+
if ! command -v az &> /dev/null; then
|
|
63
|
+
log_error "Azure CLI not found. Install from: https://aka.ms/azure-cli"
|
|
64
|
+
exit 1
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
# Check login
|
|
68
|
+
if ! az account show &> /dev/null; then
|
|
69
|
+
log_error "Not logged in to Azure. Run: az login"
|
|
70
|
+
exit 1
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
# Set subscription if provided
|
|
74
|
+
if [ -n "$SUBSCRIPTION_ID" ]; then
|
|
75
|
+
log_info "Setting subscription to: $SUBSCRIPTION_ID"
|
|
76
|
+
az account set --subscription "$SUBSCRIPTION_ID"
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
log_success "Prerequisites checked"
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
create_resource_group() {
|
|
83
|
+
log_info "Creating resource group: $RESOURCE_GROUP"
|
|
84
|
+
|
|
85
|
+
if az group exists -n "$RESOURCE_GROUP" | grep -q true; then
|
|
86
|
+
log_warning "Resource group already exists"
|
|
87
|
+
else
|
|
88
|
+
az group create \
|
|
89
|
+
--name "$RESOURCE_GROUP" \
|
|
90
|
+
--location "$LOCATION" \
|
|
91
|
+
--tags \
|
|
92
|
+
environment="$ENVIRONMENT" \
|
|
93
|
+
application="$APP_NAME" \
|
|
94
|
+
managedBy="bicep" \
|
|
95
|
+
framework="morph-spec"
|
|
96
|
+
|
|
97
|
+
log_success "Resource group created"
|
|
98
|
+
fi
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
generate_sql_password() {
|
|
102
|
+
# Generate secure random password
|
|
103
|
+
SQL_PASSWORD=$(openssl rand -base64 32 | tr -d "=+/" | cut -c1-25)
|
|
104
|
+
log_success "SQL password generated (stored in Key Vault after deploy)"
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
deploy_infrastructure() {
|
|
108
|
+
log_info "Deploying infrastructure..."
|
|
109
|
+
log_info " App Name: $APP_NAME"
|
|
110
|
+
log_info " Environment: $ENVIRONMENT"
|
|
111
|
+
log_info " Hosting Type: $HOSTING_TYPE"
|
|
112
|
+
log_info " Location: $LOCATION"
|
|
113
|
+
|
|
114
|
+
# Prepare parameters file
|
|
115
|
+
PARAMS_FILE="parameters.${ENVIRONMENT}.json"
|
|
116
|
+
|
|
117
|
+
if [ ! -f "$PARAMS_FILE" ]; then
|
|
118
|
+
log_error "Parameters file not found: $PARAMS_FILE"
|
|
119
|
+
exit 1
|
|
120
|
+
fi
|
|
121
|
+
|
|
122
|
+
# Deploy
|
|
123
|
+
az deployment group create \
|
|
124
|
+
--resource-group "$RESOURCE_GROUP" \
|
|
125
|
+
--name "$DEPLOYMENT_NAME" \
|
|
126
|
+
--template-file main.bicep \
|
|
127
|
+
--parameters "@$PARAMS_FILE" \
|
|
128
|
+
--parameters \
|
|
129
|
+
appName="$APP_NAME" \
|
|
130
|
+
environment="$ENVIRONMENT" \
|
|
131
|
+
location="$LOCATION" \
|
|
132
|
+
hostingType="$HOSTING_TYPE" \
|
|
133
|
+
appServiceSku="$APP_SERVICE_SKU" \
|
|
134
|
+
containerImage="$CONTAINER_IMAGE" \
|
|
135
|
+
sqlAdminPassword="$SQL_PASSWORD" \
|
|
136
|
+
--output table
|
|
137
|
+
|
|
138
|
+
log_success "Infrastructure deployed"
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
show_outputs() {
|
|
142
|
+
log_info "Retrieving deployment outputs..."
|
|
143
|
+
|
|
144
|
+
APP_URL=$(az deployment group show \
|
|
145
|
+
-g "$RESOURCE_GROUP" \
|
|
146
|
+
-n "$DEPLOYMENT_NAME" \
|
|
147
|
+
--query properties.outputs.appUrl.value -o tsv)
|
|
148
|
+
|
|
149
|
+
SQL_CONNECTION=$(az deployment group show \
|
|
150
|
+
-g "$RESOURCE_GROUP" \
|
|
151
|
+
-n "$DEPLOYMENT_NAME" \
|
|
152
|
+
--query properties.outputs.sqlConnectionString.value -o tsv)
|
|
153
|
+
|
|
154
|
+
APPINSIGHTS_CONNECTION=$(az deployment group show \
|
|
155
|
+
-g "$RESOURCE_GROUP" \
|
|
156
|
+
-n "$DEPLOYMENT_NAME" \
|
|
157
|
+
--query properties.outputs.appInsightsConnectionString.value -o tsv)
|
|
158
|
+
|
|
159
|
+
echo ""
|
|
160
|
+
echo "╔════════════════════════════════════════════════════════════════╗"
|
|
161
|
+
echo "║ DEPLOYMENT SUCCESSFUL ║"
|
|
162
|
+
echo "╚════════════════════════════════════════════════════════════════╝"
|
|
163
|
+
echo ""
|
|
164
|
+
echo "🌐 Application URL:"
|
|
165
|
+
echo " $APP_URL"
|
|
166
|
+
echo ""
|
|
167
|
+
echo "🗄️ SQL Connection String:"
|
|
168
|
+
echo " $SQL_CONNECTION"
|
|
169
|
+
echo ""
|
|
170
|
+
echo "📊 App Insights Connection String:"
|
|
171
|
+
echo " $APPINSIGHTS_CONNECTION"
|
|
172
|
+
echo ""
|
|
173
|
+
echo "💡 Next steps:"
|
|
174
|
+
|
|
175
|
+
if [ "$HOSTING_TYPE" = "appservice" ]; then
|
|
176
|
+
echo " 1. Deploy your code: az webapp up --name app-${APP_NAME}-${ENVIRONMENT}"
|
|
177
|
+
echo " 2. View logs: az webapp log tail --name app-${APP_NAME}-${ENVIRONMENT} -g $RESOURCE_GROUP"
|
|
178
|
+
else
|
|
179
|
+
echo " 1. Build and push container: az acr build --registry <ACR> --image ${APP_NAME}:latest ."
|
|
180
|
+
echo " 2. Update container app: az containerapp update -n ca-${APP_NAME}-${ENVIRONMENT} -g $RESOURCE_GROUP --image <IMAGE>"
|
|
181
|
+
echo " 3. View logs: az containerapp logs show -n ca-${APP_NAME}-${ENVIRONMENT} -g $RESOURCE_GROUP --follow"
|
|
182
|
+
fi
|
|
183
|
+
|
|
184
|
+
echo ""
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
# ==============================================================================
|
|
188
|
+
# MAIN
|
|
189
|
+
# ==============================================================================
|
|
190
|
+
|
|
191
|
+
main() {
|
|
192
|
+
echo ""
|
|
193
|
+
echo "╔════════════════════════════════════════════════════════════════╗"
|
|
194
|
+
echo "║ MORPH-SPEC - Azure Infrastructure Deploy ║"
|
|
195
|
+
echo "╚════════════════════════════════════════════════════════════════╝"
|
|
196
|
+
echo ""
|
|
197
|
+
|
|
198
|
+
check_prerequisites
|
|
199
|
+
create_resource_group
|
|
200
|
+
generate_sql_password
|
|
201
|
+
deploy_infrastructure
|
|
202
|
+
show_outputs
|
|
203
|
+
|
|
204
|
+
log_success "Deployment complete! 🚀"
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
# Run main function
|
|
208
|
+
main
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// ==============================================================================
|
|
2
|
+
// MORPH-SPEC - Key Vault
|
|
3
|
+
// Azure Key Vault for secrets management
|
|
4
|
+
// ==============================================================================
|
|
5
|
+
|
|
6
|
+
@description('Key Vault name')
|
|
7
|
+
@minLength(3)
|
|
8
|
+
@maxLength(24)
|
|
9
|
+
param name string
|
|
10
|
+
|
|
11
|
+
@description('Location')
|
|
12
|
+
param location string
|
|
13
|
+
|
|
14
|
+
@description('Tags')
|
|
15
|
+
param tags object = {}
|
|
16
|
+
|
|
17
|
+
@description('Enable soft delete')
|
|
18
|
+
param enableSoftDelete bool = true
|
|
19
|
+
|
|
20
|
+
@description('Soft delete retention days')
|
|
21
|
+
@minValue(7)
|
|
22
|
+
@maxValue(90)
|
|
23
|
+
param softDeleteRetentionDays int = 30
|
|
24
|
+
|
|
25
|
+
@description('Enable purge protection')
|
|
26
|
+
param enablePurgeProtection bool = false
|
|
27
|
+
|
|
28
|
+
@description('Object IDs to grant access (optional)')
|
|
29
|
+
param accessPoliciesObjectIds array = []
|
|
30
|
+
|
|
31
|
+
// ==============================================================================
|
|
32
|
+
// KEY VAULT
|
|
33
|
+
// ==============================================================================
|
|
34
|
+
|
|
35
|
+
resource keyVault 'Microsoft.KeyVault/vaults@2023-07-01' = {
|
|
36
|
+
name: name
|
|
37
|
+
location: location
|
|
38
|
+
tags: tags
|
|
39
|
+
properties: {
|
|
40
|
+
tenantId: subscription().tenantId
|
|
41
|
+
sku: {
|
|
42
|
+
family: 'A'
|
|
43
|
+
name: 'standard'
|
|
44
|
+
}
|
|
45
|
+
enabledForDeployment: true
|
|
46
|
+
enabledForDiskEncryption: false
|
|
47
|
+
enabledForTemplateDeployment: true
|
|
48
|
+
enableSoftDelete: enableSoftDelete
|
|
49
|
+
softDeleteRetentionInDays: softDeleteRetentionDays
|
|
50
|
+
enablePurgeProtection: enablePurgeProtection ? true : null
|
|
51
|
+
enableRbacAuthorization: true
|
|
52
|
+
publicNetworkAccess: 'Enabled'
|
|
53
|
+
networkAcls: {
|
|
54
|
+
defaultAction: 'Allow'
|
|
55
|
+
bypass: 'AzureServices'
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ==============================================================================
|
|
61
|
+
// ACCESS POLICIES (Optional - if not using RBAC)
|
|
62
|
+
// ==============================================================================
|
|
63
|
+
|
|
64
|
+
resource accessPolicies 'Microsoft.KeyVault/vaults/accessPolicies@2023-07-01' = if (length(accessPoliciesObjectIds) > 0) {
|
|
65
|
+
parent: keyVault
|
|
66
|
+
name: 'add'
|
|
67
|
+
properties: {
|
|
68
|
+
accessPolicies: [for objectId in accessPoliciesObjectIds: {
|
|
69
|
+
tenantId: subscription().tenantId
|
|
70
|
+
objectId: objectId
|
|
71
|
+
permissions: {
|
|
72
|
+
secrets: ['get', 'list', 'set', 'delete']
|
|
73
|
+
keys: ['get', 'list', 'create', 'delete']
|
|
74
|
+
certificates: ['get', 'list', 'create', 'delete']
|
|
75
|
+
}
|
|
76
|
+
}]
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ==============================================================================
|
|
81
|
+
// OUTPUTS
|
|
82
|
+
// ==============================================================================
|
|
83
|
+
|
|
84
|
+
@description('Key Vault ID')
|
|
85
|
+
output id string = keyVault.id
|
|
86
|
+
|
|
87
|
+
@description('Key Vault name')
|
|
88
|
+
output name string = keyVault.name
|
|
89
|
+
|
|
90
|
+
@description('Key Vault URI')
|
|
91
|
+
output uri string = keyVault.properties.vaultUri
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
// ==============================================================================
|
|
2
|
+
// MORPH-SPEC - Main Bicep Template
|
|
3
|
+
// Entry point para infraestrutura Azure
|
|
4
|
+
// ==============================================================================
|
|
5
|
+
|
|
6
|
+
targetScope = 'resourceGroup'
|
|
7
|
+
|
|
8
|
+
// ==============================================================================
|
|
9
|
+
// PARAMETERS
|
|
10
|
+
// ==============================================================================
|
|
11
|
+
|
|
12
|
+
@description('Environment name (dev, staging, prod)')
|
|
13
|
+
@allowed(['dev', 'staging', 'prod'])
|
|
14
|
+
param environment string = 'dev'
|
|
15
|
+
|
|
16
|
+
@description('Location for all resources')
|
|
17
|
+
param location string = resourceGroup().location
|
|
18
|
+
|
|
19
|
+
@description('Application name (used for naming resources)')
|
|
20
|
+
@minLength(3)
|
|
21
|
+
@maxLength(15)
|
|
22
|
+
param appName string
|
|
23
|
+
|
|
24
|
+
@description('SQL Server administrator password')
|
|
25
|
+
@secure()
|
|
26
|
+
param sqlAdminPassword string
|
|
27
|
+
|
|
28
|
+
@description('Container image to deploy (only for Container Apps)')
|
|
29
|
+
param containerImage string = 'mcr.microsoft.com/hello-world:latest'
|
|
30
|
+
|
|
31
|
+
@description('Hosting type: appservice or containerapp')
|
|
32
|
+
@allowed(['appservice', 'containerapp'])
|
|
33
|
+
param hostingType string = 'appservice'
|
|
34
|
+
|
|
35
|
+
@description('App Service SKU (only for App Service hosting)')
|
|
36
|
+
@allowed(['F1', 'B1', 'S1', 'P1v2'])
|
|
37
|
+
param appServiceSku string = 'F1'
|
|
38
|
+
|
|
39
|
+
// ==============================================================================
|
|
40
|
+
// VARIABLES
|
|
41
|
+
// ==============================================================================
|
|
42
|
+
|
|
43
|
+
var resourcePrefix = '${appName}-${environment}'
|
|
44
|
+
var tags = {
|
|
45
|
+
environment: environment
|
|
46
|
+
application: appName
|
|
47
|
+
managedBy: 'bicep'
|
|
48
|
+
framework: 'morph-spec'
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ==============================================================================
|
|
52
|
+
// LOG ANALYTICS WORKSPACE
|
|
53
|
+
// Required for Container Apps and Application Insights
|
|
54
|
+
// ==============================================================================
|
|
55
|
+
|
|
56
|
+
resource logAnalytics 'Microsoft.OperationalInsights/workspaces@2022-10-01' = {
|
|
57
|
+
name: '${resourcePrefix}-logs'
|
|
58
|
+
location: location
|
|
59
|
+
tags: tags
|
|
60
|
+
properties: {
|
|
61
|
+
sku: {
|
|
62
|
+
name: 'PerGB2018'
|
|
63
|
+
}
|
|
64
|
+
retentionInDays: environment == 'prod' ? 90 : 30
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ==============================================================================
|
|
69
|
+
// MODULES
|
|
70
|
+
// ==============================================================================
|
|
71
|
+
|
|
72
|
+
// Application Insights
|
|
73
|
+
module appInsights 'app-insights.bicep' = {
|
|
74
|
+
name: 'appInsights-${uniqueString(resourceGroup().id)}'
|
|
75
|
+
params: {
|
|
76
|
+
name: '${resourcePrefix}-insights'
|
|
77
|
+
location: location
|
|
78
|
+
tags: tags
|
|
79
|
+
logAnalyticsWorkspaceId: logAnalytics.id
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Key Vault
|
|
84
|
+
module keyVault 'key-vault.bicep' = {
|
|
85
|
+
name: 'keyVault-${uniqueString(resourceGroup().id)}'
|
|
86
|
+
params: {
|
|
87
|
+
name: '${resourcePrefix}-kv'
|
|
88
|
+
location: location
|
|
89
|
+
tags: tags
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Storage Account
|
|
94
|
+
module storage 'storage.bicep' = {
|
|
95
|
+
name: 'storage-${uniqueString(resourceGroup().id)}'
|
|
96
|
+
params: {
|
|
97
|
+
name: replace('${resourcePrefix}st', '-', '')
|
|
98
|
+
location: location
|
|
99
|
+
tags: tags
|
|
100
|
+
sku: environment == 'prod' ? 'Standard_GRS' : 'Standard_LRS'
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// SQL Database
|
|
105
|
+
module sqlDatabase 'sql-database.bicep' = {
|
|
106
|
+
name: 'sqlDatabase-${uniqueString(resourceGroup().id)}'
|
|
107
|
+
params: {
|
|
108
|
+
serverName: '${resourcePrefix}-sql'
|
|
109
|
+
databaseName: appName
|
|
110
|
+
location: location
|
|
111
|
+
tags: tags
|
|
112
|
+
adminPassword: sqlAdminPassword
|
|
113
|
+
useFree: environment == 'dev'
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ==============================================================================
|
|
118
|
+
// HOSTING - App Service (Conditional)
|
|
119
|
+
// ==============================================================================
|
|
120
|
+
|
|
121
|
+
module appService 'app-service.bicep' = if (hostingType == 'appservice') {
|
|
122
|
+
name: 'appService-${uniqueString(resourceGroup().id)}'
|
|
123
|
+
params: {
|
|
124
|
+
name: 'app-${resourcePrefix}'
|
|
125
|
+
location: location
|
|
126
|
+
tags: tags
|
|
127
|
+
sku: appServiceSku
|
|
128
|
+
appInsightsConnectionString: appInsights.outputs.connectionString
|
|
129
|
+
alwaysOn: appServiceSku != 'F1' // Only available on paid tiers
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ==============================================================================
|
|
134
|
+
// HOSTING - Container Apps (Conditional)
|
|
135
|
+
// ==============================================================================
|
|
136
|
+
|
|
137
|
+
// Container Apps Environment
|
|
138
|
+
module containerAppEnv 'container-app-env.bicep' = if (hostingType == 'containerapp') {
|
|
139
|
+
name: 'containerAppEnv-${uniqueString(resourceGroup().id)}'
|
|
140
|
+
params: {
|
|
141
|
+
name: '${resourcePrefix}-env'
|
|
142
|
+
location: location
|
|
143
|
+
tags: tags
|
|
144
|
+
logAnalyticsWorkspaceId: logAnalytics.id
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Container App
|
|
149
|
+
module containerApp 'container-app.bicep' = if (hostingType == 'containerapp') {
|
|
150
|
+
name: 'containerApp-${uniqueString(resourceGroup().id)}'
|
|
151
|
+
params: {
|
|
152
|
+
name: 'ca-${resourcePrefix}'
|
|
153
|
+
location: location
|
|
154
|
+
tags: tags
|
|
155
|
+
environmentId: hostingType == 'containerapp' ? containerAppEnv.outputs.id : ''
|
|
156
|
+
containerImage: containerImage
|
|
157
|
+
appInsightsConnectionString: appInsights.outputs.connectionString
|
|
158
|
+
minReplicas: environment == 'prod' ? 1 : 0
|
|
159
|
+
maxReplicas: environment == 'prod' ? 10 : 3
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ==============================================================================
|
|
164
|
+
// OUTPUTS
|
|
165
|
+
// ==============================================================================
|
|
166
|
+
|
|
167
|
+
@description('Application URL')
|
|
168
|
+
output appUrl string = hostingType == 'appservice' ? appService.outputs.url : containerApp.outputs.url
|
|
169
|
+
|
|
170
|
+
@description('Hosting Type')
|
|
171
|
+
output hostingType string = hostingType
|
|
172
|
+
|
|
173
|
+
@description('SQL Server connection string')
|
|
174
|
+
output sqlConnectionString string = sqlDatabase.outputs.connectionString
|
|
175
|
+
|
|
176
|
+
@description('Key Vault URI')
|
|
177
|
+
output keyVaultUri string = keyVault.outputs.uri
|
|
178
|
+
|
|
179
|
+
@description('Storage Account connection string')
|
|
180
|
+
output storageConnectionString string = storage.outputs.connectionString
|
|
181
|
+
|
|
182
|
+
@description('Application Insights connection string')
|
|
183
|
+
output appInsightsConnectionString string = appInsights.outputs.connectionString
|
|
184
|
+
|
|
185
|
+
@description('Log Analytics Workspace ID')
|
|
186
|
+
output logAnalyticsWorkspaceId string = logAnalytics.id
|
|
187
|
+
|
|
188
|
+
@description('App Service Principal ID (for Managed Identity)')
|
|
189
|
+
output appPrincipalId string = hostingType == 'appservice' ? appService.outputs.principalId : ''
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
|
|
3
|
+
"contentVersion": "1.0.0.0",
|
|
4
|
+
"parameters": {
|
|
5
|
+
"environment": {
|
|
6
|
+
"value": "dev"
|
|
7
|
+
},
|
|
8
|
+
"appName": {
|
|
9
|
+
"value": "{{APP_NAME}}"
|
|
10
|
+
},
|
|
11
|
+
"sqlAdminPassword": {
|
|
12
|
+
"reference": {
|
|
13
|
+
"keyVault": {
|
|
14
|
+
"id": "/subscriptions/{{SUBSCRIPTION_ID}}/resourceGroups/{{RESOURCE_GROUP}}/providers/Microsoft.KeyVault/vaults/{{KEY_VAULT_NAME}}"
|
|
15
|
+
},
|
|
16
|
+
"secretName": "sql-admin-password"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"hostingType": {
|
|
20
|
+
"value": "appservice"
|
|
21
|
+
},
|
|
22
|
+
"appServiceSku": {
|
|
23
|
+
"value": "F1"
|
|
24
|
+
},
|
|
25
|
+
"containerImage": {
|
|
26
|
+
"value": "{{ACR_NAME}}.azurecr.io/{{APP_NAME}}:latest"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
|
|
3
|
+
"contentVersion": "1.0.0.0",
|
|
4
|
+
"parameters": {
|
|
5
|
+
"environment": {
|
|
6
|
+
"value": "prod"
|
|
7
|
+
},
|
|
8
|
+
"appName": {
|
|
9
|
+
"value": "{{APP_NAME}}"
|
|
10
|
+
},
|
|
11
|
+
"sqlAdminPassword": {
|
|
12
|
+
"reference": {
|
|
13
|
+
"keyVault": {
|
|
14
|
+
"id": "/subscriptions/{{SUBSCRIPTION_ID}}/resourceGroups/{{RESOURCE_GROUP}}/providers/Microsoft.KeyVault/vaults/{{KEY_VAULT_NAME}}"
|
|
15
|
+
},
|
|
16
|
+
"secretName": "sql-admin-password"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"hostingType": {
|
|
20
|
+
"value": "containerapp"
|
|
21
|
+
},
|
|
22
|
+
"appServiceSku": {
|
|
23
|
+
"value": "P1v2"
|
|
24
|
+
},
|
|
25
|
+
"containerImage": {
|
|
26
|
+
"value": "{{ACR_NAME}}.azurecr.io/{{APP_NAME}}:{{VERSION}}"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
|
|
3
|
+
"contentVersion": "1.0.0.0",
|
|
4
|
+
"parameters": {
|
|
5
|
+
"environment": {
|
|
6
|
+
"value": "staging"
|
|
7
|
+
},
|
|
8
|
+
"appName": {
|
|
9
|
+
"value": "{{APP_NAME}}"
|
|
10
|
+
},
|
|
11
|
+
"sqlAdminPassword": {
|
|
12
|
+
"reference": {
|
|
13
|
+
"keyVault": {
|
|
14
|
+
"id": "/subscriptions/{{SUBSCRIPTION_ID}}/resourceGroups/{{RESOURCE_GROUP}}/providers/Microsoft.KeyVault/vaults/{{KEY_VAULT_NAME}}"
|
|
15
|
+
},
|
|
16
|
+
"secretName": "sql-admin-password"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"hostingType": {
|
|
20
|
+
"value": "containerapp"
|
|
21
|
+
},
|
|
22
|
+
"appServiceSku": {
|
|
23
|
+
"value": "B1"
|
|
24
|
+
},
|
|
25
|
+
"containerImage": {
|
|
26
|
+
"value": "{{ACR_NAME}}.azurecr.io/{{APP_NAME}}:latest"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// ==============================================================================
|
|
2
|
+
// MORPH-SPEC - SQL Database
|
|
3
|
+
// Azure SQL Server with Database (supports Free tier)
|
|
4
|
+
// ==============================================================================
|
|
5
|
+
|
|
6
|
+
@description('SQL Server name')
|
|
7
|
+
param serverName string
|
|
8
|
+
|
|
9
|
+
@description('Database name')
|
|
10
|
+
param databaseName string
|
|
11
|
+
|
|
12
|
+
@description('Location')
|
|
13
|
+
param location string
|
|
14
|
+
|
|
15
|
+
@description('Tags')
|
|
16
|
+
param tags object = {}
|
|
17
|
+
|
|
18
|
+
@description('Admin username')
|
|
19
|
+
param adminUsername string = 'sqladmin'
|
|
20
|
+
|
|
21
|
+
@description('Admin password')
|
|
22
|
+
@secure()
|
|
23
|
+
param adminPassword string
|
|
24
|
+
|
|
25
|
+
@description('Use free tier (32GB, limited DTUs)')
|
|
26
|
+
param useFree bool = true
|
|
27
|
+
|
|
28
|
+
// ==============================================================================
|
|
29
|
+
// SQL SERVER
|
|
30
|
+
// ==============================================================================
|
|
31
|
+
|
|
32
|
+
resource sqlServer 'Microsoft.Sql/servers@2023-05-01-preview' = {
|
|
33
|
+
name: serverName
|
|
34
|
+
location: location
|
|
35
|
+
tags: tags
|
|
36
|
+
properties: {
|
|
37
|
+
administratorLogin: adminUsername
|
|
38
|
+
administratorLoginPassword: adminPassword
|
|
39
|
+
version: '12.0'
|
|
40
|
+
minimalTlsVersion: '1.2'
|
|
41
|
+
publicNetworkAccess: 'Enabled'
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ==============================================================================
|
|
46
|
+
// SQL DATABASE
|
|
47
|
+
// ==============================================================================
|
|
48
|
+
|
|
49
|
+
resource sqlDatabase 'Microsoft.Sql/servers/databases@2023-05-01-preview' = {
|
|
50
|
+
parent: sqlServer
|
|
51
|
+
name: databaseName
|
|
52
|
+
location: location
|
|
53
|
+
tags: tags
|
|
54
|
+
sku: useFree ? {
|
|
55
|
+
name: 'Free'
|
|
56
|
+
tier: 'Free'
|
|
57
|
+
} : {
|
|
58
|
+
name: 'Basic'
|
|
59
|
+
tier: 'Basic'
|
|
60
|
+
capacity: 5
|
|
61
|
+
}
|
|
62
|
+
properties: {
|
|
63
|
+
collation: 'SQL_Latin1_General_CP1_CI_AS'
|
|
64
|
+
maxSizeBytes: useFree ? 32212254720 : 2147483648 // 32GB free, 2GB basic
|
|
65
|
+
catalogCollation: 'SQL_Latin1_General_CP1_CI_AS'
|
|
66
|
+
zoneRedundant: false
|
|
67
|
+
readScale: 'Disabled'
|
|
68
|
+
requestedBackupStorageRedundancy: 'Local'
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ==============================================================================
|
|
73
|
+
// FIREWALL RULES
|
|
74
|
+
// ==============================================================================
|
|
75
|
+
|
|
76
|
+
// Allow Azure services
|
|
77
|
+
resource firewallAzure 'Microsoft.Sql/servers/firewallRules@2023-05-01-preview' = {
|
|
78
|
+
parent: sqlServer
|
|
79
|
+
name: 'AllowAllAzureIps'
|
|
80
|
+
properties: {
|
|
81
|
+
startIpAddress: '0.0.0.0'
|
|
82
|
+
endIpAddress: '0.0.0.0'
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ==============================================================================
|
|
87
|
+
// OUTPUTS
|
|
88
|
+
// ==============================================================================
|
|
89
|
+
|
|
90
|
+
@description('SQL Server ID')
|
|
91
|
+
output serverId string = sqlServer.id
|
|
92
|
+
|
|
93
|
+
@description('SQL Server FQDN')
|
|
94
|
+
output serverFqdn string = sqlServer.properties.fullyQualifiedDomainName
|
|
95
|
+
|
|
96
|
+
@description('Database ID')
|
|
97
|
+
output databaseId string = sqlDatabase.id
|
|
98
|
+
|
|
99
|
+
@description('Connection string (password placeholder)')
|
|
100
|
+
output connectionString string = 'Server=tcp:${sqlServer.properties.fullyQualifiedDomainName},1433;Database=${databaseName};User ID=${adminUsername};Password=${adminPassword};Encrypt=true;TrustServerCertificate=false;Connection Timeout=30;'
|
|
101
|
+
|
|
102
|
+
@description('Connection string template (no password)')
|
|
103
|
+
output connectionStringTemplate string = 'Server=tcp:${sqlServer.properties.fullyQualifiedDomainName},1433;Database=${databaseName};User ID=${adminUsername};Password={your_password};Encrypt=true;TrustServerCertificate=false;Connection Timeout=30;'
|