1
+ #!/usr/bin/env zx
2
+
3
+ import "zx/globals"
4
+ import { randomBytes } from "crypto"
5
+ import { request } from "@octokit/request"
6
+ import { readFileSync } from "fs"
7
+ import { create } from "xmlbuilder2"
8
+
9
+ echo ( `DeviceScript Gateway configuration.` )
10
+ echo ( `` )
11
+ echo ( `This script will create a new resource group, with a web app, application insights, key vault and storage account.` )
12
+ echo ( `Make sure that you have the Azure CLI available and you are logged in.` )
13
+ echo ( `` )
14
+
15
+ const gatewayVersion = JSON . parse ( readFileSync ( "./package.json" , { encoding : "utf8" } ) ) . version
16
+ echo ( chalk . blue ( `gateway version: ${ gatewayVersion } ` ) )
17
+
18
+ const resourceGroup = await question ( chalk . blue ( "Pick a name for the resource group: " ) )
19
+ if ( ! resourceGroup ) throw "no resource group name given"
20
+
21
+ // check if resource group already exists
22
+ echo ( `Searching for existing resource group ${ resourceGroup } ...` )
23
+ const exists = JSON . parse ( ( await $ `az group list --query "[?name=='${ resourceGroup } ']"` ) . stdout )
24
+ if ( exists ?. length ) {
25
+ const config = await question ( chalk . red ( "Resource group already exists, delete? (yes/no) " ) , { choices : [ "yes" , "no" ] } )
26
+ if ( config !== "yes" ) throw "resource group already exists"
27
+
28
+ echo ( `deleting resource group ${ resourceGroup } ...` )
29
+ await $ `resourceGroup="${ resourceGroup } "
30
+ az group delete --yes --name $resourceGroup`
31
+ }
32
+
33
+ const namePrefix = await question ( chalk . blue ( "Pick a name prefix for generated resources (unique, > 3 and < 13 characters): " ) )
34
+ if ( ! namePrefix ) throw "no name prefix given"
35
+
36
+ // check keyvaults already exist
37
+ echo ( `Looking for deleting keyvaults that might name clash...` )
38
+ const deletevaults = JSON . parse ( ( await $ `az keyvault list-deleted` ) . stdout )
39
+ const deletedvault = deletevaults ?. find ( v => v . name === `${ namePrefix . toLowerCase ( ) } keys` )
40
+ if ( deletedvault )
41
+ throw `delete keyvault ${ deletedvault . name } already exists`
42
+
43
+ // fetch current user azure id
44
+ echo ( `Resolving Azure sign in user information...` )
45
+ const userInfo = JSON . parse ( ( await $ `az ad signed-in-user show` ) . stdout )
46
+ const adminUserId = userInfo . id
47
+ echo ( chalk . blue ( `Azure signin user: ${ userInfo . displayName } , ${ adminUserId } ` ) )
48
+
49
+ // generate password
50
+ const adminPassword = randomBytes ( 64 ) . toString ( 'base64url' )
51
+
52
+ // write parameter file
53
+ const parameterFile = `azuredeploy.parameters.json`
54
+ echo `write ${ parameterFile } `
55
+ fs . writeFileSync ( parameterFile , JSON . stringify ( {
56
+ "$schema" : "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#" ,
57
+ "contentVersion" : "1.0.0.0" ,
58
+ "parameters" : {
59
+ "gatewayVersion" : {
60
+ "value" : gatewayVersion
61
+ } ,
62
+ "namePrefix" : {
63
+ "value" : namePrefix
64
+ } ,
65
+ "adminUserId" : {
66
+ "value" : adminUserId
67
+ } ,
68
+ "adminPassword" : {
69
+ "value" : adminPassword
70
+ }
71
+ }
72
+ } , null , 4 ) , { encoding : "utf8" } )
73
+
74
+ const rsinfo = JSON . parse ( ( await $ `resourceGroup="${ resourceGroup } "
75
+ az group create --name $resourceGroup --location centralus` ) . stdout )
76
+
77
+ echo ( chalk . blue ( `Resource group: ${ rsinfo . name } , ${ rsinfo . id } ` ) )
78
+
79
+ // create resources
80
+ const dinfo = JSON . parse ( ( await $ `resourceGroup="${ resourceGroup } "
81
+ templateFile="azuredeploy.json"
82
+ parametersFile="${ parameterFile } "
83
+ az deployment group create \
84
+ --name devicescript \
85
+ --resource-group $resourceGroup \
86
+ --template-file $templateFile \
87
+ --parameters $parametersFile` ) . stdout )
88
+ const did = dinfo
89
+ const { outputs } = dinfo . properties
90
+ const { webAppName, keyVaultName } = outputs
91
+
92
+ echo ( chalk . blue ( `Deployment: web app ${ webAppName } , vault ${ keyVaultName } ` ) )
93
+
94
+ // generate local resource file
95
+ fs . writeFileSync ( ".env" ,
96
+ `RESOURCE_GROUP="${ resourceGroup } "
97
+ KEY_VAULT_NAME="${ keyVaultName } "
98
+ SELF_URL="http://0.0.0.0:7071"` , { encoding : "utf8" } )
99
+
100
+ // download publish profile
101
+ const pb = JSON . parse ( ( await $ `resourceGroup="${ resourceGroup } "
102
+ name="${ webAppName } "
103
+ az webapp deployment list-publishing-profiles --name $name --resource-group $resourceGroup` ) . stdout )
104
+ const zpb = pb ?. filter ( o => o . publishMethod === "ZipDeploy" )
105
+ if ( ! zpb ) throw "failed to fetch zip deploy publishing profile"
106
+
107
+ echo ( 'download publish profile...' )
108
+ const doc = create ( )
109
+ const pp = doc . ele ( 'publishData' ) . ele ( 'publishProfile' )
110
+ Object . keys ( zpd ) . forEach ( key => pp . att ( key , zpd [ key ] ) )
111
+ const pfn = `${ webAppName } .PublishSettings`
112
+ const xzpb = doc . end ( { prettyPrint : true } )
113
+ echo ( `publish profile: ${ pfn } ` )
114
+ fs . writeFileSync ( pfn , xzpb , { encoding : "utf8" } )
115
+
116
+ // final notes
117
+ echo ( chalk . blue ( `Azure resources and local development configured successfully` ) )
118
+ echo ( `- add GitHub secret AZURE_WEBAPP_PUBLISH_PROFILE with the content of ${ pfn } ` )
119
+ echo ( `- navigate to https://${ webAppName } .azurewebsites.net/swagger/` )
120
+ echo ( ` and sign in as user: admin, password: ${ adminPassword } ` )
121
+ echo ( ` (you can find the key in vault ${ keyVaultName } /secrets/passwords.)` )
0 commit comments