functions/LoadBalancer/SetupLoadBalancer.ps1

<#
  .SYNOPSIS
  SetupLoadBalancer
   
  .DESCRIPTION
  SetupLoadBalancer
   
  .INPUTS
  SetupLoadBalancer - The name of SetupLoadBalancer
 
  .OUTPUTS
  None
   
  .EXAMPLE
  SetupLoadBalancer
 
  .EXAMPLE
  SetupLoadBalancer
 
 
#>

function SetupLoadBalancer() {
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string] $baseUrl
        ,
        [Parameter(Mandatory = $true)]
        [ValidateNotNull()] 
        $config
        ,
        [Parameter(Mandatory = $true)]
        [ValidateNotNull()]
        [bool] $local
    )

    Write-Verbose 'SetupLoadBalancer: Starting'

    $AKS_IP_WHITELIST = ""

    LoginToAzure

    SetCurrentAzureSubscription -subscriptionName $($config.azure.subscription)

    # $AKS_SUBSCRIPTION_ID = $userInfo.AKS_SUBSCRIPTION_ID
    # $IS_CAFE_ENVIRONMENT = $userInfo.IS_CAFE_ENVIRONMENT

    $AKS_PERS_RESOURCE_GROUP = $config.azure.resourceGroup
    $AKS_PERS_LOCATION = $config.azure.location

    # Get location name from resource group
    $AKS_PERS_LOCATION = (Get-AzureRmResourceGroup -Name "$AKS_PERS_RESOURCE_GROUP").Location
    Write-Host "Using location: [$AKS_PERS_LOCATION]"

    $customerid = $config.customerid

    if ([string]::IsNullOrWhiteSpace($customerid)) {
        # https://kevinmarquette.github.io/2017-04-10-Powershell-exceptions-everything-you-ever-wanted-to-know/
        throw "customerid is null in config"
    }

    $customerid = $customerid.ToLower().Trim()
    Write-Host "Customer ID: $customerid"

    $ingressExternalType = $config.ingress.external.type
    $ingressInternalType = $config.ingress.internal.type

    if ([string]::IsNullOrWhiteSpace($ingressExternalType)) {
        # https://kevinmarquette.github.io/2017-04-10-Powershell-exceptions-everything-you-ever-wanted-to-know/
        throw "ingress.external.type is null in config"
    }
    if ([string]::IsNullOrWhiteSpace($ingressInternalType)) {
        throw "ingress.internal.type is null in config"
    }
    if ([string]::IsNullOrWhiteSpace($($config.dns.name))) {
        throw "dns.name is null in config"
    }

    $AKS_IP_WHITELIST = $config.ingress.external.whitelist

    # read the vnet and subnet info from kubernetes secret
    $AKS_VNET_NAME = $config.networking.vnet
    $AKS_SUBNET_NAME = $config.networking.subnet
    $AKS_SUBNET_RESOURCE_GROUP = $config.networking.subnet_resource_group

    Write-Host "Found vnet info from secret: vnet: $AKS_VNET_NAME, subnet: $AKS_SUBNET_NAME, subnetResourceGroup: $AKS_SUBNET_RESOURCE_GROUP"

    if ($ingressExternalType -eq "whitelist") {
        Write-Host "Whitelist: $AKS_IP_WHITELIST"

        SaveSecretValue -secretname whitelistip -valueName iprange -value "${AKS_IP_WHITELIST}"
    }

    Write-Host "Setting up Network Security Group for the subnet"

    # setup network security group
    $AKS_PERS_NETWORK_SECURITY_GROUP = "$($AKS_PERS_RESOURCE_GROUP.ToLower())-nsg"

    if ([string]::IsNullOrWhiteSpace($(Get-AzureRmNetworkSecurityGroup -Name $AKS_PERS_NETWORK_SECURITY_GROUP -ResourceGroupName "$AKS_PERS_RESOURCE_GROUP").Name)) {

        Write-Host "Creating the Network Security Group for the subnet"
        New-AzureRmNetworkSecurityGroup -Name $AKS_PERS_NETWORK_SECURITY_GROUP -ResourceGroupName $AKS_PERS_RESOURCE_GROUP
    }
    else {
        Write-Host "Network Security Group already exists: $AKS_PERS_NETWORK_SECURITY_GROUP"
    }

    if ($($config.network_security_group.create_nsg_rules)) {
        Write-Host "Adding or updating rules to Network Security Group for the subnet"
        $sourceTagForAdminAccess = "VirtualNetwork"
        if ($($config.allow_kubectl_from_outside_vnet)) {
            $sourceTagForAdminAccess = "Internet"
            Write-Host "Enabling admin access to cluster from Internet"
        }

        $sourceTagForHttpAccess = "Internet"
        if (![string]::IsNullOrWhiteSpace($AKS_IP_WHITELIST)) {
            $sourceTagForHttpAccess = $AKS_IP_WHITELIST
        }

        DeleteNetworkSecurityGroupRule -resourceGroup $AKS_PERS_RESOURCE_GROUP -networkSecurityGroup $AKS_PERS_NETWORK_SECURITY_GROUP -rulename "HttpPort"
        DeleteNetworkSecurityGroupRule -resourceGroup $AKS_PERS_RESOURCE_GROUP -networkSecurityGroup $AKS_PERS_NETWORK_SECURITY_GROUP -rulename "HttpsPort"

        SetNetworkSecurityGroupRule -resourceGroup $AKS_PERS_RESOURCE_GROUP -networkSecurityGroup $AKS_PERS_NETWORK_SECURITY_GROUP `
            -rulename "allow_kube_tls" `
            -ruledescription "allow kubectl and HTTPS access from ${sourceTagForAdminAccess}." `
            -sourceTag "${sourceTagForAdminAccess}" -port 443 -priority 100 

        SetNetworkSecurityGroupRule -resourceGroup $AKS_PERS_RESOURCE_GROUP -networkSecurityGroup $AKS_PERS_NETWORK_SECURITY_GROUP `
            -rulename "allow_http" `
            -ruledescription "allow HTTP access from ${sourceTagForAdminAccess}." `
            -sourceTag "${sourceTagForAdminAccess}" -port 80 -priority 101
          
        SetNetworkSecurityGroupRule -resourceGroup $AKS_PERS_RESOURCE_GROUP -networkSecurityGroup $AKS_PERS_NETWORK_SECURITY_GROUP `
            -rulename "allow_ssh" `
            -ruledescription "allow SSH access from ${sourceTagForAdminAccess}." `
            -sourceTag "${sourceTagForAdminAccess}" -port 22 -priority 104

        SetNetworkSecurityGroupRule -resourceGroup $AKS_PERS_RESOURCE_GROUP -networkSecurityGroup $AKS_PERS_NETWORK_SECURITY_GROUP `
            -rulename "allow_mysql" `
            -ruledescription "allow MySQL access from ${sourceTagForAdminAccess}." `
            -sourceTag "${sourceTagForAdminAccess}" -port 3306 -priority 205
          
        # if we already have opened the ports for admin access then we're not allowed to add another rule for opening them
        if (($sourceTagForHttpAccess -eq "Internet") -and ($sourceTagForAdminAccess -eq "Internet")) {
            Write-Host "Since we already have rules open port 80 and 443 to the Internet, we do not need to create separate ones for the Internet"
        }
        else {
            if ($($config.ingress.external) -ne "vnetonly") {
                SetNetworkSecurityGroupRule -resourceGroup $AKS_PERS_RESOURCE_GROUP -networkSecurityGroup $AKS_PERS_NETWORK_SECURITY_GROUP `
                    -rulename "HttpPort" `
                    -ruledescription "allow HTTP access from ${sourceTagForHttpAccess}." `
                    -sourceTag "${sourceTagForHttpAccess}" -port 80 -priority 500
  
                SetNetworkSecurityGroupRule -resourceGroup $AKS_PERS_RESOURCE_GROUP -networkSecurityGroup $AKS_PERS_NETWORK_SECURITY_GROUP `
                    -rulename "HttpsPort" `
                    -ruledescription "allow HTTPS access from ${sourceTagForHttpAccess}." `
                    -sourceTag "${sourceTagForHttpAccess}" -port 443 -priority 501
            }
        }

        $nsgid = az network nsg list --resource-group ${AKS_PERS_RESOURCE_GROUP} --query "[?name == '${AKS_PERS_NETWORK_SECURITY_GROUP}'].id" -o tsv
        Write-Host "Found ID for ${AKS_PERS_NETWORK_SECURITY_GROUP}: $nsgid"

        Write-Host "Setting NSG into subnet"
        az network vnet subnet update -n "${AKS_SUBNET_NAME}" -g "${AKS_SUBNET_RESOURCE_GROUP}" --vnet-name "${AKS_VNET_NAME}" --network-security-group "$nsgid" --query "provisioningState" -o tsv
    }

    # delete existing containers
    kubectl delete 'pods,services,configMaps,deployments,ingress' -l k8s-traefik=traefik -n kube-system --ignore-not-found=true


    # set Google DNS servers to resolve external urls
    # http://blog.kubernetes.io/2017/04/configuring-private-dns-zones-upstream-nameservers-kubernetes.html
    kubectl delete -f "$baseUrl/loadbalancer/dns/upstream.yaml" --ignore-not-found=true
    Start-Sleep -Seconds 10
    kubectl create -f "$baseUrl/loadbalancer/dns/upstream.yaml"
    # to debug dns: https://kubernetes.io/docs/tasks/administer-cluster/dns-custom-nameservers/#inheriting-dns-from-the-node

    kubectl delete ServiceAccount traefik-ingress-controller-serviceaccount -n kube-system --ignore-not-found=true

    if ($($config.ssl) ) {
        # if the SSL cert is not set in kube secrets then ask for the files
        # ask for tls cert files
        $AKS_SSL_CERT_FOLDER = $($config.ssl_folder)
        if ((!(Test-Path -Path "$AKS_SSL_CERT_FOLDER"))) {
            Write-Error "SSL Folder does not exist: $AKS_SSL_CERT_FOLDER"
        }     

        $AKS_SSL_CERT_FOLDER_UNIX_PATH = (($AKS_SSL_CERT_FOLDER -replace "\\", "/")).ToLower().Trim("/")    

        kubectl delete secret traefik-cert-ahmn -n kube-system --ignore-not-found=true

        if ($($config.ssl_merge_intermediate_cert)) {
            # download the intermediate certificate and append to certificate
            $intermediatecert = $(Invoke-WebRequest -UseBasicParsing -Uri "$baseUrl/intermediate.crt").Content
            $sitecert = Get-Content "$AKS_SSL_CERT_FOLDER\tls.crt" -Raw 

            $siteplusintermediatecert = $sitecert + $intermediatecert

            $siteplusintermediatecert | Out-File -FilePath "$AKS_SSL_CERT_FOLDER\tlsplusintermediate.crt"

            Write-Host "Storing TLS certs plus intermediate cert from $AKS_SSL_CERT_FOLDER_UNIX_PATH as kubernetes secret"
            kubectl create secret generic traefik-cert-ahmn -n kube-system --from-file="$AKS_SSL_CERT_FOLDER_UNIX_PATH/tlsplusintermediate.crt" --from-file="$AKS_SSL_CERT_FOLDER_UNIX_PATH/tls.key"
        }
        else {
            Write-Host "Storing TLS certs from $AKS_SSL_CERT_FOLDER_UNIX_PATH as kubernetes secret"
            kubectl create secret generic traefik-cert-ahmn -n kube-system --from-file="$AKS_SSL_CERT_FOLDER_UNIX_PATH/tls.crt" --from-file="$AKS_SSL_CERT_FOLDER_UNIX_PATH/tls.key"                
        }
    }
    else {
        Write-Host "SSL option was not specified in the deployment config: $($config.ssl)"
    }

    Write-Host "baseUrl: $baseUrl"

    $externalSubnetName = ""
    if ($($config.ingress.external.subnet)) {
        $externalSubnetName = $($config.ingress.external.subnet);
    }
    elseif ($($config.networking.subnet)) {
        $externalSubnetName = $($config.networking.subnet);
    }

    $externalIp = ""
    if ($($config.ingress.external.ipAddress)) {
        $externalIp = $($config.ingress.external.ipAddress);
    }
    elseif ("$($config.ingress.external.type)" -ne "vnetonly") {
        Write-Host "Setting up a public load balancer"

        $ipResourceGroup = $AKS_PERS_RESOURCE_GROUP
        $publicIpName = "IngressPublicIP"
        $externalip = Get-AzureRmPublicIpAddress -Name $publicIpName -ResourceGroupName $ipResourceGroup
        if ([string]::IsNullOrWhiteSpace($externalip)) {
            $publicIp = New-AzureRmPublicIpAddress -Name $publicIpName -ResourceGroupName ipResourceGroup -AllocationMethod Static -Location $AKS_PERS_LOCATION
            $externalip = Get-AzureRmPublicIpAddress -Name $publicIpName -ResourceGroupName $ipResourceGroup
        }  
        Write-Host "Using Public IP: [$externalip]"
    }

    $internalSubnetName = ""
    if ($($config.ingress.internal.subnet)) {
        $internalSubnetName = $($config.ingress.internal.subnet);
    }
    elseif ($($config.networking.subnet)) {
        $internalSubnetName = $($config.networking.subnet);
    }

    $internalIp = ""
    if ($($config.ingress.internal.ipAddress)) {
        $internalIp = $($config.ingress.internal.ipAddress);
    }

    LoadLoadBalancerStack -baseUrl $baseUrl -ssl $($config.ssl) `
        -ingressInternalType "$ingressInternalType" -ingressExternalType "$ingressExternalType" `
        -customerid $customerid -isOnPrem $false `
        -externalSubnetName "$externalSubnetName" -externalIp "$externalip" `
        -internalSubnetName "$internalSubnetName" -internalIp "$internalIp" `
        -local $local

    
    # setting up traefik
    # https://github.com/containous/traefik/blob/master/docs/user-guide/kubernetes.md

    Write-Verbose "Calling GetLoadBalancerIPs"
    $loadBalancerIPResult = GetLoadBalancerIPs
    $EXTERNAL_IP = $loadBalancerIPResult.ExternalIP
    $INTERNAL_IP = $loadBalancerIPResult.InternalIP
    Write-Verbose "Back from GetLoadBalancerIPs"

    if ($($config.ingress.fixloadbalancer)) {
        FixLoadBalancers -resourceGroup $AKS_PERS_RESOURCE_GROUP
    }

    # if($($config.ingress.loadbalancerconfig)){
    # MoveInternalLoadBalancerToIP -subscriptionId $($(GetCurrentAzureSubscription).AKS_SUBSCRIPTION_ID) -resourceGroup $AKS_PERS_RESOURCE_GROUP `
    # -subnetResourceGroup $config.ingress.loadbalancerconfig.subnet_resource_group -vnetName $config.ingress.loadbalancerconfig.vnet `
    # -subnetName $config.ingress.loadbalancerconfig.subnet -newIpAddress $config.ingress.loadbalancerconfig.privateIpAddress
    # }

    $dnsrecordname = $($config.dns.name)

    SaveSecretValue -secretname "dnshostname" -valueName "value" -value $dnsrecordname

    if ($($config.dns.create_dns_entries)) {
        SetupDNS -dnsResourceGroup $DNS_RESOURCE_GROUP -dnsrecordname $dnsrecordname -externalIP $EXTERNAL_IP 
    }
    else {
        Write-Host "To access the urls from your browser, add the following entries in your c:\windows\system32\drivers\etc\hosts file"
        Write-Host "$EXTERNAL_IP $dnsrecordname"
    }        

    Write-Verbose 'SetupLoadBalancer: Done'

}

Export-ModuleMember -Function "SetupLoadBalancer"