DSCResources/ArcGIS_NotebookServerDockerEngine/ArcGIS_NotebookServerDockerEngine.psm1

$modulePath = Join-Path -Path (Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent) -ChildPath 'Modules'

# Import the ArcGIS Common Modules
Import-Module -Name (Join-Path -Path $modulePath `
        -ChildPath (Join-Path -Path 'ArcGIS.Common' `
            -ChildPath 'ArcGIS.Common.psm1'))

function Get-TargetResource {
    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    param
    (
        [parameter(Mandatory = $true)]
        [System.String]
        $SiteName,

        [parameter(Mandatory = $true)]
        [System.String]
        $DockerEngineBinariesArchiveUrl,

        [parameter(Mandatory = $true)]
        [System.String]
        $ServiceCredentialUsername,

        [parameter(Mandatory = $true)]
        [System.Boolean]
        $ForceUpdate
    )
    
    $null
}

function Set-TargetResource {
    [CmdletBinding()]
    param
    (
        [parameter(Mandatory = $true)]
        [System.String]
        $SiteName,

        [parameter(Mandatory = $true)]
        [System.String]
        $DockerEngineBinariesArchiveUrl,

        [parameter(Mandatory = $true)]
        [System.String]
        $ServiceCredentialUsername,

        [parameter(Mandatory = $true)]
        [System.Boolean]
        $ForceUpdate
    )

    # Check if windows container feature is enabled
    $WindowsContainerFeature = Get-WindowsFeature -Name Containers
    if($WindowsContainerFeature.Installed -eq $false){
        Write-Verbose "Enabling Windows Containers Feature"
        Enable-WindowsOptionalFeature -Online -FeatureName containers -All -NoRestart
    }
    
    $DockerInstallPath = "$Env:ProgramFiles\Docker"
    $DockerDaemonExePath = Join-Path $DockerInstallPath "dockerd.exe"
    $DockerExePath = Join-Path $DockerInstallPath "docker.exe"
    # Install Docker Engine
    # Download Docker Engine binaries from url
    $InstallDocker = $true
    if(Get-Service docker -ErrorAction Ignore){
        Write-Verbose "Docker Service is already running."
        if($ForceUpdate){
            Write-Verbose "Force Update is set to true. Stopping Docker Service"
            Stop-Service docker
            Write-Verbose "Docker Service Stopped"

            # Unregister docker service
            Write-Verbose "Unregistering Docker Service"
            &$($DockerDaemonExePath) --unregister-service
            Write-Verbose "Docker Service Unregistered"

            # Uninstall Docker
            Write-Verbose "Uninstalling Docker Engine"
            if(Test-Path $DockerInstallPath){
                Remove-Item -Path $DockerInstallPath -Recurse -Force
            }
            Write-Verbose "Docker Engine Uninstalled"
        }else{
            Write-Verbose "Skipping Docker Engine Installation as it is already installed."
            $InstallDocker = $False
        }
    }else{
        Write-Verbose "Docker Service is not running."
    }

    if($InstallDocker){
        $DockerEnginerZipFileName = Get-FileNameFromUrl $DockerEngineBinariesArchiveUrl
        $DockerEnginerZipFilePath =  (Join-Path $env:TEMP $DockerEnginerZipFileName)
        Invoke-WebRequest -OutFile $DockerEnginerZipFilePath -Uri $DockerEngineBinariesArchiveUrl -UseBasicParsing -ErrorAction Ignore

        Write-Verbose "Installing Docker Engine"
        Expand-Archive $DockerEnginerZipFilePath -DestinationPath $Env:ProgramFiles

        # Register docker service
        &$($DockerDaemonExePath) --register-service 
        # Start docker service
        Start-Service docker
        #Test docker engine
        &$($DockerExePath) info
        Write-Verbose "Docker Engine Installed"

        #Add docker to Path
        $env:Path = $env:Path + ";$($DockerInstallPath)"
        # Add docker to the system path
        [Environment]::SetEnvironmentVariable('Path', $env:Path, [System.EnvironmentVariableTarget]::Machine)
    }

    # Create a 'docker-users' Local Group if not exists
    if (-not(Get-LocalGroup -Name 'docker-users' -ErrorAction Ignore)) {
        New-LocalGroup -Name 'docker-users' -Description 'Docker Users' 
    }else{
        Write-Verbose "Local Group 'docker-users' already exists"
    }

    # Add the service account to the 'docker-users' Local Group if not exists
    if (-not(Get-LocalGroupMember -Group 'docker-users' -Member $ServiceCredentialUsername -ErrorAction Ignore)) {   
        Add-LocalGroupMember -Group 'docker-users' -Member $ServiceCredentialUsername 
    }else{
        Write-Verbose "Service account '$($ServiceCredentialUsername)' already exists in the 'docker-users' Local Group"
    }

    $DockerConfigDirectory = "$($Env:ProgramData)\Docker\config"
    $DockerDaemonConfigurationFile = (Join-Path $DockerConfigDirectory "daemon.json")
    if(Test-Path $DockerDaemonConfigurationFile){
        $DockerDaemonConfiguration = Get-Content -Path $DockerDaemonConfigurationFile -Raw | ConvertFrom-Json
        #If not, add "group" key with value "docker-users" to the docker config file
        if(-not($DockerDaemonConfiguration.group) `
                -or ($DockerDaemonConfiguration.group -ine 'docker-users')){
            $DockerDaemonConfiguration.group = 'docker-users'
            $DockerDaemonConfiguration | ConvertTo-Json | Set-Content -Path $DockerDaemonConfigurationFile
        }else{
            Write-Verbose "Docker Daemon Configuration already has 'group' key with value 'docker-users'"
        }
    }else{
        $DockerDaemonConfiguration = @{
            group = "docker-users"
        }
        # check if config directory exists
        if(-not(Test-Path $DockerConfigDirectory)){
            New-Item -Path $DockerConfigDirectory -ItemType Directory
        }
        $DockerDaemonConfiguration | ConvertTo-Json | Set-Content -Path $DockerDaemonConfigurationFile
    }

    if(-not(Test-DockerAPIAccess)){
        Write-Verbose "Enabling Docker API Access"
        $scProcess = Start-Process "sc" -ArgumentList "config docker binpath= `"\`"C:\Program Files\docker\dockerd.exe\`" --run-service -H tcp://localhost:2375 -H npipe://`"" -Wait -Verbose -NoNewWindow -PassThru
        if($scProcess.ExitCode -eq 0) {
            Write-Verbose "Successfully updated config for docker service. Output - $($scProcess.StandardOutput)"
        }else{
            Write-Verbose "Failed to update config for docker service. Error - $($scProcess.StandardError)"
        }
        Write-Verbose "Docker API Access Enabled"
    }

    Write-Verbose "Restarting Docker Service"
    Restart-Service docker
    Write-Verbose "Docker Service Restarted"

    if(Test-DockerAPIAccess){
        Write-Verbose "Docker API is accessible"
    }else{
        Write-Verbose "[WARNING] Docker API is not accessible. Please check the Docker Engine installation."
    }
}

function Test-DockerAPIAccess {
    # Enable Docker API access
    # Check if docker api available on port 2375
    $DockerAPIPort = 2375
    $DockerAPIPortStatus = Test-NetConnection -ComputerName localhost -Port $DockerAPIPort
    if($DockerAPIPortStatus.TcpTestSucceeded -eq $false){
        Write-Verbose "Docker API is not available on port $DockerAPIPort."
    }else{
        Write-Verbose "Docker API is available on port $DockerAPIPort"
    }
    return $DockerAPIPortStatus.TcpTestSucceeded
}
function Test-TargetResource
{
    [CmdletBinding()]
    [OutputType([System.Boolean])]
    param
    (
        [parameter(Mandatory = $true)]
        [System.String]
        $SiteName,
        
        [parameter(Mandatory = $true)]
        [System.String]
        $DockerEngineBinariesArchiveUrl,

        [parameter(Mandatory = $true)]
        [System.String]
        $ServiceCredentialUsername,

        [parameter(Mandatory = $true)]
        [System.Boolean]
        $ForceUpdate
    )

    $Result = $True

    if($ForceUpdate){
        Write-Verbose "Force Update is set to true. Skipping Test-TargetResource"
        $Result = $False
    }else{
        $WindowsContainerFeature = Get-WindowsFeature -Name Containers
        if($WindowsContainerFeature.Installed -eq $false){
            $Result = $False
        }else{
            if($DockerEngineBinariesArchiveUrl){
                # Check if Docker Engine is already installed
                if(Get-Service docker -ErrorAction Ignore) { 
                    Write-Verbose "Docker Service is running."
                    # Check if the service account is added to the 'docker-users' Local Group
                    if (-not (Get-LocalGroupMember -Group 'docker-users' -Member $ServiceCredentialUsername -ErrorAction Ignore)) {   
                        Write-Verbose "Service account '$($ServiceCredentialUsername)' is not added to the 'docker-users' Local Group."
                        $Result = $False
                    }else{
                        Write-Verbose "Service account '$($ServiceCredentialUsername)' is added to the 'docker-users' Local Group."
                    }
                }else{
                    Write-Verbose "Docker Service is not running."
                    $Result = $False
                }
            }else{
                throw "[ERROR] Docker Engine Binaries Archive URL is required."
            }
        }
    }

    $Result
}

function Get-FileNameFromUrl
{
    param(
        [string]$Url
    )
    $FileName = $Url
    if($FileName) {
        $pos = $FileName.IndexOf('?')
        if($pos -gt 0) { 
            $FileName = $FileName.Substring(0, $pos) 
        } 
        $FileName = $FileName.Substring($FileName.LastIndexOf('/')+1)   
    }     
    $FileName
}

Export-ModuleMember -Function *-TargetResource