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 { [DscProperty(Key)] [Ensure]$Ensure [DscProperty()] [string]$version [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 -contains "DockerMsftProvider")) { Write-Verbose "Install DockerMsftProvider Provider" Install-Module -Name DockerMsftProvider -Repository psgallery -Force } $package = Find-Package -ProviderName DockerMsftProvider -RequiredVersion $GetVersion if ($package) { Write-Verbose "Required version package was found in provider. Installing" Install-Package $package -Update } else { Write-Verbose "Package was not found in provider" } } 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") { if ((Get-PackageProvider).Name -contains "DockerMsftProvider") { $dockerPackage = Find-Package -ProviderName DockerMsftProvider if ($dockerPackage) { if ($dockerPackage.Version -eq $this.version) { return $true } else { Write-Verbose "Incorrect docker version installed" return $false } } else { Write-Verbose "Docker is not installed" return $false } } else { Write-Verbose "DockerMsftProvider Package Provider is missing" 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 { # A DSC resource must define at least one key property. [DscProperty(Key)] [string]$registryURI [DscProperty(Mandatory)] [string]$Certificate # Mandatory indicates the property is required and DSC will guarantee it is set. [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 { # A DSC resource must define at least one key property. [DscProperty(Key)] [Ensure]$Ensure [DscProperty()] [string]$BaseConfigJson='{}' [DscProperty()] [string[]] $InsecureRegistries [DscProperty()] [string[]] $Labels [DscProperty()] [string]$DaemonBinding='tcp://0.0.0.0:2375' [DscProperty()] [boolean] $ExposeAPI [DscProperty()] [boolean] $RestartOnChange # 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 } if($this.exposeApi -eq $true){ $pendingConfiguration | Add-Member -Name "hosts" -MemberType NoteProperty -Value @($this.daemonBinding, "npipe://") } return $pendingConfiguration | ConvertTo-Json } } # [DscResource()] indicates the class is a DSC resource. [DscResource()] class cDockerSwarm { # A DSC resource must define at least one key property. [DscProperty(Key)] [string]$SwarmMasterURI # Mandatory indicates the property is required and DSC will guarantee it is set. [DscProperty(Mandatory)] [Swarm] $SwarmMode [DscProperty()] [int] $ManagerCount=3 [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" $SwarmInfo = . "$($Env:ProgramFiles)\docker\docker.exe" -H $SwarmDockerHost 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 $SwarmDockerHost node promote $env:COMPUTERNAME } } elseif ($this.SwarmMode -eq [Swarm]::Active) { #$managers = docker -H $SwarmDockerHost info -f '{{ json .Swarm.Managers }}' 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($SwarmDockerHost, "manager") } else { Write-Verbose "Joining the Swarm as a worker" $this.JoinSwarm($SwarmDockerHost, "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 } [string]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,$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 swarm join-token $type -q break } catch { Write-Verbose "Waiting for manager to come online" start-sleep 5 } } if ($token) { . "$($Env:ProgramFiles)\docker\docker.exe" swarm join --token $token $this.SwarmMasterURI } else { write-verbose "Failed to Get token; can't join swarm" } } } |