<#
	run.ps1 executes the agent
 #>

$ErrorActionPreference = 'Stop'
$WarningPreference = 'SilentlyContinue'
$VerbosePreference = 'SilentlyContinue'
$DebugPreference = 'SilentlyContinue'
$InformationPreference = 'SilentlyContinue'

Import-Module -WarningAction Ignore -Name "$PSScriptRoot\utils.psm1"

function Get-Address
{
    param(
        [parameter(Mandatory = $false)] [string]$Addr
    )

    if (-not $Addr) {
        return ""
    }

    # If given address is a network interface on the system, retrieve configured IP on that interface (only the first configured IP is taken)
    try {
        $getAdapterJson = wins.exe cli net get --name $Addr
        if ($?) {
            $namedNetwork = $getAdapterJson | ConvertTo-JsonObj
            if ($namedNetwork) {
                return $namedNetwork.AddressCIDR -replace "/32",""
            }
        }
    } catch {}

    # Repair the container route for `169.254.169.254` before cloud provider query
    $actualGateway = $(route.exe print 0.0.0.0 | Where-Object {$_ -match '0\.0\.0\.0.*[a-z]'} | Select-Object -First 1 | ForEach-Object {($_ -replace '0\.0\.0\.0|[a-z]|\s+',' ').Trim() -split ' '} | Select-Object -First 1)
    $expectedGateway = $(route.exe print 169.254.169.254 | Where-Object {$_ -match '169\.254\.169\.254'} | Select-Object -First 1 | ForEach-Object {($_ -replace '169\.254\.169\.254|255\.255\.255\.255|[a-z]|\s+',' ').Trim() -split ' '} | Select-Object -First 1)
    if ($actualGateway -ne $expectedGateway) {
        $errMsg = $(route.exe add 169.254.169.254 MASK 255.255.255.255 $actualGateway METRIC 1)
        if (-not $?) {
            Log-Error "Could not repair contain route for using cloud provider"
        }
    }

    # Loop through cloud provider options to get IP from metadata, if not found return given value
    switch ($Addr)
    {
        "awslocal" {
            return $(curl.exe -s "http://169.254.169.254/latest/meta-data/local-ipv4")
        }
        "awspublic" {
            return $(curl.exe -s "http://169.254.169.254/latest/meta-data/public-ipv4")
        }
        "doprivate" {
            return $(curl.exe -s "http://169.254.169.254/metadata/v1/interfaces/private/0/ipv4/address")
        }
        "dopublic" {
            return $(curl.exe -s "http://169.254.169.254/metadata/v1/interfaces/public/0/ipv4/address")
        }
        "azprivate" {
            return $(curl.exe -s -H "Metadata:true" "http://169.254.169.254/metadata/instance/network/interface/0/ipv4/ipAddress/0/privateIpAddress?api-version=2017-08-01&format=text")
        }
        "azpublic" {
            return $(curl.exe -s -H "Metadata:true" "http://169.254.169.254/metadata/instance/network/interface/0/ipv4/ipAddress/0/publicIpAddress?api-version=2017-08-01&format=text")
        }
        "gceinternal" {
            return $(curl.exe -s -H "Metadata-Flavor: Google" "http://metadata.google.internal/computeMetadata/v1/instance/network-interfaces/0/ip?alt=json")
        }
        "gceexternal" {
            return $(curl.exe -s -H "Metadata-Flavor: Google" "http://metadata.google.internal/computeMetadata/v1/instance/network-interfaces/0/access-configs/0/external-ip?alt=json")
        }
        "packetlocal" {
            return $(curl.exe -s "https://metadata.packet.net/2009-04-04/meta-data/local-ipv4")
        }
        "packetpublic" {
            return $(curl.exe -s "https://metadata.packet.net/2009-04-04/meta-data/public-ipv4")
        }
        "ipify" {
            return $(curl.exe -s "https://api.ipify.org")
        }
    }

    return $Addr
}

# required envs
Set-Env -Key "DOCKER_HOST" -Value "npipe:////./pipe/docker_engine"
Set-Env -Key "CATTLE_ROLE" -Value "worker"

# clean up
$CLUSTER_CLEANUP = Get-Env -Key "CLUSTER_CLEANUP"
if ($CLUSTER_CLEANUP -eq "true")
{
    Start-Process -NoNewWindow -Wait -FilePath "c:\etc\rancher\agent.exe"
    exit 0
}

# init parameters
$CATTLE_SERVER = Get-Env -Key "CATTLE_SERVER"
$CATTLE_TOKEN = Get-Env -Key "CATTLE_TOKEN"
$CATTLE_NODE_NAME = Get-Env -Key "CATTLE_NODE_NAME"
$CATTLE_ADDRESS = Get-Env -Key "CATTLE_ADDRESS"
$CATTLE_INTERNAL_ADDRESS = Get-Env -Key "CATTLE_INTERNAL_ADDRESS"
$CATTLE_CA_CHECKSUM = Get-Env -Key "CATTLE_CA_CHECKSUM"
$CATTLE_NODE_LABEL = @()
$CATTLE_NODE_TAINTS = @()

# parse arguments
$vals = $null
for ($i = $args.Length; $i -ge 0; $i--)
{
    $arg = $args[$i]
    switch -regex ($arg)
    {
        '^(-d|--debug)$' {
            Set-Env -Key "CATTLE_DEBUG" -Value "true"
            $vals = $null
        }
        '^(-s|--server)$' {
            $CATTLE_SERVER = ($vals | Select-Object -First 1)
            $vals = $null
        }
        '^(-t|--token)$' {
            $CATTLE_TOKEN = ($vals | Select-Object -First 1)
            $vals = $null
        }
        '^(-c|--ca-checksum)$' {
            $CATTLE_CA_CHECKSUM = ($vals | Select-Object -First 1)
            $vals = $null
        }
        '^(-all|--all-roles)$' {
            $vals = $null
        }
        '^(-e|--etcd)$' {
            $vals = $null
        }
        '^(-w|--worker)$' {
            $vals = $null
        }
        '^(-p|--controlplane)$' {
            $vals = $null
        }
        '^(-r|--node-name)$' {
            $CATTLE_NODE_NAME = ($vals | Select-Object -First 1)
            $vals = $null
        }
        '^(-n|--no-register)$' {
            Set-Env -Key "CATTLE_AGENT_CONNECT" -Value "true"
            $vals = $null
        }
        '^(-a|--address)$' {
            $CATTLE_ADDRESS = ($vals | Select-Object -First 1)
            $vals = $null
        }
        '^(-i|--internal-address)$' {
            $CATTLE_INTERNAL_ADDRESS = ($vals | Select-Object -First 1)
            $vals = $null
        }
        '^(-l|--label)$' {
            if ($vals) {
                $CATTLE_NODE_LABEL += $vals
            }
            $vals = $null
        }
        '^(-o|--only-write-certs)$' {
            Set-Env -Key "CATTLE_WRITE_CERT_ONLY" -Value "true"
            $vals = $null
        }
        '^--taints$' {
            if ($vals) {
                $CATTLE_NODE_TAINTS += $vals
            }
            $vals = $null
        }
        default {
            if ($vals) {
                $vals = ,$arg + $vals
            } else {
                $vals = @($arg)
            }
        }
    }
}

# use the register token to request wins server
if (Test-Path -PathType Leaf -Path "c:\cattle-credentials\token")
{
    $token = Get-Content -Path "c:\cattle-credentials\token" -Raw -ErrorAction Ignore
    Set-Env -Key "WINS_AUTH_TOKEN" -Value $token
}

# check docker npipe
$CATTLE_CLUSTER = Get-Env -Key "CATTLE_CLUSTER"
if ($CATTLE_CLUSTER -ne "true")
{
    $dockerNPipe = Get-ChildItem //./pipe/ -ErrorAction Ignore | ? Name -eq "docker_engine"
    if (-not $dockerNPipe) {
        Log-Warn "Default docker named pipe is not found"
        Log-Warn "Please bind mount in the docker named pipe to //./pipe/docker_engine if docker errors occur"
        Log-Warn "example: docker run -v //./pipe/custom_docker_named_pipe://./pipe/docker_engine ..."
    }
}

# get address
$CATTLE_ADDRESS = Get-Address -Addr $CATTLE_ADDRESS
$CATTLE_INTERNAL_ADDRESS = Get-Address -Addr $CATTLE_INTERNAL_ADDRESS

# get default network metadata when nodeName or address is blank
if ((-not $CATTLE_NODE_NAME) -or (-not $CATTLE_ADDRESS))
{
    $getAdapterJson = wins.exe cli net get
    if ($?) {
        $defaultNetwork = $getAdapterJson | ConvertTo-JsonObj
        if ($defaultNetwork) {
            if (-not $CATTLE_NODE_NAME) {
                $CATTLE_NODE_NAME = $defaultNetwork.HostName
                $CATTLE_NODE_NAME = $CATTLE_NODE_NAME.ToLower()
            }

            if (-not $CATTLE_ADDRESS) {
                $CATTLE_ADDRESS = $defaultNetwork.AddressCIDR -replace "/32",""
            }
        } else {
            Log-Warn "Could not convert '$getAdapterJson' to json object"
        }
    } else {
        Log-Warn "Could not get host network metadata: $getAdapterJson"
    }
}

# check token and address
$CATTLE_K8S_MANAGED = Get-Env -Key "CATTLE_K8S_MANAGED"
if ($CATTLE_K8S_MANAGED -ne "true")
{
    if (-not $CATTLE_TOKEN) {
        Log-Fatal "--token is a required option"
    }
    if (-not $CATTLE_ADDRESS) {
        Log-Fatal "--address is a required option"
    }
}

# check rancher server address
if (-not $CATTLE_SERVER)
{
    Log-Fatal "--server is a required option"
}

# check rancher server
try
{
    curl.exe --insecure -s -fL "$CATTLE_SERVER/ping" | Out-Null
    if ($?) {
        Log-Info "$CATTLE_SERVER is accessible"
    } else {
        Log-Fatal "$CATTLE_SERVER is not accessible"
    }
}
catch
{
    Log-Fatal "$CATTLE_SERVER is not accessible: $($_.Exception.Message)"
}

# download cattle server CA
if ($CATTLE_CA_CHECKSUM)
{
    $sslCertDir = Get-Env -Key "SSL_CERT_DIR"
    $server = $CATTLE_SERVER
    $caChecksum = $CATTLE_CA_CHECKSUM
    $temp = New-TemporaryFile
    $cacerts = $null
    try {
        $cacerts = $(curl.exe --insecure -s -fL "$server/v3/settings/cacerts" | ConvertTo-JsonObj).value
    } catch {}
    if (-not $cacerts) {
        Log-Fatal "Could not get cattle server CA from $server"
    }

    $cacerts + "`n" | Out-File -NoNewline -Encoding ascii -FilePath $temp.FullName
    $tempHasher = Get-FileHash -LiteralPath $temp.FullName -Algorithm SHA256
    if ($tempHasher.Hash.ToLower() -ne $caChecksum.ToLower()) {
        $temp.Delete()
        Log-Fatal "Actual cattle server CA checksum is $($tempHasher.Hash.ToLower()), $server/v3/settings/cacerts does not match $($caChecksum.ToLower())"
    }
    Remove-Item -Force -Recurse -Path "$sslCertDir\serverca" -ErrorAction Ignore
    New-Item -Force -ItemType Directory -Path $sslCertDir -ErrorAction Ignore | Out-Null
    $temp.MoveTo("$sslCertDir\serverca")

    # import the self-signed certificate
    $caBytes = $null
    Get-Content "$sslCertDir\serverca" | % {
        if ($_ -match '-+BEGIN CERTIFICATE-+') {
            $caBytes = @()
        } elseif ($_ -match '-+END CERTIFICATE-+') {
            $caTemp = New-TemporaryFile
            $caString = [Convert]::ToBase64String($caBytes)
            Set-Content -Value $caString -Path $caTemp.FullName
            certoc.exe -addstore root $caTemp.FullName | Out-Null
            if (-not $?) {
                $caTemp.Delete()
                Log-Fatal "Failed to import rancher server certificates to Root"
            }
            $caTemp.Delete()
        } else {
            $caBytes += [Convert]::FromBase64String($_)
        }
    }

    $CATTLE_SERVER_HOSTNAME = ([System.Uri]"$server").Host
    $CATTLE_SERVER_HOSTNAME_WITH_PORT = ([System.Uri]"$server").Authority

    # windows path could not allow colons
    $CATTLE_SERVER_HOSTNAME_WITH_PORT = $CATTLE_SERVER_HOSTNAME_WITH_PORT -replace ":", ""

    $dockerCertsPath = "c:\etc\docker\certs.d\$CATTLE_SERVER_HOSTNAME_WITH_PORT"
    New-Item -Force -ItemType Directory -Path $dockerCertsPath -ErrorAction Ignore | Out-Null
    Copy-Item -Force -Path "$sslCertDir\serverca" -Destination "$dockerCertsPath\ca.crt" -ErrorAction Ignore
}

# add labels
$getVersionJson = wins.exe cli host get-version
if ($?) {
    $windowsCurrentVersion = $getVersionJson | ConvertTo-JsonObj
    if ($windowsCurrentVersion) {
        $versionTag = "$($windowsCurrentVersion.CurrentMajorVersionNumber).$($windowsCurrentVersion.CurrentMinorVersionNumber).$($windowsCurrentVersion.CurrentBuildNumber).$($windowsCurrentVersion.UBR)"
        $CATTLE_NODE_LABEL += @("rke.cattle.io/windows-version=$versionTag")
        $CATTLE_NODE_LABEL += @("rke.cattle.io/windows-release-id=$($windowsCurrentVersion.ReleaseId)")
        $CATTLE_NODE_LABEL += @("rke.cattle.io/windows-major-version=$($windowsCurrentVersion.CurrentMajorVersionNumber)")
        $CATTLE_NODE_LABEL += @("rke.cattle.io/windows-minor-version=$($windowsCurrentVersion.CurrentMinorVersionNumber)")
        $CATTLE_NODE_LABEL += @("rke.cattle.io/windows-kernel-version=$($windowsCurrentVersion.BuildLabEx)")
        $CATTLE_NODE_LABEL += @("rke.cattle.io/windows-build=$($windowsCurrentVersion.CurrentBuild)")
    } else {
        Log-Warn "Could not convert Windows Current Version JSON '$getVersionJson' to object"
    }
} else {
    Log-Warn "Could not get host version: $getVersionJson"
}

# set environment variables
Set-Env -Key "CATTLE_SERVER" -Value $CATTLE_SERVER
Set-Env -Key "CATTLE_TOKEN" -Value $CATTLE_TOKEN
Set-Env -Key "CATTLE_ADDRESS" -Val $CATTLE_ADDRESS
Set-Env -Key "CATTLE_INTERNAL_ADDRESS" -Val $CATTLE_INTERNAL_ADDRESS
Set-Env -Key "CATTLE_NODE_NAME" -Value $CATTLE_NODE_NAME
Set-Env -Key "CATTLE_NODE_LABEL" -Value $($CATTLE_NODE_LABEL -join ",")
Set-Env -Key "CATTLE_NODE_TAINTS" -Value $($CATTLE_NODE_TAINTS -join ",")

# upgrade wins.exe
Transfer-File -Src c:\Windows\wins.exe -Dst c:\etc\rancher\wins\wins.exe

Start-Process -NoNewWindow -Wait -FilePath "c:\etc\rancher\agent.exe"
