Skip to content

Commit eb99499

Browse files
committed
working with azd and configuration
1 parent 044115d commit eb99499

21 files changed

+1922
-30
lines changed

.dockerignore

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1-
.env
1+
**/.env
2+
**/.env.local
3+
.env*
24
.dockerignore
35
.git
46
.gitignore
5-
node_modules/
7+
**/node_modules/
8+
api/public
69
dist/
710
.history/
811
.github/

.env

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,7 @@ VUE_APP_GITHUB_ENT=
1313
# Determines the GitHub Personal Access Token to use for API calls.
1414
# Create with scopes copilot, manage_billing:copilot, admin:enterprise, or manage_billing:enterprise, read:enterprise AND read:org
1515
VUE_APP_GITHUB_TOKEN=
16+
17+
# GitHub Api Url
18+
# for proxy api - set to /api/github
19+
VUE_APP_GITHUB_API_URL=https://api.github.com

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,6 @@ pnpm-debug.log*
2121
*.njsproj
2222
*.sln
2323
*.sw?
24+
.azure
25+
26+
api/public

api.Dockerfile

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ WORKDIR /app
44
COPY package*.json ./
55
RUN npm install
66
COPY . .
7+
# this will tokenize the app
78
RUN npm run build
89

910
# Stage 2: Prepare the Node.js API
@@ -17,9 +18,14 @@ COPY api/ .
1718

1819
# Copy the built Vue.js app from the previous stage
1920
COPY --from=build-stage /app/dist /api/public
21+
COPY --from=build-stage /app/dist/assets/app-config.js /api/app-config.template.js
22+
23+
# install gettext-base for envsubst
24+
RUN apt-get update && apt-get install -y gettext-base
2025

2126
# Expose the port your API will run on
2227
EXPOSE 3000
2328

2429
# Command to run your API (and serve your Vue.js app)
25-
CMD ["node", "server.mjs"]
30+
RUN chmod +x /api/docker-entrypoint.api/entrypoint.sh
31+
ENTRYPOINT ["/api/docker-entrypoint.api/entrypoint.sh"]
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#!/bin/bash
2+
3+
configTemplateFile=/api/app-config.template.js
4+
configTargetFile=/api/public/assets/app-config.js
5+
6+
envsubst <"$configTemplateFile" >"$configTargetFile"
7+
8+
node server.mjs

api/server.mjs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ import { fileURLToPath } from 'url';
66
import session from 'express-session';
77
import {createProxyMiddleware} from 'http-proxy-middleware';
88

9-
dotenv.config({ path: path.join(__dirname, '.env.local') });
10-
119
// Construct __dirname equivalent in ES module scope
1210
const __dirname = path.dirname(fileURLToPath(import.meta.url));
11+
//dotenv.config({ path: path.join(__dirname, '.env') });
12+
dotenv.config({ path: path.join(__dirname, '.env.local') });
1313

1414
const app = express();
1515

@@ -27,6 +27,7 @@ const authMiddleware = (req, res, next) => {
2727
return;
2828
}
2929
req.headers['Authorization'] = `Bearer ${req.session.token}`;
30+
console.log('Added Authorization to:', req.url);
3031
next();
3132
};
3233

@@ -73,11 +74,24 @@ const exchangeCode = async (code) => {
7374
app.use(express.static(path.join(__dirname, 'public')));
7475

7576
app.get('/login', (req, res) => {
76-
res.redirect(`https://github.com/login/oauth/authorize?client_id=${process.env.CLIENT_ID}`);
77+
// build the URL to redirect to GitHub using host and scheme
78+
const redirectUrl = `${req.protocol}://${req.get('host')}/callback`;
79+
// generate random state
80+
// store the state in the session
81+
req.session.state = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
82+
83+
res.redirect(`https://github.com/login/oauth/authorize?client_id=${process.env.CLIENT_ID}&redirect_uri=${redirectUrl}&state=${req.session.state}`);
7784
});
7885

7986
app.get('/callback', async (req, res) => {
8087
const code = req.query.code;
88+
const state = req.query.state;
89+
90+
// check the state against the session
91+
if (state !== req.session.state) {
92+
res.send('Invalid state');
93+
return;
94+
}
8195

8296
const tokenData = await exchangeCode(code);
8397

azure.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# yaml-language-server: $schema=https://raw.githubusercontent.com/Azure/azure-dev/main/schemas/v1.0/azure.yaml.json
2+
3+
name: copilot-metrics-viewer
4+
metadata:
5+
6+
services:
7+
copilot-metrics-viewer:
8+
project: .
9+
host: containerapp
10+
language: js
11+
docker:
12+
path: api.Dockerfile

infra/abbreviations.json

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
{
2+
"analysisServicesServers": "as",
3+
"apiManagementService": "apim-",
4+
"appConfigurationStores": "appcs-",
5+
"appManagedEnvironments": "cae-",
6+
"appContainerApps": "ca-",
7+
"authorizationPolicyDefinitions": "policy-",
8+
"automationAutomationAccounts": "aa-",
9+
"blueprintBlueprints": "bp-",
10+
"blueprintBlueprintsArtifacts": "bpa-",
11+
"cacheRedis": "redis-",
12+
"cdnProfiles": "cdnp-",
13+
"cdnProfilesEndpoints": "cdne-",
14+
"cognitiveServicesAccounts": "cog-",
15+
"cognitiveServicesFormRecognizer": "cog-fr-",
16+
"cognitiveServicesTextAnalytics": "cog-ta-",
17+
"computeAvailabilitySets": "avail-",
18+
"computeCloudServices": "cld-",
19+
"computeDiskEncryptionSets": "des",
20+
"computeDisks": "disk",
21+
"computeDisksOs": "osdisk",
22+
"computeGalleries": "gal",
23+
"computeSnapshots": "snap-",
24+
"computeVirtualMachines": "vm",
25+
"computeVirtualMachineScaleSets": "vmss-",
26+
"containerInstanceContainerGroups": "ci",
27+
"containerRegistryRegistries": "cr",
28+
"containerServiceManagedClusters": "aks-",
29+
"databricksWorkspaces": "dbw-",
30+
"dataFactoryFactories": "adf-",
31+
"dataLakeAnalyticsAccounts": "dla",
32+
"dataLakeStoreAccounts": "dls",
33+
"dataMigrationServices": "dms-",
34+
"dBforMySQLServers": "mysql-",
35+
"dBforPostgreSQLServers": "psql-",
36+
"devicesIotHubs": "iot-",
37+
"devicesProvisioningServices": "provs-",
38+
"devicesProvisioningServicesCertificates": "pcert-",
39+
"documentDBDatabaseAccounts": "cosmos-",
40+
"eventGridDomains": "evgd-",
41+
"eventGridDomainsTopics": "evgt-",
42+
"eventGridEventSubscriptions": "evgs-",
43+
"eventHubNamespaces": "evhns-",
44+
"eventHubNamespacesEventHubs": "evh-",
45+
"hdInsightClustersHadoop": "hadoop-",
46+
"hdInsightClustersHbase": "hbase-",
47+
"hdInsightClustersKafka": "kafka-",
48+
"hdInsightClustersMl": "mls-",
49+
"hdInsightClustersSpark": "spark-",
50+
"hdInsightClustersStorm": "storm-",
51+
"hybridComputeMachines": "arcs-",
52+
"insightsActionGroups": "ag-",
53+
"insightsComponents": "appi-",
54+
"keyVaultVaults": "kv-",
55+
"kubernetesConnectedClusters": "arck",
56+
"kustoClusters": "dec",
57+
"kustoClustersDatabases": "dedb",
58+
"logicIntegrationAccounts": "ia-",
59+
"logicWorkflows": "logic-",
60+
"machineLearningServicesWorkspaces": "mlw-",
61+
"managedIdentityUserAssignedIdentities": "id-",
62+
"managementManagementGroups": "mg-",
63+
"migrateAssessmentProjects": "migr-",
64+
"networkApplicationGateways": "agw-",
65+
"networkApplicationSecurityGroups": "asg-",
66+
"networkAzureFirewalls": "afw-",
67+
"networkBastionHosts": "bas-",
68+
"networkConnections": "con-",
69+
"networkDnsZones": "dnsz-",
70+
"networkExpressRouteCircuits": "erc-",
71+
"networkFirewallPolicies": "afwp-",
72+
"networkFirewallPoliciesWebApplication": "waf",
73+
"networkFirewallPoliciesRuleGroups": "wafrg",
74+
"networkFrontDoors": "fd-",
75+
"networkFrontdoorWebApplicationFirewallPolicies": "fdfp-",
76+
"networkLoadBalancersExternal": "lbe-",
77+
"networkLoadBalancersInternal": "lbi-",
78+
"networkLoadBalancersInboundNatRules": "rule-",
79+
"networkLocalNetworkGateways": "lgw-",
80+
"networkNatGateways": "ng-",
81+
"networkNetworkInterfaces": "nic-",
82+
"networkNetworkSecurityGroups": "nsg-",
83+
"networkNetworkSecurityGroupsSecurityRules": "nsgsr-",
84+
"networkNetworkWatchers": "nw-",
85+
"networkPrivateDnsZones": "pdnsz-",
86+
"networkPrivateLinkServices": "pl-",
87+
"networkPublicIPAddresses": "pip-",
88+
"networkPublicIPPrefixes": "ippre-",
89+
"networkRouteFilters": "rf-",
90+
"networkRouteTables": "rt-",
91+
"networkRouteTablesRoutes": "udr-",
92+
"networkTrafficManagerProfiles": "traf-",
93+
"networkVirtualNetworkGateways": "vgw-",
94+
"networkVirtualNetworks": "vnet-",
95+
"networkVirtualNetworksSubnets": "snet-",
96+
"networkVirtualNetworksVirtualNetworkPeerings": "peer-",
97+
"networkVirtualWans": "vwan-",
98+
"networkVpnGateways": "vpng-",
99+
"networkVpnGatewaysVpnConnections": "vcn-",
100+
"networkVpnGatewaysVpnSites": "vst-",
101+
"notificationHubsNamespaces": "ntfns-",
102+
"notificationHubsNamespacesNotificationHubs": "ntf-",
103+
"operationalInsightsWorkspaces": "log-",
104+
"portalDashboards": "dash-",
105+
"powerBIDedicatedCapacities": "pbi-",
106+
"purviewAccounts": "pview-",
107+
"recoveryServicesVaults": "rsv-",
108+
"resourcesResourceGroups": "rg-",
109+
"searchSearchServices": "srch-",
110+
"serviceBusNamespaces": "sb-",
111+
"serviceBusNamespacesQueues": "sbq-",
112+
"serviceBusNamespacesTopics": "sbt-",
113+
"serviceEndPointPolicies": "se-",
114+
"serviceFabricClusters": "sf-",
115+
"signalRServiceSignalR": "sigr",
116+
"sqlManagedInstances": "sqlmi-",
117+
"sqlServers": "sql-",
118+
"sqlServersDataWarehouse": "sqldw-",
119+
"sqlServersDatabases": "sqldb-",
120+
"sqlServersDatabasesStretch": "sqlstrdb-",
121+
"storageStorageAccounts": "st",
122+
"storageStorageAccountsVm": "stvm",
123+
"storSimpleManagers": "ssimp",
124+
"streamAnalyticsCluster": "asa-",
125+
"synapseWorkspaces": "syn",
126+
"synapseWorkspacesAnalyticsWorkspaces": "synw",
127+
"synapseWorkspacesSqlPoolsDedicated": "syndp",
128+
"synapseWorkspacesSqlPoolsSpark": "synsp",
129+
"timeSeriesInsightsEnvironments": "tsi-",
130+
"webServerFarms": "plan-",
131+
"webSitesAppService": "app-",
132+
"webSitesAppServiceEnvironment": "ase-",
133+
"webSitesFunctions": "func-",
134+
"webStaticSites": "stapp-"
135+
}
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
param name string
2+
param location string = resourceGroup().location
3+
param tags object = {}
4+
5+
param identityName string
6+
param containerRegistryName string
7+
param containerAppsEnvironmentName string
8+
param applicationInsightsName string
9+
param exists bool
10+
@secure()
11+
param appDefinition object
12+
13+
var appSettingsArray = filter(array(appDefinition.settings), i => i.name != '')
14+
var secrets = map(filter(appSettingsArray, i => i.?secret != null), i => {
15+
name: i.name
16+
value: i.value
17+
secretRef: i.?secretRef ?? take(replace(replace(toLower(i.name), '_', '-'), '.', '-'), 32)
18+
})
19+
var env = map(filter(appSettingsArray, i => i.?secret == null), i => {
20+
name: i.name
21+
value: i.value
22+
})
23+
24+
resource identity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = {
25+
name: identityName
26+
location: location
27+
}
28+
29+
resource containerRegistry 'Microsoft.ContainerRegistry/registries@2023-01-01-preview' existing = {
30+
name: containerRegistryName
31+
}
32+
33+
resource containerAppsEnvironment 'Microsoft.App/managedEnvironments@2023-05-01' existing = {
34+
name: containerAppsEnvironmentName
35+
}
36+
37+
resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = {
38+
name: applicationInsightsName
39+
}
40+
41+
resource acrPullRole 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
42+
scope: containerRegistry
43+
name: guid(subscription().id, resourceGroup().id, identity.id, 'acrPullRole')
44+
properties: {
45+
roleDefinitionId: subscriptionResourceId(
46+
'Microsoft.Authorization/roleDefinitions', '7f951dda-4ed3-4680-a7ca-43fe172d538d')
47+
principalType: 'ServicePrincipal'
48+
principalId: identity.properties.principalId
49+
}
50+
}
51+
52+
module fetchLatestImage '../modules/fetch-container-image.bicep' = {
53+
name: '${name}-fetch-image'
54+
params: {
55+
exists: exists
56+
name: name
57+
}
58+
}
59+
60+
resource app 'Microsoft.App/containerApps@2023-05-02-preview' = {
61+
name: name
62+
location: location
63+
tags: union(tags, {'azd-service-name': 'copilot-metrics-viewer' })
64+
dependsOn: [ acrPullRole ]
65+
identity: {
66+
type: 'UserAssigned'
67+
userAssignedIdentities: { '${identity.id}': {} }
68+
}
69+
properties: {
70+
managedEnvironmentId: containerAppsEnvironment.id
71+
configuration: {
72+
ingress: {
73+
external: true
74+
targetPort: 3000
75+
transport: 'auto'
76+
}
77+
registries: [
78+
{
79+
server: '${containerRegistryName}.azurecr.io'
80+
identity: identity.id
81+
}
82+
]
83+
secrets: union([
84+
],
85+
map(secrets, secret => {
86+
name: secret.secretRef
87+
value: secret.value
88+
}))
89+
}
90+
template: {
91+
containers: [
92+
{
93+
image: fetchLatestImage.outputs.?containers[?0].?image ?? 'mcr.microsoft.com/azuredocs/containerapps-helloworld:latest'
94+
name: 'main'
95+
env: union([
96+
{
97+
name: 'APPLICATIONINSIGHTS_CONNECTION_STRING'
98+
value: applicationInsights.properties.ConnectionString
99+
}
100+
{
101+
name: 'PORT'
102+
value: '3000'
103+
}
104+
],
105+
env,
106+
map(secrets, secret => {
107+
name: secret.name
108+
secretRef: secret.secretRef
109+
}))
110+
resources: {
111+
cpu: json('0.25')
112+
memory: '0.5Gi'
113+
}
114+
}
115+
]
116+
scale: {
117+
minReplicas: 0
118+
maxReplicas: 1
119+
}
120+
}
121+
}
122+
}
123+
124+
output defaultDomain string = containerAppsEnvironment.properties.defaultDomain
125+
output name string = app.name
126+
output uri string = 'https://${app.properties.configuration.ingress.fqdn}'
127+
output id string = app.id

0 commit comments

Comments
 (0)