cDSCDockerSwarm.psm1

# Defines the values for the resource's Ensure property.
enum Ensure
{
    # The resource must be absent.
    Absent
    # The resource must be present.
    Present
}

enum Swarm
{
    # The Swarm must be active
    Active
    # The Swarm must be inactive
    Inactive
}

enum SwarmManagement
{
    # Manage Manager Count
    Automatic
    # Only join as worker
    WorkerOnly
}

enum DownloadChannel
{
    Stable
    Edge
    Test
    Get
    DockerProject
    EE
}

# [DscResource()] indicates the class is a DSC resource.
[DscResource()]
class cDockerBinaries
{
    #Ensure binaries are installed or not
    [DscProperty(Key)]
    [Ensure]$Ensure

    #Docker Version in the form of "17.06.0-ce"
    [DscProperty()]
    [string]$version

    #Download Channel for different CE Builds, or EE for microsoft provided
    [DscProperty()]
    [DownloadChannel]$DownloadChannel

    # Sets the desired state of the resource.
    [void] Set()
    {
        $dlURL =""
        switch ($this.DownloadChannel) {
            Stable {$dlURL = "https://download.docker.com/win/static/stable/x86_64"}
            Edge {$dlURL = "https://download.docker.com/win/static/edge/x86_64"}
            Test {$dlURL = "https://download.docker.com/win/static/test/x86_64"}
            Get {$dlURL = "http://get.docker.com/builds/Windows/x86_64"}
            DockerProject {$dlURL = "https://master.dockerproject.org"}
            EE {}
        }
        $GetVersion = $this.version

        if ($this.DownloadChannel -eq "EE") {
            #Use DockerMsftProvider
            Write-Verbose "Using DockerMsftProvider"

            if ((Get-PackageProvider -ListAvailable).Name -notcontains "NuGet") {
                Write-Verbose "Installing NuGet"
                Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force
            }
            if (!(get-module DockerMsftProvider -listavailable)) {
                Write-Verbose "Installing DockerMsftProvider "
                Install-Module -Name DockerMsftProvider -Repository PSGallery -Force
            }     
            #Attempt installation
            try {
                Install-Package -Name docker -RequiredVersion $this.version -ProviderName DockerMsftProvider -Force -verbose            
            }
            catch {
                Write-Verbose "Could not install Docker $($this.version); $($_.Exception)"                    
            }
        }
        else {
            Write-Verbose "Using $($this.DownloadChannel) channel URL $dlURL"
            Write-Verbose "Updating Docker binaries from $($dlURL)/docker-$GetVersion.zip"

            Invoke-WebRequest "$dlURL/docker-$GetVersion.zip" -UseBasicParsing -OutFile "$($env:temp)\docker.zip"
            $DockerRegistered = (Get-Service).Name -contains "Docker"

            if ($DockerRegistered) {
                Stop-Service docker
                start-sleep 2
            }

            Expand-Archive -Path "$($env:temp)\docker.zip" -DestinationPath $Env:ProgramFiles -Force
            Remove-Item -Force "$($env:temp)\docker.zip"      
            
            if (!$DockerRegistered) {
                Write-Verbose "Registering Docker Service"
                $Env:Path += ";$($Env:ProgramFiles)\docker"
                [Environment]::SetEnvironmentVariable('PATH', $env:Path, 'Machine')
                . "$($Env:ProgramFiles)\docker\dockerd.exe" --register-service 
            }
        }
        Write-Verbose "Starting Docker Service"
        Start-Service docker  
    }        
    
    # Tests if the resource is in the desired state.
    [bool] Test()
    {        
        if ($this.DownloadChannel -eq "EE") {
            try {
                $dockerPackage = Get-Package docker -ProviderName DockerMsftProvider -errorAction Stop
                if ($dockerPackage.version -match $this.version) {
                    Write-Verbose "Correct Version Installed"
                    return $true
                }
                else {                
                    Write-Verbose "Wrong Version of Docker is installed: $($dockerPackage.version) should be $($this.version)"
                    return $false
                }
            }
            catch {
                Write-Verbose "Failed to find docker package"
                return $false
            }            
        }
        else {
            $service = (Get-Service).Name -contains "Docker"
            $exeExists = Test-Path $env:ProgramFiles\docker\dockerd.exe
            if ($exeExists){ 
                $CurrentVersion = (Get-Item $env:ProgramFiles\docker\dockerd.exe).VersionInfo.ProductVersion 
            }
            else {
                $CurrentVersion = $null    
            }

            if ($service -and ($CurrentVersion -eq $this.version)) {               
                Write-Verbose "desired version $($this.version) is installed"
                return $true
            }
            elseif ($service -and ($CurrentVersion -ne $this.version)) {
                Write-Verbose "Desired version $($this.version) is not installed"
                return $false
            }            
            else {
                Write-Verbose "Docker is not installed"
                return $false
            }
        }
    }    
    # Gets the resource's current state.
    [cDockerBinaries] Get()
    {        
        $exeExists = Test-Path $env:ProgramFiles\docker\dockerd.exe
        $DockerRegistered = (Get-Service).Name -contains "Docker"
        if ($exeExists -and $DockerRegistered) {
            $this.Ensure = [Ensure]::Present
            $this.version = (Get-Item $env:ProgramFiles\docker\dockerd.exe).VersionInfo.ProductVersion
        }
        else {
            $this.Ensure = [Ensure]::Absent
            $this.version = $null
        }
        return $this
    }    
}

[DscResource()]
class cInsecureRegistryCert
{
    # Registry URI in format "registry:5000"
    [DscProperty(Key)]
    [string]$registryURI

    #Certificate text or read content from file
    [DscProperty(Mandatory)]
    [string]$Certificate

    #Ensure Present or Absent
    [DscProperty(Mandatory)]
    [Ensure] $ensure

    # Sets the desired state of the resource.
    [void] Set()
    {
        $CertPath = "$($env:ProgramData)\docker\certs.d\$($this.registryURI -replace ':','')"

        if ($this.ensure -eq [ensure]::Present) {
            Write-Verbose "Writing Certificate"
            if (-not (Test-Path $CertPath)) {
               mkdir $CertPath
            }
            $this.Certificate | Out-File "$CertPath\ca.crt" -Encoding ascii -Force
        }
        else {
            if (Test-Path $CertPath) {
                Write-Verbose "Removing Certificate"
                Remove-Item $CertPath -Force -Recurse
            }
        }
    }        
    
    # Tests if the resource is in the desired state.
    [bool] Test()
    {
        $CertPath = "$($env:ProgramData)\docker\certs.d\$($this.registryURI -replace ':','')"
        if ($this.ensure -eq [ensure]::Present) {
            if(test-path "$CertPath\ca.crt") {
                try {
                    $currentCert =  New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
                    $currentCert.Import("$CertPath\ca.crt")
                }
                catch {
                    Write-Verbose "Invalid Current Cert; could not be imported to test"
                    return $false
                }                    
                
                try {                    
                $enc = [system.Text.Encoding]::UTF8
                $desiredCert =  New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
                $desiredCert.Import($enc.GetBytes($this.Certificate))
                  }
                catch {
                    Write-Verbose "Invalid Desired Certificate defined in configuration; could not be imported to test"
                    return $false
                }   
                                   
                if($currentCert.Thumbprint -eq $desiredCert.Thumbprint) {

                Write-Verbose "valid certificate installed"
                return $true
                }
                else {
                    write-verbose "Wrong registry Certificate"                                    
                    return $false
                }
            }
            else {
                Write-Verbose "Missing CA Cert for Registry"
                return $false
            }
        }
        else {
            if(test-path "$($env:ProgramData)\docker\certs.d\$($this.registryURI -replace ':','')\ca.crt") {
                Write-Verbose "CA Cert for Registry Exists that should not"
                return $false    
            }
            else {
                return $true
            }
        }
    }    
    # Gets the resource's current state.
    [cInsecureRegistryCert] Get()
    {        
        if(test-path "$($env:ProgramData)\docker\certs.d\$($this.registryURI -replace ':','')\ca.crt") {
            $this.ensure = [ensure]::Present
        }
        else {
            $this.ensure = [ensure]::Absent
        }
        return $this
    }
  
}

[DscResource()]
class cDockerConfig
{

    #Ensure present or absent
    [DscProperty(Key)]
    [Ensure]$Ensure

    #JSON format string of general configuration opstions, not including labels, hosts, and registries
    [DscProperty()]
    [string]$BaseConfigJson='{}'

    #Array of registries to be added to the configuration
    [DscProperty()]
    [string[]] $InsecureRegistries

    #Array of labels to be added to the configuration
    [DscProperty()]
    [string[]] $Labels

    #Daemon binings, defaults to all interfaces. Specify in format of 'tcp://0.0.0.0:2375'
    [DscProperty()]
    [string]$DaemonBinding

    #Adds named pipe and TCP bindings to configuration, allowing external access. This is required for Swarm mode
    [DscProperty()]
    [boolean] $ExposeAPI
    
    #Restart docker on any chane of the configuration
    [DscProperty()]
    [boolean] $RestartOnChange

    #Enable TLS
    [DscProperty()]
    [bool]$EnableTLS=$false

    # Sets the desired state of the resource.
    [void] Set()
    {    
        if ($this.Ensure -eq [Ensure]::Present) {
            
            $pendingConfiguration = $this.GetPendingConfiguration()

            #Does a config exist at all?
            $ConfigExists = $this.ConfigExists()
            Write-Verbose "Config Exists: $ConfigExists" 
            #Write Configuration
            $pendingConfiguration |  Out-File "$($env:ProgramData)\docker\config\daemon.json" -Encoding ascii -Force

            #Restart docker service if the configuration changed, or if this is the initial configuration
            if ($this.RestartOnChange -or !($ConfigExists)) {
                Write-Verbose "Restarting the Docker service"
                Restart-Service Docker
                start-sleep 5
            }
        }
        else {
            Remove-Item "$($env:ProgramData)\docker\config\daemon.json" -Force
        }     
    }        
    
    # Tests if the resource is in the desired state.
    [bool] Test()
    {   
        if ($this.Ensure -eq [Ensure]::Present) {     
            if($this.ConfigExists()){            
                $currentConfiguration = Get-Content "$($env:ProgramData)\docker\config\daemon.json" -raw
                $pendingConfigurationJS = $this.GetPendingConfiguration() | Out-String                

                if ($currentConfiguration -eq $pendingConfigurationJS) {
                    Write-Verbose "Configuration Matches"
                    return $true
                }
                else{
                    Write-Verbose "Configuration Does not Match"
                    return $false
                }
            }
            else{
                Write-Verbose "Missing daemon.json"
                return $false
            }
        }
        else { #Make sure the config is absent
            if($this.ConfigExists()){
                Write-Verbose "daemon.json Exists but should not"
                return $false
              }
            else {
             Write-Verbose "daemon.json does not exist"
                return $true
            }
        }

        return $false
    }    
    # Gets the resource's current state.
    [cDockerConfig] Get()
    {   
        $ConfigExists = $this.ConfigExists()    
        if($ConfigExists){            
            $currentConfiguration = Get-Content "$($env:ProgramData)\docker\config\daemon.json" -raw
            $pendingConfigurationJS = $this.GetPendingConfiguration() | Out-String                

            if ($currentConfiguration -eq $pendingConfigurationJS) {
                $this.Ensure = [ensure]::Present
            }
            else {
                $this.Ensure = [ensure]::Absent
            }
        }
        else {
            $this.Ensure = [ensure]::Absent
        }
        return $this
     }

     [bool]ConfigExists() {
       if (Test-Path "$($env:ProgramData)\docker\config\daemon.json") {
                return $true
            }
            else {
                return $false
            }
     }

     [string]GetPendingConfiguration() {
     
        $pendingConfiguration = $this.BaseConfigJson | ConvertFrom-json

        if ($this.InsecureRegistries) {
            $pendingConfiguration | Add-Member -Name "insecure-registries" -Value  $this.InsecureRegistries -MemberType NoteProperty
        }
        if ($this.Labels) {
            $pendingConfiguration | Add-Member -Name "labels" -Value $this.Labels -MemberType NoteProperty
        }

        $CertExists = Test-Path $env:ALLUSERSPROFILE\docker\certs.d\cert.pem
        $KeyExists = Test-Path $env:ALLUSERSPROFILE\docker\certs.d\key.pem
        if ($this.EnableTLS -and $CertExists -and $KeyExists) {
            #Add TLS
            $pendingConfiguration | Add-Member -MemberType NoteProperty -Name  "tlscacert" -Value "C:\ProgramData\docker\certs.d\ca.pem"
            $pendingConfiguration | Add-Member -MemberType NoteProperty -Name  "tlscert" -Value "C:\ProgramData\docker\certs.d\cert.pem"
            $pendingConfiguration | Add-Member -MemberType NoteProperty -Name  "tlskey" -Value "C:\ProgramData\docker\certs.d\key.pem"
            $pendingConfiguration | Add-Member -MemberType NoteProperty -Name  "tlsverify" -Value $true     
            #Adjust port for TLS
            if($this.exposeApi -eq $true){
                if ($this.DaemonBinding) {
                    $binding = $this.DaemonBinding            
                }
                else {
                    $binding = "tcp://0.0.0.0:2376"
                }
                $pendingConfiguration | Add-Member -Name "hosts" -MemberType NoteProperty -Value @($binding, "npipe://")
            }       
        }
        else{
            if($this.exposeApi -eq $true){
                if ($this.DaemonBinding) {
                    $binding = $this.DaemonBinding            
                }
                else {
                    $binding = "tcp://0.0.0.0:2375"
                }
                $pendingConfiguration | Add-Member -Name "hosts" -MemberType NoteProperty -Value @($binding, "npipe://")
            }
        }

        return $pendingConfiguration | ConvertTo-Json
     }
    

}


# [DscResource()] indicates the class is a DSC resource.
[DscResource()]
class cDockerSwarm
{

    #Swarm Master URl in the format of "10.10.10.10:2377"
    [DscProperty(Key)]
    [string]$SwarmMasterURI

    #Activate swarm mode on the host and connect to swarm master. Must be Active or Inactive
    [DscProperty(Mandatory)]
    [Swarm] $SwarmMode

    #Number of managers to attempt of SwarmManagement is automatic. The nodes will join as managers until the number specified is met.
    [DscProperty()]
    [int] $ManagerCount=3

    #Automatic will manage the number of managers in the swarm. WorkerOnly will join only as worker nodes
    [DscProperty(Mandatory)]
    [SwarmManagement]$SwarmManagement

    # Sets the desired state of the resource.
    [void] Set()
    {    
        Write-Verbose "Using Swarm Master: $($this.SwarmMasterURI)"
        $SwarmDockerHost = $($this.SwarmMasterURI).Split(':')[0]
        $SwarmManagerIsMe = (Get-NetIPAddress).IPAddress -contains $SwarmDockerHost
        Write-Verbose "Getting Local Docker info"

        $LocalInfo = $this.GetLocalDockerInfo()
        
        Write-Verbose "Getting Swarm info from $SwarmDockerHost"
        if ((test-netconnection $SwarmDockerHost -Port 2375).tcpTestSucceeded) {
            $swarmConnString = $SwarmDockerHost
            $tls = $null
        }
        elseif ((test-netconnection $SwarmDockerHost -Port 2376).tcpTestSucceeded) {
            $swarmConnString = "$($SwarmDockerHost):2376"
            $tls = "--tlsverify"
        }
        else {
            write-error "no connection to remote swarm manager"
        }
        #Random seed to sleep to get better distribution, and prevent too many managers.
        Start-Sleep (get-random -Minimum 0 -Maximum 15)
        $SwarmInfo = . "$($Env:ProgramFiles)\docker\docker.exe" -H $swarmConnString $tls info -f '{{ json .Swarm }}' | ConvertFrom-Json
        $managers = $SwarmInfo.managers
        
        if ($LocalInfo.Swarm.LocalNodeState -eq "active") {
            $InRightSwarm = $LocalInfo.Swarm.RemoteManagers.Addr -contains $this.SwarmMasterURI
            if (!$InRightSwarm) {
                Write-Verbose "Server is in the wrong swarm; leaving"
                . "$($Env:ProgramFiles)\docker\docker.exe" swarm leave -f
            }
            elseif ($this.SwarmMode -eq [Swarm]::Inactive) {
                Write-Verbose "Server is in the a swarm and should be inactive; leaving"
                . "$($Env:ProgramFiles)\docker\docker.exe" swarm leave -f
            }
            elseif (($this.SwarmMode -eq [Swarm]::Active) -and ($managers -lt $this.ManagerCount)) {
                . "$($Env:ProgramFiles)\docker\docker.exe" -H $swarmConnString $tls node promote $env:COMPUTERNAME
            }
        }
        elseif ($this.SwarmMode -eq [Swarm]::Active) {
            
            if ($SwarmManagerIsMe) {
                Write-Verbose "Creating a new Swarm"
                . "$($Env:ProgramFiles)\docker\docker.exe" swarm init --advertise-addr $this.SwarmMasterURI
            }
            elseif (($this.SwarmManagement -eq [SwarmManagement]::Automatic) -and ($managers -lt $this.ManagerCount)) {
                Write-Verbose "Joining the Swarm as a manager"
                $this.JoinSwarm($swarmConnString, $tls, "manager")
            }
            else {
                Write-Verbose "Joining the Swarm as a worker"
                $this.JoinSwarm($swarmConnString, $tls,"worker")
            }
        }        
        
    }        
    
    # Tests if the resource is in the desired state.
    [bool] Test()
    {        
            $LocalInfo = $this.GetLocalDockerInfo()
            
            if ($LocalInfo.Swarm.LocalNodeState  -eq "active" -and ($this.SwarmMode -eq [Swarm]::Active)) {
                Write-Verbose "Swarm is Active"
                #Test for swarm membership
                $InRightSwarm = $LocalInfo.Swarm.RemoteManagers.Addr -contains $this.SwarmMasterURI
                if ($InRightSwarm) {
                    Write-Verbose "In Correct Swarm"
                    #Test for manager count
                    if (($this.SwarmManagement -eq [SwarmManagement]::WorkerOnly) -or  ($LocalInfo.Swarm.managers -ge $this.ManagerCount )) {
                        Write-Verbose "Swarm State Good. Managers: $($LocalInfo.Swarm.managers)"
                        return $true    
                    }
                    else {
                        if ($LocalInfo.Swarm.ControlAvailable -eq $true) {
                            Write-Verbose "Not enough Managers: $($LocalInfo.Swarm.managers), but node is already a manager"
                            return $true
                        }
                        else
                        {
                        Write-Verbose "Not enough Managers: $($LocalInfo.Swarm.managers), need to be promoted"
                        return $false
                        }
                    }
                }
                else {
                    Write-Verbose "In Wrong Swarm: $($LocalInfo.Swarm.RemoteManagers.Addr) vs $($this.SwarmMasterURI)"
                    return $false
                }                
            }
            elseif ($LocalInfo.Swarm.LocalNodeState  -eq "inactive" -and ($this.SwarmMode -eq [Swarm]::Active)) {
                Write-Verbose "Swarm State $($LocalInfo.Swarm.LocalNodeState), should be $($this.SwarmMode)"                
                return $false
            }
            elseif ($LocalInfo.Swarm.LocalNodeState  -eq "active" -and ($this.SwarmMode -eq [Swarm]::Inactive)) {
                Write-Verbose "Swarm State $($LocalInfo.Swarm.LocalNodeState), should be $($this.SwarmMode)"                
                return $false
            }
            elseif ($LocalInfo.Swarm.LocalNodeState  -eq "inactive" -and ($this.SwarmMode -eq [Swarm]::Inactive)) {
                Write-Verbose "Swarm State Good"    
                return $true
            }
            else {
                Write-Verbose "Default return: failure to determine state"                
                return $false
            }
    }    
    # Gets the resource's current state.
    [cDockerSwarm] Get()
    {        
        $SwarmState = . "$($Env:ProgramFiles)\docker\docker.exe" info -f '{{ json .Swarm.LocalNodeState }}' | ConvertFrom-Json
            if ($SwarmState -eq "active"){
                $this.SwarmMode = [Swarm]::Active
            }
            elseif ($SwarmState -eq "inactive") {
                $this.SwarmMode = [Swarm]::Inactive
            }
        return $this 
    }
    
    [psobject]GetLocalDockerInfo(){
        #Try in a loop, in case docker was just restarted and is not ready yet
        $info = $null
        $i = 0
        while (!$info -and $i -lt 5) { 
            try{
                $i++
                $ErrorActionPreference = 'stop'
                Write-Verbose "Trying to get token from swarm manager"
                $info = . "$($Env:ProgramFiles)\docker\docker.exe" info -f '{{ json . }}' | ConvertFrom-Json                
                break
            }
            catch {
                Write-Verbose "Waiting for local docker to come online"
                start-sleep 5
            }            
        }
        return $info
    }

    [void]JoinSwarm($host, $tls,$type)
    {
        $token = $null
        $i = 0
        while (!$token -and $i -lt 5) { 
            try{
                $i++
                $ErrorActionPreference = 'stop'
                Write-Verbose "Trying to get token from swarm manager"
                $token = . "$($Env:ProgramFiles)\docker\docker.exe" -H $host $tls swarm join-token $type -q 
                break
            }
            catch {
                Write-Verbose "Waiting for manager to come online"
                start-sleep 15
            }
        
        }
        if ($token) {
            . "$($Env:ProgramFiles)\docker\docker.exe" swarm join --token $token $this.SwarmMasterURI
        }
        else {
            write-verbose "Failed to Get token; can't join swarm"
        }
    }    
}


# [DscResource()] indicates the class is a DSC resource.
[DscResource()]
class cDockerTLSAutoEnrollment
{

    # A DSC resource must define at least one key property.
    [DscProperty(Key)]
    [Ensure]$Ensure

    [DscProperty()]
    [String]$EnrollmentServer

    
    # Sets the desired state of the resource.
    [void] Set()
    {
        Write-Verbose "Using Enrollment Server: $($this.EnrollmentServer)"        
        $SwarmManagerIsMe = (Get-NetIPAddress).IPAddress -contains $this.EnrollmentServer
        Write-Verbose "Swarm Manager is this node: $SwarmManagerIsMe"
         if ($SwarmManagerIsMe -and $this.Ensure -eq [Ensure]::Present) {
            #Prepare for Enrollment
            if (!(Test-Path $env:SystemDrive\DockerTLSCA)) {
                Write-Verbose "Create Folder $("$env:SystemDrive\DockerTLSCA")"
                mkdir $env:SystemDrive\DockerTLSCA             
            }
            if (!(Test-Path $env:USERPROFILE\.docker)) {
                Write-Verbose "Create Folder $("$env:USERPROFILE\.docker")"
                mkdir $env:USERPROFILE\.docker
            }
            if (!(Test-Path $env:ALLUSERSPROFILE\docker\certs.d)) {
                Write-Verbose "Create Folder $("$env:ALLUSERSPROFILE\docker\certs.d")"
                mkdir $env:ALLUSERSPROFILE\docker\certs.d
            } 

            #Pull enrollment container
            Write-Verbose "Getting Enrollment Container"
            . "$($Env:ProgramFiles)\docker\docker" pull pscripted/dsc-dockerswarm-tls:latest

            #Run TLS Enrollment Container
            #This will create local certs for the CA and running host, and allow other nodes to get their own signed certs from the CA
            Write-Verbose "Running Enrollment Container"
            #Double convert to JSON for IPs to escape json to prevent docker clobbering
            . "$($Env:ProgramFiles)\docker\docker" run --restart unless-stopped -dit `
            -p 3000:3000 `
            -e DockerHost=$env:computername `
            -e DockerHostIPs=$((get-netipaddress -AddressFamily IPv4 -AddressState Preferred).IPaddress | convertto-json -Compress | convertto-json) `
            -v "$env:SystemDrive\DockerTLSCA:C:\DockerTLSCA" `
            -v "$env:ALLUSERSPROFILE\docker:$env:ALLUSERSPROFILE\docker" `
            -v "$env:USERPROFILE\.docker:c:\users\containeradministrator\.docker" pscripted/dsc-dockerswarm-tls:latest         
            
            $i = 0  
            while (!(Test-Path $env:ALLUSERSPROFILE\docker\certs.d\cert.pem) -and $i -lt 5) { 
                start-sleep 10
            }            
        }
        elseif ($this.Ensure -eq [ensure]::Present) {
            #Attempt enrollment from master
            $i = 0  
            while (!(Test-Path $env:ALLUSERSPROFILE\docker\certs.d\cert.pem) -and $i -lt 10) { 
                try{
                    $i++
                    $ErrorActionPreference = 'stop'
                    Write-Verbose "Trying to enroll in TLS"
                    Install-cDSCSwarmTLSCert -SwarmMasterIP $this.EnrollmentServer -port 3000 -serverCerts
                    break
                }
                catch {
                    Write-Verbose "Waiting for TLS container to come online"
                    start-sleep 60
                }
            }
            if (Test-Path $env:ALLUSERSPROFILE\docker\certs.d\cert.pem) {
                write-verbose "Certificates Installed"
            }
            else {
                write-verbose "Failed to Get Certificates"
            }
        }
        elseif ($this.Ensure -eq [ensure]::Absent) {
            $containerID = . "$($Env:ProgramFiles)\docker\docker" ps -f "ancestor=pscripted/dsc-dockerswarm-tls:latest" -q     
            if ($containerID) {
                . "$($Env:ProgramFiles)\docker\docker" stop $containerID     
                . "$($Env:ProgramFiles)\docker\docker" rm $containerID
            }
        }
    }        
    
    # Tests if the resource is in the desired state.
    [bool] Test()
    {   
        Write-Verbose "Using Enrollment Server: $($this.EnrollmentServer)"        
        $SwarmManagerIsMe = (Get-NetIPAddress).IPAddress -contains $this.EnrollmentServer
        if ($SwarmManagerIsMe -and $this.Ensure -eq [Ensure]::Present) {
            if (. "$($Env:ProgramFiles)\docker\docker" ps -f "ancestor=pscripted/dsc-dockerswarm-tls:latest" -q) {
                Write-Verbose "Enrollment container already running"
                $running = $true
            }
            else {
                Write-Verbose "No Enrollment Container running"
                $running = $false
            }
                
            if ($this.Ensure -eq [Ensure]::Present) {
                return $running
            }
            else {
                return !$running
            }
        }
        else {
            Write-Verbose "Checking for Host Certificates"
            $CertExists = Test-Path $env:ALLUSERSPROFILE\docker\certs.d\cert.pem
            $KeyExists = Test-Path $env:ALLUSERSPROFILE\docker\certs.d\key.pem
            if ($CertExists -and $KeyExists) {
                return $true            
            }
            else{
                return $false
            }
        }
    }    
    # Gets the resource's current state.
    [cDockerTLSAutoEnrollment] Get()
    {   
        Write-Verbose "Using Enrollment Server: $($this.EnrollmentServer)"        
        $SwarmManagerIsMe = (Get-NetIPAddress).IPAddress -contains $this.EnrollmentServer
        if ($SwarmManagerIsMe -and $this.Ensure -eq [Ensure]::Present) {     
            Write-verbose "This node should be the CA"
            if (. "$($Env:ProgramFiles)\docker\docker" ps -f "ancestor=pscripted/dsc-dockerswarm-tls:latest" -q) {
                Write-verbose "CA Container is running"
                $this.Ensure = [Ensure]::Present
            }
            else {
                Write-verbose "CA Container is not running"
                $this.Ensure = [Ensure]::Absent                
            }
        }
        else {
            $CertExists = Test-Path $env:ALLUSERSPROFILE\docker\certs.d\cert.pem
            $KeyExists = Test-Path $env:ALLUSERSPROFILE\docker\certs.d\key.pem
            if ($CertExists -and $KeyExists) {
                $this.Ensure = [Ensure]::Present            
            }
            else{
                $this.Ensure = [Ensure]::Absent
            }
        }
        return $this
    }    
}

##Functions

##############################
#.SYNOPSIS
#Install Docker daemon certs provided by cDSCDockerSwarm enrollment container
#
#.DESCRIPTION
#Access the TLS AutoEnrollment Container for server and client certs for docker and download to appropriate directories
#
#.PARAMETER SwarmMasterIP
#IP of the host with the container running.
#
#.PARAMETER port
#Port the container is bound to. Default is 3000
#
#.EXAMPLE
#Install-cDSCSwarmTLSCert -SwarmMasterIP 192.168.0.20

##############################
function Install-cDSCSwarmTLSCert {
    [CmdletBinding()]
    param (
        [string]$SwarmMasterIP,
        [int]$port=3000,
        [switch]$serverCerts        
    )
    
    begin {
        if (!(Test-Path $env:ALLUSERSPROFILE\docker\certs.d)) {
            mkdir $env:ALLUSERSPROFILE\docker\certs.d | out-null
        }
        if (!(Test-Path $env:USERPROFILE\.docker)) {
            mkdir $env:USERPROFILE\.docker | out-null
        }
    }
    
    process {
        
        [array]$ips = (get-netipaddress -AddressState Preferred -AddressFamily IPv4).IPAddress
        if (($SwarmMasterIP -eq "localhost") -or ($ips -contains $SwarmMasterIP)) {
            $containerID = . "$($Env:ProgramFiles)\docker\docker" ps -f "ancestor=pscripted/dsc-dockerswarm-tls:latest" -q
            $containerIP = . "$($Env:ProgramFiles)\docker\docker" inspect $containerID -f '{{json .NetworkSettings.Networks.nat.IPAddress}}'
            $CAContainerURI = "$($containerIP | convertfrom-json):$port"
        }
        else {
            $CAContainerURI = "$($SwarmMasterIP):$port"
        }
        try {
            $Certs = Invoke-RestMethod "http://$CAContainerURI/swarmnode" -Method Post -Body (@{servername=$env:computername;ips=$ips} | Convertto-JSON) -ContentType "application/JSON" 
        }
        catch {
            Write-Error "Unable to connect to TLS Enrollment Server. AutoEnroll must be enabled in the DSC Configuration"
        }
        try {
                $certs.clientCert | out-file -FilePath  $env:USERPROFILE\.docker\cert.pem -Force -Encoding ascii
                $certs.clientKey  | out-file -FilePath  $env:USERPROFILE\.docker\key.pem -Force -Encoding ascii
                $certs.CACert | out-file -FilePath  $env:USERPROFILE\.docker\ca.pem -Force -Encoding ascii
            if ($PSBoundParameters.ContainsKey('serverCerts')) {
                $certs.ServerCert | out-file -FilePath  $env:ALLUSERSPROFILE\docker\certs.d\cert.pem -Force -Encoding ascii
                $certs.ServerKey | out-file -FilePath  $env:ALLUSERSPROFILE\docker\certs.d\key.pem -Force -Encoding ascii
                $certs.CACert | out-file -FilePath  $env:ALLUSERSPROFILE\docker\certs.d\ca.pem -Force -Encoding ascii
            }
        }
        catch {
            Write-Error "Unable to save all certificates"    
        }
    }
    
    end {
    }
}