PiraeusDebug.psm1

function New-PiraeusDeployment  
{    
    param ([string]$SubscriptionName, [string]$ResourceGroupName, [string]$ClusterName, [string]$Email, [string]$Dns, [string]$Location, [string]$StorageAcctName, [int]$NodeCount = 1, [string]$LogLevel = "Information", [string]$FrontendVMSize, [string]$OrleansVMSize, [string]$AppID, [string]$Password)
  
    if($SubscriptionName.Length -eq 0)
    {
        Write-Host "Required parameter -SubscriptionName not set...terminating script" -ForegroundColor Yellow
        return
    }
    
    if($ResourceGroupName.Length -eq 0)
    {
        Write-Host "Required parameter -ResourceGroupName not set...terminating script" -ForegroundColor Yellow
        return
    }
    
    if($Email.Length -eq 0)
    {
        Write-Host "Required parameter -Email not set...terminating script" -ForegroundColor Yellow
        return
    }
    
    if($Dns.Length -eq 0)
    {
        Write-Host "Required parameter -Dns not set...terminating script" -ForegroundColor Yellow
        return
    }
    
    if($Location.Length -eq 0)
    {
        Write-Host "Required parameter -Location not set...terminating script" -ForegroundColor Yellow
        return
    }
    
    if($StorageAcctName.Length -eq 0)
    {
        Write-Host "Required parameter -StorageAcctName not set...terminating script" -ForegroundColor Yellow
        return
    }
    
    $apiKey1 = New-RandomKey(16)
    $apiKey2 = New-RandomKey(16)
    $apiCodes = $apiKey1 + ";" + $apiKey2
    $apiSymmetricKey = New-RandomKey(32)
    $symmetricKey = New-RandomKey(32)
    
    if($ClusterName.Length -eq 0)
    {
        $ClusterName = "piraeuscluster"
    }
    
    if($StorageAcctName.Length -eq 0)
    {
        $storageAcctName = New-RandomStorageAcctName
    }
    else
    {
        $storageAcctName = $StorageAcctName
    }
    
    if($FrontendVMSize.Length -eq 0)
    {
        $frontendVMSize = "Standard_D2s_v3"
    }
    else
    {
        $frontendVMSize = $FrontendVMSize
    }
    
    if($OrleansVMSize.Length -eq 0)
    {
        $orleansVMSize = "Standard_D4s_v3"
    }
    else
    {
        $orleansVMSize = $OrleansVMSize
    }
    
    

    $config = Get-Content -Raw -Path "./deploy.json" | ConvertFrom-Json    
    $config.storageAcctName = $storageAcctName
    $config.resourceGroupName = $ResourceGroupName
    $config.subscriptionName = $SubscriptionName
    $config.location = $Location
    $config.email = $Email
    $config.dns = $Dns
    $config.apiSymmetricKey = $apiSymmetricKey
    $config.apiSecurityCodes = $apiCodes
    $config.symmetricKey = $symmetricKey
    $config.apiIssuer = "http://$Dns.$Location.cloudapp.azure.com/mgmt"
    $config.apiAudience = $config.apiIssuer
    $config.tokenType = "JWT"
    $config.identityClaimType = "http://$Dns.$Location.cloudapp.azure.com/name"
    $config.issuer = "http://$Dns.$Location.cloudapp.azure.com/"
    $config.audience = $config.issuer
    $config.coapAuthority = "http://$Dns.$Location.cloudapp.azure.com"
    $config.frontendVMSize = $frontendVMSize
    $config.orleansVMSize  = $orleansVMSize
    $config.nodeCount = 1
    $config.clusterName = $ClusterName
    $config.logLevel = $LogLevel
    
    
    $email = $config.email
    $dnsName = $config.dns
    $location = $config.location
    $resourceGroupName = $config.resourceGroupName
    $subscriptionNameOrId = $config.subscriptionName
    $clusterName = $config.clusterName
    $nodeCount = $config.nodeCount
    $apiIssuer = $config.apiIssuer            
    $apiAudience = $config.apiAudience
    $apiSymmetricKey = $config.apiSymmetricKey
    $apiSecurityCodes = $config.apiSecurityCodes
    $identityClaimType = $config.identityClaimType
    $issuer = $config.issuer
    $audience = $config.audience
    $symmetricKey = $config.symmetricKey
    $tokenType = $config.tokenType
    $coapAuthority = $config.coapAuthority
    $frontendVMSize = $config.frontendVMSize
    $orleansVMSize = $config.orleansVMSize
    $logLevel = $LogLevel
    
    if($AppID.Length -ne 0)
    {
        $appId = $AppID
    }
    
    if($Password.Length -ne 0)
    {
        $pwd = $Password
    }    

    $step = 1
    
    az extension show -n application-insights
    
    if($LASTEXITCODE -ne 0)
    {
        Write-Host "-- Step $step - Add Azure CLI extension for Application Insights" -ForegroundColor Green
        az extension add -n application-insights -y
        $step++
    }

    New-CleanUpK8Deployment "$clusterName" "$resourceGroupName"
    
    #Delete the AKS cluster if exists
    $clusterLine = az aks list --query "[?contains(name, '$clusterName')]" --output table
    if($clusterLine.Length -gt 0)
    {
        Write-Host "-- Step $step - Deleting old AKS cluster $clusterName" -ForegroundColor Green
        az aks delete --name $clusterName --resource-group $resourceGroupName --yes
        $step++
    }
    
    #read the file and see if service principal exists
    #if not create one and update the file
    
    
    
    if($appId -eq $null -or $appId.Length -eq 0)
    {
        #create the service principal
        Write-Host "-- Step $step - Creating service principal" -ForegroundColor Green
        $creds = az ad sp create-for-rbac  --skip-assignment
        $credsObj = ConvertFrom-Json -InputObject "$creds"
        $appId = $credsObj.appId
        $pwd = $credsObj.password
        $config.appId = $credsObj.appId
        $config.pwd = $credsObj.password
    }
    else
    {
        $config.appId = $appId
        $config.pwd = $pwd        
    }

    #$dateTimeString = Get-Date -Format "MM-dd-yyyyTHH-mm-ss"
    #$filename = "./deploy-" + $dateTimeString + ".json"
    #$config | ConvertTo-Json -depth 100 | Out-File $filename
    
    $env:AZURE_HTTP_USER_AGENT='pid-332e88b9-31d3-5070-af65-de3780ad5c8b'
    
    #Set the subscription
    Write-Host "-- Step $step - Setting subscription $subscriptionNameOrId" -ForegroundColor Green
    az account set --subscription "$subscriptionNameOrId"
    if($LASTEXITCODE -ne 0)
    {
        Write-Host "Subscription - $subscriptionNameOrId could not be set" -ForegroundColor Yellow
        Write-Host "Exiting script" -ForegroundColor Yellow
        return;
    }
    $step++
    
    #check if the Resource Group exists
    $rgoutcome = az group exists --name $resourceGroupName
    
    if($rgoutcome -eq "false")
    {
        Write-Host "-- Step $step - Create resource group '$ResourceGroupName'" -ForegroundColor Green
        az group create --name $resourceGroupName --location $location 
        $step++
    }    
    
    #Check if the Orleans storage account exists
    $saLine= az storage account check-name --name $storageAcctName
    if(-Not $saLine[2].Contains("true"))
    {
        Write-Host "-- Step $step - Cleaning up orleans storage account for new deployment" -ForegroundColor Green
        #$tempCS1 = az storage account show-connection-string --name $storageAcctName --resource-group $resourceGroupName
        az storage container delete --name grainstate  --account-name $storageAcctName         
        az storage table delete --name OrleansSiloInstances --account-name $storageAcctName 
        az storage table delete --name 'MetricsHourPrimaryTransactionsBlob' --account-name $storageAcctName 
        az storage table delete --name 'MetricsHourPrimaryTransactionsFile' --account-name $storageAcctName 
        az storage table delete --name 'MetricsHourPrimaryTransactionsQueue' --account-name $storageAcctName 
        az storage table delete --name 'MetricsHourPrimaryTransactionsTable' --account-name $storageAcctName
        #Write-Host "-- Step $step - Deleting orleans storage account" -ForegroundColor Green
        #az storage account delete --name $storageAcctName --resource-group $resourceGroupName --yes
        $step++
    }
    else
    {
        #create the storage account
        Write-Host "-- Step $step - Creating orleans storage account" -ForegroundColor Green
        az storage account create --location $location --name $storageAcctName --resource-group $resourceGroupName --sku "Standard_LRS"
        $step++
    } 
    
     #Check if the Audit storage account exists
    $auditStorageAcctName = $storageAcctName + "audit"
    $asaLine= az storage account check-name --name $auditStorageAcctName 
    if(-Not $asaLine[2].Contains("true"))
    {
        #delete the storage account
        #Write-Host "-- Step $step - Deleting audit storage account" -ForegroundColor Green
        #az storage account delete --name $auditStorageAcctName --resource-group $resourceGroupName --yes
        Write-Host "-- Step $step - Cleaning up audit storage account for new deployment" -ForegroundColor Green
        az storage table delete --name 'MetricsHourPrimaryTransactionsBlob' --account-name $auditStorageAcctName 
        az storage table delete --name 'MetricsHourPrimaryTransactionsFile' --account-name $auditStorageAcctName 
        az storage table delete --name 'MetricsHourPrimaryTransactionsQueue' --account-name $auditStorageAcctName 
        az storage table delete --name 'MetricsHourPrimaryTransactionsTable' --account-name $auditStorageAcctName
        $step++
    }
    else
    {    
        #create the storage account
        Write-Host "-- Step $step - Creating audit storage account" -ForegroundColor Green
        az storage account create --location $location --name $auditStorageAcctName --resource-group $resourceGroupName --sku "Standard_LRS"
        $step++
    }
    
    #Get the credentials for the storage accounts
    #Get the credentials for the storage accounts
    $storageJsonString = az storage account show-connection-string --name $storageAcctName --resource-group $resourceGroupName
    $storageObj = ConvertFrom-Json -InputObject "$storageJsonString"
    $dataConnectionString = $storageObj.connectionString
    
    $auditStorageJsonString = az storage account show-connection-string --name $auditStorageAcctName --resource-group $resourceGroupName
    $auditStorageObj = ConvertFrom-Json -InputObject "$auditStorageJsonString"
    $auditConnectionString = $auditStorageObj.connectionString
    
    $dateTimeString = Get-Date -Format "MM-dd-yyyyTHH-mm-ss"
    $filename = "./deploy-" + $dateTimeString + ".json"
    $config | ConvertTo-Json -depth 100 | Out-File $filename
    
    #create AKS cluster
    Write-Host "-- Step $step - Create AKS cluster" -ForegroundColor Green    
    az aks create --resource-group $resourceGroupName --name $clusterName --node-count $nodeCount --service-principal $appId --client-secret $pwd --node-vm-size $frontendVMSize --generate-ssh-keys
    $step++

    #get AKS credentials
    Write-Host "-- Step $step - Get AKS credentials" -ForegroundColor Green
    Get-AksCredentials $resourceGroupName $clusterName
    $step++
    
    #apply RBAC
    Write-Host "-- Step $step - Apply kubectl RBAC" -ForegroundColor Green
    Set-ApplyYaml "./helm-rbac.yaml" "kube-system"
    $step++
    
    #initialize tiller with helm
    Write-Host "-- Step $step - Intialize tiller" -ForegroundColor Green
    helm init --service-account tiller
    Write-Host "...waiting 45 seconds for Tiller to start" -ForegroundColor Yellow
    Start-Sleep -Seconds 45
    $step++
    
    Write-Host "-- Step $step - Label existing node in cluster pool=nodepool1" -ForegroundColor Green
    Set-NodeLabel "nodepool1" "pool" "nodepool1"
    $step++
    
    Write-Host "-- Step $step - Creating namespace for cert-manager" -ForegroundColor Green
    kubectl create namespace cert-manager
    $step++

    Write-Host "-- Step $step - Disabling validation on cert-manager" -ForegroundColor Green
    kubectl label namespace cert-manager certmanager.k8s.io/disable-validation="true"
    $step++

    Write-Host "-- Step $step - Getting cert-manager CRDs" -ForegroundColor Green
    #Set-ApplyYaml "https://raw.githubusercontent.com/jetstack/cert-manager/release-0.11/deploy/manifests/00-crds.yaml" "cert-manager"
    kubectl apply -f "https://raw.githubusercontent.com/jetstack/cert-manager/release-0.11/deploy/manifests/00-crds.yaml" -n "cert-manager" --validate=false
    $step++
    
    Write-Host "-- Step $step - Adding Jetstack Helm repository " -ForegroundColor Green
    helm repo add jetstack https://charts.jetstack.io
    helm repo update

    Write-Host "-- Step $step - Installing cert-manager" -ForegroundColor Green
    helm install --name cert-manager --namespace cert-manager --version v0.11.0 --set ingressShim.extraArgs='{--default-issuer-name=letsencrypt-prod,--default-issuer-kind=ClusterIssuer}' jetstack/cert-manager --set webhook.enabled=true           
    Write-Host "Wait 45 seconds for cert-manager to initialize" -ForegroundColor Yellow
    Start-Sleep -Seconds 45
    $step++

    Write-Host "-- Step $step - Applying the certificate issuer" -ForegroundColor Green
    Copy-Item -Path "./issuer.yaml" -Destination "./issuer-copy.yaml"
    Update-Yaml -newValue $email -matchString "EMAILREF" -filename "./issuer-copy.yaml"            
    kubectl apply -f ./issuer-copy.yaml -n kube-system
    Write-Host "Wait 30 seconds for issuer to initialize"
    Start-Sleep -Seconds 30
    Remove-Item -Path "./issuer-copy.yaml"
    $step++

    Write-Host "-- Step $step - Adding NGINX Helm repository" -ForegroundColor Green
    helm repo update NGINX 
    $step++

    Write-Host "-- Step $step - Installing NGINX ingress controller" -ForegroundColor Green
    New-InstallNGINX
    #helm install stable/nginx-ingress --namespace kube-system --set controller.replicaCount=1
    Write-Host "Wait 45 seconds for nginx to initialize"
    Start-Sleep -Seconds 45
    $step++

    Write-Host "-- Step $step - NGINX ingress controller's external IP" -ForegroundColor Green
    $IP = Get-ExternalIP            
    Write-Host "Got external IP = $IP" -ForegroundColor Yellow
 
    # Get the resource-id of the public ip
    $PUBLICIPID=$(az network public-ip list --query "[?ipAddress!=null]|[?contains(ipAddress, '$IP')].[id]" --output tsv)
    Write-Host "PublicIPID = $PUBLICIPID" -ForegroundColor Yellow
    $step++

    #update the azure network with the public IP ID
    Write-Host "-- Step $step - Update Azure Network with Public IP ID" -ForegroundColor Green
    if($subscriptionNameOrId.Length -ne 0)
    {
      az network public-ip update --ids $PUBLICIPID --dns-name $dnsName --subscription $subscriptionNameOrId
    }
    else
    {
      az network public-ip update --ids $PUBLICIPID --dns-name $dnsName
    }
    $step++
    
    
    #update and apply certificate with DNS and location
    Write-Host "-- Step $step - Update and apply the certificate" -ForegroundColor Green
    Copy-Item -Path "./certificate.yaml" -Destination "./certificate-copy.yaml"
    Update-Yaml -newValue $dnsName -matchString "INGRESSDNS" -filename "./certificate-copy.yaml"
    Update-Yaml -newValue $location -matchString "LOCATION" -filename "./certificate-copy.yaml"
    Set-ApplyYaml "./certificate-copy.yaml" -n "cert-manager"
    Remove-Item -Path "./certificate-copy.yaml"
    $step++

    #add orleans VM to a new node pool
    Write-Host "-- Step $step - Adding Orleans node to node pool" -ForegroundColor Green
    az aks nodepool add --resource-group $resourceGroupName --cluster-name $clusterName --name "nodepool2" --node-count $nodeCount --node-vm-size $orleansVMSize
    Write-Host "Waiting 60 seconds for node to initialize"
    Start-Sleep -Seconds 60
    $step++
    
    #label the new node pool
    Write-Host "-- Step $step - Label orleans node in cluster pool=nodepool2" -ForegroundColor Green
    Set-NodeLabel "nodepool2" "pool" "nodepool2"
    $step++

    
    

    Write-Host ("K8 api services online...let's deploy and finish up") -ForegroundColor Green

    $siloAIKey = Get-InstrumentationKey "$Dns-silo" $resourceGroupName $location
    #apply the piraeus silo helm chart
    Write-Host "-- Step $step - Deploying helm chart for piraeus-silo" -ForegroundColor Green
    helm install ./piraeus-silo --name piraeus-silo --namespace kube-system --set dataConnectionString=$dataConnectionString --set instrumentationKey=$siloAIKey --set logLevel=$logLevel
    if($LASTEXITCODE -ne 0 )
    {
        New-WaitForApiServices
        helm install ./piraeus-silo --name piraeus-silo --namespace kube-system --set dataConnectionString=$dataConnectionString --set instrumentationKey=$siloAIKey --set logLevel=$logLevel
    }
    $step++

    $mgmtAIKey = Get-InstrumentationKey "$Dns-api" $resourceGroupName $location
    $tagain = $false
    Write-Host "-- Step $step - Deploying helm chart for piraeus management api" -ForegroundColor Green
    helm install ./piraeus-mgmt-api --namespace kube-system --set dataConnectionString="$dataConnectionString"  --set managementApiIssuer="$apiIssuer" --set managementApiAudience="$apiAudience" --set managmentApiSymmetricKey="$apiSymmetricKey" --set managementApiSecurityCodes="$apiSecurityCodes" --set instrumentationKey=$mgmtAIKey --set logLevel=$logLevel
    if($LASTEXITCODE -ne 0 )
    {
        New-WaitForApiServices
        helm install ./piraeus-mgmt-api --namespace kube-system --set dataConnectionString="$dataConnectionString"  --set managementApiIssuer="$apiIssuer" --set managementApiAudience="$apiAudience" --set managmentApiSymmetricKey="$apiSymmetricKey" --set managementApiSecurityCodes="$apiSecurityCodes" --set instrumentationKey=$mgmtAIKey --set logLevel=$logLevel
    }
    
    $step++
    
    $websocketAIKey = Get-InstrumentationKey "$Dns-websocket" $resourceGroupName $location
    Write-Host "-- Step $step - Deploying helm chart for piraeus front end" -ForegroundColor Green
    helm install ./piraeus-websocket --namespace kube-system --set dataConnectionString="$dataConnectionString" --set auditConnectionString="$auditConnectionString" --set clientIdentityNameClaimType="$identityClaimType" --set clientIssuer="$issuer" --set clientAudience="$audience" --set clientTokenType="$tokenType" --set clientSymmetricKey="$symmetricKey" --set coapAuthority="$coapAuthority" --set instrumentationKey=$websocketAIKey --set logLevel=$logLevel 
    if($LASTEXITCODE -ne 0 )
    {
        New-WaitForApiServices
        helm install ./piraeus-websocket --namespace kube-system --set dataConnectionString="$dataConnectionString" --set auditConnectionString="$auditConnectionString" --set clientIdentityNameClaimType="$identityClaimType" --set clientIssuer="$issuer" --set clientAudience="$audience" --set clientTokenType="$tokenType" --set clientSymmetricKey="$symmetricKey" --set coapAuthority="$coapAuthority" --set instrumentationKey=$websocketAIKey --set logLevel=$logLevel 
    }
    $step++
    
    #update the ingress controller with the routing data and dns
    Write-Host "-- Step $step - Apply update to NGINX ingress controller" -ForegroundColor Green
    Copy-Item -Path "./ingress.yaml" -Destination "./ingress-copy.yaml"
    Update-Yaml -newValue $dnsName -matchString "INGRESSDNS" -filename "./ingress-copy.yaml"
    Update-Yaml -newValue $location -matchString "LOCATION" -filename "./ingress-copy.yaml"
    Set-ApplyYaml "./ingress-copy.yaml" "kube-system"
    Remove-Item -Path "./ingress-copy.yaml"
    $step++
    
    Write-Host "---- OUTPUTS -----" -ForegroundColor Cyan
    Write-Host "Hostname - $dnsName.$location.cloudapp.azure.com" -ForegroundColor Magenta
    Write-Host "Application ID - $appId" -ForegroundColor Magenta
    Write-Host "Password - $pwd" -ForegroundColor Magenta
    Write-Host "-------------------" -ForegroundColor Cyan
    Write-Host ""
    
    Write-Host "Waiting 60 seconds for new containers to start before configuring demo." -Foreground Yellow
    Start-Sleep -Seconds 60
    
    Write-Host "-- Step $step - Running Sample Configuration" -ForegroundColor Green
    Write-Host "---- Running Sample Configuration ---" -Foreground Cyan
    New-SampleConfig $Dns $Location $apiKey1
    $step++
    
   Write-Host "-- Step $step - Writing file for MQTT client configuration" -ForegroundColor Green   
   $config.email = $null
   $config.storageAcctName = $null
   $config.resourceGroupName = $null
   $config.subscriptionName = $null
   $config.appId = $null
   $config.pwd = $null
   $config.clusterName = $null
   $config.nodeCount = $null
   $config.apiIssuer = $null
   $config.apiAudience = $null
   $config.apiSymmetricKey = $null
   $config.apiSecurityCodes = $null
   $config.tokenType = $null
   $config.coapAuthority = $null
   $config.frontendVMSize = $null
   $config.orleansVMSize = $null
   $config.logLevel = $null
   
   $config | ConvertTo-Json -depth 100 | Out-File "./../src/Samples.Mqtt.Client/config.json"
   
   Write-Host "---- OUTPUTS -----" -ForegroundColor Cyan
   Write-Host "Hostname - $dnsName.$location.cloudapp.azure.com" -ForegroundColor Magenta
   Write-Host "Application ID - $appId" -ForegroundColor Magenta
   Write-Host "Password - $pwd" -ForegroundColor Magenta
   Write-Host "-------------------" -ForegroundColor Cyan
   Write-Host""
   Write-Host "Done :-) Dare Mighty Things" -ForegroundColor Cyan    
}

function Set-KubeApply
{
    param([string]$filename, [string]$ns)
    $looper = $true
    while($looper)
    {    
        kubectl apply -f $filename -n $ns
        if($LASTEXITCODE -ne 0)
        {
            Write-Host "Waiting 30 to re-apply file..." -ForegroundColor Yellow
            Start-Sleep -Seconds 30
        }
        else
        {
            $looper = $false
        }
    }
}

function New-InstallNGINX
{
    $looper = $true
    while($looper)
    {
        try
        {
            helm install stable/nginx-ingress --namespace kube-system --set controller.replicaCount=1
            if($LASTEXITCODE -ne 0 )
            {
                Write-Host "Error installing NGINX, waiting 20 seconds to try install NGINX again..." -ForegroundColor Yellow
                Start-Sleep -Seconds 20
            }
            else
            {
                $looper = $false
            }            
        }
        catch
        {
            Write-Host "Waiting 20 seconds to try install NGINX again..." -ForegroundColor Yellow
            Start-Sleep -Seconds 20
        }
    }
}

function New-WaitForApiServices
{
    $v = kubectl get apiservice
    $ft = $true
    while(([string]$v).IndexOf("False") -ne -1)
    {
        if($ft)
        {
            Write-Host("K8 metrics-server and/or cert-manager-webhook is offline right now. We'll keep waiting until they are online") -ForegroundColor Yellow
            $ft = $false
        }
        else
        {
            Write-Host("Waiting 60 secs for the K8 apiservices to come back online, yuck...") -ForegroundColor Yellow
        }
        Start-Sleep -Seconds 60
        $v = kubectl get apiservice
    }
}

function Get-AksCredentials
{
    param([string]$rgn, [string]$cn)

    $looper = $true
    while($looper)
    {
        try
        {         
            az aks get-credentials --resource-group $rgn --name $cn            
            $looper = $false
        }
        catch
        {
            Write-Host "Waiting 30 seconds to try get aks credentials again..." -ForegroundColor Yellow
            Start-Sleep -Seconds 30
        }    
    }
}



function Get-ExternalIP
{
    $looper = $TRUE
    while($looper)
    {   $externalIP = ""                  
        $lineValue = kubectl get service -l app=nginx-ingress --namespace kube-system
        
        Write-Host "Last Exit Code for get external ip $LASTEXITCODE" -ForegroundColor White
        if($LASTEXITCODE -ne 0 )
        {
            Write-Host "Try get external ip...waiting 30 seconds" -ForegroundColor Yellow
            Start-Sleep -Seconds 30
        }  
        elseif($lineValue.Length -gt 0)
        {
            $line = $lineValue[1]
            $lineout = $line -split '\s+'
            $externalIP = $lineout[3]              
        }
        
              
        if($externalIP -eq "<pending>")
        {        
            Write-Host "External IP is pending...waiting 30 seconds" -ForegroundColor Yellow
            Start-Sleep -Seconds 30
        }
        elseif($externalIP.Length -eq 0)
        {
            Write-Host "External IP is zero length...waiting 30 seconds" -ForegroundColor Yellow
            Start-Sleep -Seconds 30
        }
        else
        {
            $looper = $FALSE
            Write-Host "External IP is $externalIP" -ForegroundColor Magenta
            return $externalIP
        }
    }
}

#---- functions ----
function Set-NodeLabel
{
    param([string]$nodeMatchValue, [string]$key, [string]$value)
    
    $looper = $true
    while($looper)
    {    
        $nodes = kubectl get nodes
        if($LASTEXITCODE -ne 0)
        {
            Write-Host "Waiting 10 seconds to get nodes from kubectl..." -ForegroundColor Yellow
            Start-Sleep -Seconds 10
        }
        else
        {
            foreach($node in $nodes)
            {
               $nodeVal = $node.Split(" ")[0]
               if($nodeVal.Contains($nodeMatchValue))
               {
                    kubectl label nodes $nodeVal "$key=$value"
                    if($LASTEXITCODE -ne 0)
                    {
                        Write-Host "Set node label failed. Waiting 10 seconds to try again..." -ForegroundColor Yellow
                        Start-Sleep -Seconds 10
                    }
                    else
                    {
                        $looper = $false
                    }
               }
            }
        }
    }
}

function Set-ApplyYaml
{
    param([string]$file, [string]$ns)

    $looper = $true
    while($looper)
    {
        kubectl apply -f $file -n $ns
        if($LASTEXITCODE -ne 0)
        {
            Write-Host "kubectl apply failed for $file. Waiting 10 seconds to try again..." -ForegroundColor Yellow
            Start-Sleep -Seconds 10
        }
        else
        {
            $looper = $false
        }
    }
}

function Update-Yaml
{
    Param ([string]$newValue, [string]$matchString, [string]$filename)

    (Get-Content $filename) -replace $matchString,$newValue | out-file $filename -Encoding ascii
}

#---- end functions


function New-CleanUpK8Deployment
{
    param([string]$cName, [string]$rgName)

    #Remove previous deployments from kubectl
    $cleanup = Read-Host "Clean up previous kubectl deployment [y/n] ? "
    if($cleanup.ToLowerInvariant() -eq "y")
    {
        $cleanupClusterName = Read-Host "Enter previous cluster name [Enter blank == $cName] "
        $cleanupResourceGroup = Read-Host "Enter previous resource group name [Enter blank == $rgName] "
        
        if($cleanupClusterName.Length -eq 0)
        {
            $cleanupClusterName = $cName
        }
        
        if($cleanupResourceGroup.Length -eq 0)
        {
            $cleanupResourceGroup = $rgName
        }
        
        $condition1 = "users.clusterUser_" + $cleanupResourceGroup + "_" + $cleanupClusterName
        $condition2 = "clusters." + $cleanupClusterName
        kubectl config unset $condition1
        kubectl config unset $condition2
    }
}

function New-RandomKey       
{
    param([int]$Length)
    
    $random = new-Object System.Random
    $buffer = [System.Byte[]]::new($Length)
    $random.NextBytes($buffer)
    $stringVar = [Convert]::ToBase64String($buffer)
    if($stringVar.Contains("+") -or $stringVar.Contains("/"))
    {
        return New-RandomKey($Length)
    }
    else
    {
        return $stringVar
    }
}


function New-RandomStorageAcctName
{
    $alpha = "abcdefghijklmnopqrstuvwxyz"
    $alpha2 = "0123456789"
    $array = $alpha.ToCharArray()
    $array2 = $alpha2.ToCharArray()
    $maxLength = 6
    $random = new-Object System.Random
    $randonString = ""
    $dummy = $null
    For ($i=0; $i -lt $maxLength; $i++)  
    {
        $index = $random.Next($alpha.Length)
        $randomString += $array[$index] 
    }
    
        
    $maxLength = 2
    For ($i=0; $i -lt $maxLength; $i++)  
    {
        $index = $random.Next($alpha2.Length)
        $randomString += $array2[$index] 
    }
    
    return $randomString
}

function Get-InstrumentationKey
{
    param([string]$appName, [string]$rg, [string]$loc)

    $jsonAppString = az monitor app-insights component show --app $appName -g $rg
    $host.ui.RawUI.ForegroundColor = 'Gray'    
    if($LASTEXITCODE -ne 0)
    {
        Write-Host "-- Step $step - Creating App Insights $appName" -ForegroundColor Green
            
        $jsonAppString = az monitor app-insights component create -a $appName -l $loc -k other -g $rg --application-type other
        $step++       
    }

    $appKey = New-RandomKey(8)
    $jsonKeyString = az monitor app-insights api-key create --api-key $appKey -g $resourceGroupName -a $appName
    $keyObj = ConvertFrom-Json -InputObject "$jsonKeyString"
    $instrumentationKey = $keyObj.apiKey    
    $host.ui.RawUI.ForegroundColor = 'Gray'
    return $instrumentationKey
}


function New-SampleConfig
{
    param([string]$DnsName, [string]$Location, [string]$Key)
    
    $authority = $DnsName.ToLower() + "." + $Location.ToLower() + ".cloudapp.azure.com"
    $url = "https://$authority"
    Write-Host "Using $url for management api" -ForegroundColor Yellow
   
    Write-Host "Module imported" -ForegroundColor Yellow

    #get a security token for the management API
    Write-Host "--- Get security token for Piraeus configuration ---" -Foreground Yellow
    $token = Get-PiraeusManagementToken -ServiceUrl $url -Key $Key
    while($LASTEXITCODE -ne 0)
    {
        Write-Host "--- Try get security token again...waiting 30 seconds" -ForegroundColor Yellow
        Start-Sleep -Seconds 30
        $token = Get-PiraeusManagementToken -ServiceUrl $url -Key $Key
    }
    
    Write-Host "--- Got security token, ready to configure Piraeus ---" -ForegroundColor Green
    
        
    Write-Host "--- INFORMATION ABOUT Sample Config ----" -ForegroundColor White
    Write-Host "The client demos create security tokens based on the selection of a 'Role', i.e., 'A' or 'B'" -ForegroundColor White
    Write-Host "The script will create 2 CAPL policies" -ForegroundColor White
    Write-Host " (1) a client in role 'A' may transmit to 'resource-a' and subscribe to 'resource-b'" -ForegroundColor White
    Write-Host " (2) a client in role 'B' may transmit to 'resource-b' and subscribe to 'resource-a'" -ForegroundColor White
    Write-Host "-----------------------------------------" -ForegroundColor White
    Write-Host ""
    Start-Sleep -Seconds 1
        
    #--------------- CAPL policy for users in role "A" ------------------------
    Write-Host "-- Building CAPL Authorization Policies ---" -ForegroundColor White
    Write-Host " (1) Match Expression : Find a claim type in the security token" -ForegroundColor White
    Write-Host " (2) Operation -- Binds a claim value from the matched claim type to perform an operation, e.g., Equals" -ForegroundColor White
    Write-Host " (3) Rule -- Create a rule that binds a match expression and an operation" -ForegroundColor White
    Write-Host " (4) Policy -- create a policy that is uniquely identifiable, that incorporates a Rule (or Logical Connective)" -ForegroundColor White
    Write-Host ""
    Start-Sleep -Seconds 1              

    #define the claim type to match to determines the client's role
    $authority = $DnsName.ToLower() + "." + $Location.ToLower() + "." + "cloudapp.azure.com"
    $matchClaimType = "http://$authority/role"

    #create a match expression of 'Literal' to match the role claim type
    $match = New-CaplMatch -Type Literal -ClaimType $matchClaimType -Required $true  

    #create an operation to check the match claim value is 'Equal' to "A"
    $operation_A = New-CaplOperation -Type Equal -Value "A"

    #create a rule to bind the match expression and operation
    $rule_A = New-CaplRule -Evaluates $true -MatchExpression $match -Operation $operation_A

    #define a unique identifier (as URI) for the policy
    $policyId_A = "http://$authority/policy/resource-a" 

    #create the policy for clients in role "A"
    $policy_A = New-CaplPolicy -PolicyID $policyId_A -EvaluationExpression $rule_A
    #-------------------End Policy for "B"------------------------------------
        
        
    #--------------- CAPL policy for users in role "B" ------------------------

    #create an operation to check the match claim value is 'Equal' to "B"
    $operation_B = New-CaplOperation -Type Equal -Value "B"

    #create a rule to bind the match expression and operation
    $rule_B = New-CaplRule -Evaluates $true -MatchExpression $match -Operation $operation_B

    #define a unique identifier (as URI) for the policy
    $policyId_B = "http://$authority/policy/resource-b" 

    #create the policy for users in role "A"
    $policy_B = New-CaplPolicy -PolicyID $policyId_B -EvaluationExpression $rule_B

    #-------------------End Policy for "B"------------------------------------

    # The policies are completed. We need to add them to Piraeus

    Add-CaplPolicy -ServiceUrl $url -SecurityToken $token -Policy $policy_A 
    Add-CaplPolicy -ServiceUrl $url -SecurityToken $token -Policy $policy_B

    Write-Host "CAPL policies added to Piraeus" -ForegroundColor Yellow


    #Uniquely identify Piraeus resources by URI
    $resource_A = "http://$authority/resource-a"
    $resource_B = "http://$authority/resource-b"

    #Add the resources to Piraeus

    #Resource "A" lets users with role "A" send and users with role "B" subscribe to receive transmissions
    Add-PiraeusEventMetadata -ResourceUriString $resource_A -Enabled $true -RequireEncryptedChannel $false -PublishPolicyUriString $policyId_A -SubscribePolicyUriString $policyId_B -ServiceUrl $url -SecurityToken $token -Audit $false

    #Resource "B" lets users with role "B" send and users with role "A" subscribe to receive transmissions
    Add-PiraeusEventMetadata -ResourceUriString $resource_B -Enabled $true -RequireEncryptedChannel $false -PublishPolicyUriString $policyId_B -SubscribePolicyUriString $policyId_A -ServiceUrl $url -SecurityToken $token -Audit $false

    Write-Host "PI-System metadata added to Piraeus" -ForegroundColor Yellow
    Write-Host""


        
    #Quick check get the resource data and verify what was set
    Write-Host "----- PI-System $resource_A Metadata ----" -ForegroundColor Green
    Get-PiraeusEventMetadata -ResourceUriString $resource_A -ServiceUrl $url -SecurityToken $token
    

    Write-Host""


    Write-Host "----- PI-System $resource_B Metadata ----" -ForegroundColor Green
    Get-PiraeusEventMetadata -ResourceUriString $resource_B -ServiceUrl $url -SecurityToken $token
}

function New-PiraeusCleanup
{
#cleanup script for source check in

    $path1 = "./deploy.json"
    $deploy = Get-Content -Raw -Path $path1 | ConvertFrom-Json
    $deploy.email = ""
    $deploy.dnsName = ""
    $deploy.location = ""
    $deploy.storageAcctName = ""
    $deploy.resourceGroupName = ""
    $deploy.subscriptionNameOrId = ""
    $deploy.appId = ""
    $deploy.pwd = ""
    $deploy.clusterName = "piraeuscluster"
    $deploy.nodeCount = 1
    $deploy.apiIssuer = "http://skunklab.io/mgmt"
    $deploy.apiAudience = "http://skunklab.io/mgmt"
    $deploy.apiSymmetricKey = "//////////////////////////////////////////8="
    $deploy.apiSecurityCodes = "12345678;87654321"
    $deploy.identityClaimType = "http://skunklab.io/name"
    $deploy.issuer = "http://skunklab.io/"
    $deploy.audience = "http://skunklab.io/"
    $deploy.symmetricKey = "//////////////////////////////////////////8="
    $deploy.tokenType = "JWT"
    $deploy.coapAuthority = "skunklab.io"
    $deploy.frontendVMSize = "Standard_D2s_v3"
    $deploy.orleansVMSize = "Standard_D4s_v3"
    $deploy | ConvertTo-Json -depth 100 | Out-File $path1


    $path2 = "../src/Samples.Mqtt.Client/config.json"
    $sampleConfig = Get-Content -Raw -Path $path2 | ConvertFrom-Json
    $sampleConfig.email = ""
    $sampleConfig.dnsName = ""
    $sampleConfig.location = ""
    $sampleConfig.storageAcctName = ""
    $sampleConfig.resourceGroupName = ""
    $sampleConfig.subscriptionNameOrId = ""
    $sampleConfig.appId = $null

    $sampleConfig.appId = $null
    $sampleConfig.pwd = $null
    $sampleConfig.clusterName = $null
    $sampleConfig.nodeCount = $null
    $sampleConfig.apiIssuer = $null
    $sampleConfig.apiAudience = $null
    $sampleConfig.apiSymmetricKey = $null
    $sampleConfig.apiSecurityCodes = $null
    $sampleConfig.identityClaimType = $null
    $sampleConfig.issuer = $null
    $sampleConfig.audience = $null
    $sampleConfig.symmetricKey = $null
    $sampleConfig.tokenType = $null
    $sampleConfig.coapAuthority = $null
    $sampleConfig.frontendVMSize = $null
    $sampleConfig.orleansVMSize = $null

    $sampleConfig | ConvertTo-Json -depth 100 | Out-File $path2 
  
}