Now with docker swarm support!

First up; the links:
PowerShell Gallery: https://www.powershellgallery.com/packages/cDscDocker/1.1.0
GitHub: https://github.com/walked/cDscDocker

First off; a new example DSC Configuration

Configuration dockerConfig
{
    Import-DscResource -module cDscDocker

    Node localhost{

    LocalConfigurationManager
    {
        RebootNodeIfNeeded = $true
    }
        cDscDocker test
        {
            docker = "docker" #key; mandatory
            Ensure = 'Present' #mandatory field (Available options: Present, Absent)
            swarm = 'Active' #mandatory field (Available options: Active, Inactive)
            swarmToken = "SWMTKN-1-guidguidguidguidguidguidguidguid" #your swarm token here
            swarmURI = "192.168.1.100:2377" #IP address of manager node

        }
    }
}

dockerConfig

Hm… let’s look at what we have (that’s new) here:

Ensure:

Ensure = 'Present'

This is for the state of docker; Present means docker is installed; Absent means it’s not.

swarm:

swarm = 'Active'

This is the property that identifies whether docker should be a swarm member.

swarmToken, swarmURI:

swarmToken = "SWMTKN-1-guidguidguidguidguidguidguidguid" #your swarm token here
swarmURI = "192.168.1.100:2377" #IP address of manager node

These are only evaluated if the swarm state is set to active, but the DSC configuration manager shows it is not. Please note that changing this token or URI will not change a swarm membership if is already a member of a swarm. I still need to find some way to do better docker swarm introspection for that.  

All in all; in my lab this is testing great. There are some corner cases where if a reboot is pending for docker installed outside of the control of DSC (e.g. get-service shows docker is installed, but it’s still needing a reboot), it can fail not so gracefully. I’m going to work on making sure that doesnt happen in the next release or two.  

Full code:

enum Ensure
{
	Present
	Absent
}

enum Swarm
{
	Active
	Inactive
}

[DscResource()]
class cDscDocker 
{
	[DscProperty(Key)] 
	[string]$docker

	[DscProperty(Mandatory)] 
	[Ensure]$Ensure

	[DscProperty()]
	[string]$swarmToken

	[DscProperty()]
	[string]$swarmURI

	[DscProperty(Mandatory)]
	[Swarm]$swarm

	#Equivalent of Set-TargetResource
	#Executes the current operation necessary to get the system to the desired state
	[void] Set()
	{
		$dockerProviderAvailable = $this.CheckDockerProvider()
		$dockerInstalled = $this.CheckDocker()
		$dockerInSwarm = $this.CheckDockerSwarm()
		Write-Verbose "Settings"
		Write-Verbose "Docker $($this.Ensure)"
		Write-Verbose "Swarm $($this.swarm)"

		switch($this.Ensure)
		{
			Present{
				if(-not $dockerProviderAvailable)
				{
					Write-Verbose "Docker Provider not Available; Installing Provider"
					$this.InstallDockerProvider()
				}

				if(-not $dockerInstalled)
				{
					Write-Verbose "Docker not installed; installing docker"
					$this.InstallDocker()
				}

				if($global:DSCMachineStatus -ne 1)
				{
					Write-Verbose "Not pending reboot"
					Write-Verbose "Entering Switch"
					switch($this.swarm)
					{

						Active{
							Write-Verbose "Setting Swarm to Active"
							if(-not $dockerInSwarm){
								Write-Verbose "Not in docker swarm; executing join method"
								$this.JoinDockerSwarm()
							}

						}
						Inactive{
							Write-Verbose "Setting swarm to inactive"
							if($dockerInSwarm){
								$this.LeaveDockerSwarm()
							}

						}
					}
				}

			}
			Absent{
				if($dockerInSwarm){
					$this.LeaveDockerSwarm()
				}
				if($dockerInstalled){
					$this.UninstallDocker()
				}

			}
		}

	}

	#Equivalent of Test-TargetResource
	#Returns true if system is in the state specified; false if a modification needs to take place
	[bool] Test()
	{
		$objectValidState = $false
		$dockerPresent = $this.CheckDocker()
		if($dockerPresent)
		{
			$dockerSwarm = $this.CheckDockerSwarm()
		}
		else
		{
			$dockerSwarm = $false
		}

		if ($this.Ensure -eq [Ensure]::Present)
		{

			if ($dockerPresent)
			{

				if($this.swarm -eq [Swarm]::Active)
				{
					if($dockerSwarm -eq $true)
					{
						Write-Verbose "Docker is present (Expect: Present). Swarm is Active (Expect: Active). VALID STATE"
						$objectValidState = $true;
					}

					if($dockerSwarm -eq $false)
					{
						Write-Verbose "Docker is present (Expect: Present). Swarm is Inactive (Expect: Active). INVALID STATE"
						return $false;
					}
				}
				elseif($this.swarm -eq [Swarm]::Inactive)
				{
					if($dockerSwarm -eq $true)
					{
						Write-Verbose "Docker is present (Expect: Present). Swarm is Active (Expect: Inactive). INVALID STATE"
						return $false;
					}
					elseif($dockerSwarm -eq $false)
					{
						Write-Verbose "Docker is present (Expect: Present). Swarm is Inactive (Expect: Inactive). VALID STATE"
						$objectValidState = $true
					}
				}

				#Fallback; setting this item in case return route is missed
				$objectValidState = $true

			}

			if (-not $dockerPresent)
			{
				Write-Verbose "Docker is Absent (Expect: Present). INVALID STATE"
				return $false
			}
		}
		Else
		{
			if ($dockerPresent)
			{
				Write-Verbose "Docker is Present (Expect: Absent). INVALID STATE"
				return $false
			}
			if (-not $dockerPresent)
			{
				Write-Verbose "Docker is Absent (Expect: Absent). VALID STATE"
				$objectValidState = $true
			}

		}

		return $objectValidState

	}

	#Equivalent of the Get-TargetResource
	#Get's the current state of the system in cDscDocker object form
	[cDscDocker] Get()
	{
		if ($this.CheckDocker())
		{
			$this.Ensure = [Ensure]::Present
		}
		else
		{
			$this.Ensure = [Ensure]::Absent
		}

		return $this

	}

	#Swarm Action, join
	[void]JoinDockerSwarm()
	{
		Write-Verbose "Joining Docker Swarm"
		docker swarm join --token $($this.swarmToken) $($this.swarmURI)

	}

	#Swarm action, leave
	[void]LeaveDockerSwarm()
	{
		docker swarm leave --force
	}

	[bool]CheckDockerSwarm()
	{
		$returnValue = $false

		if($this.CheckDocker()){
			$info = docker info
			foreach($item in $info)
			{
				if($item -match "swarm")
				{
					if($item -match "\sactive")
					{
						return $true
					}
					elseif($item -match "inactive")
					{
						return $false
					}
				}
			}
		}

		return $returnValue
	}
	#Docker Action, Verify
	[bool]CheckDocker()
	{
        $installed = $false

        if($this.CheckDockerProvider())
        {

		    $packages = Get-Package -ProviderName DockerMsftProvider
		    foreach ($p in $packages)
		    {
			    if ($p.Name -eq 'docker')
			    {
				    $installed = $true
			    }
		    }

			#In case this was installed as a service via other means; e.g. not the DockerMsftProvider
			$services = Get-Service | Where-Object {$_.Name -like "docker"}
			if ($services.Length )
			{
				$installed = $true
			}
        }

		return $installed
	}

	#Docker Action, Install
	[void]InstallDocker()
	{
		Install-Package -Name docker -ProviderName DockerMsftProvider -Force
		$global:DSCMachineStatus = 1
	}

	#Docker Action, Uninstall
	[void]UninstallDocker()
	{
		Uninstall-Package -ProviderName DockerMsftProvider -Name docker -Force #Uninstall docker; will move to a function if re-used elsewhere
	}

	#Docker Provider Action; verify
	[bool]CheckDockerProvider()
	{
		$available = $false
		$providers = Get-PackageProvider -ListAvailable

		foreach($provider in $providers)
        {
		    if ($provider.Name -eq "DockerMsftProvider")
		    {
			    $available = $true
		    }
        }

		return $available
	}

	#Docker Provider Action; install
	[void]InstallDockerProvider()
	{
		Install-Module -Name DockerMsftProvider -Repository psgallery -Force
	}

}