├── .gitignore
├── .kitchen-hyperv.yml
├── .kitchen.yml
├── DSCResources
└── HubotPrerequisites
│ ├── HubotPrerequisites.psd1
│ └── HubotPrerequisites.schema.psm1
├── Examples
├── HubotInstallService_Example.ps1
├── HubotInstall_Example.ps1
└── dsc_configuration.ps1
├── Hubot.psd1
├── Hubot.psm1
├── LICENSE
├── README.md
├── Tests
├── HubotHelpers_Unit_Tests.ps1
├── HubotInstallService_Unit_Tests.ps1
├── HubotInstall_Unit_Tests.ps1
├── Hubot_MOF_Generation_Tests.ps1
├── appveyor.pester.ps1
└── integration
│ └── default
│ └── pester
│ └── default.tests.ps1
├── appveyor.yml
├── build.ps1
└── psakeBuild.ps1
/.gitignore:
--------------------------------------------------------------------------------
1 | .kitchen
2 | Artifact
--------------------------------------------------------------------------------
/.kitchen-hyperv.yml:
--------------------------------------------------------------------------------
1 | ---
2 | driver:
3 | name: hyperv
4 | parent_vhd_folder: C:\VMs\Win2016TP5Core\Virtual Hard Disks
5 | parent_vhd_name: Win2016TP5Core.vhdx
6 | memory_startup_bytes: 2GB
7 | vm_switch: HyperVWifi
8 | vm_generation: 2
9 | transport:
10 | name: winrm
11 | username: Administrator
12 | password: L0C4L4dmin!
13 | winrm_transport: plaintext
14 |
15 | provisioner:
16 | name: dsc
17 | dsc_local_configuration_manager_version: wmf5
18 | dsc_local_configuration_manager:
19 | reboot_if_needed: true
20 | debug_mode: none
21 | allow_module_overwrite: true
22 | configuration_script_folder: examples
23 | configuration_script: dsc_configuration.ps1
24 | configuration_data_variable: configData
25 | configuration_name: Hubot
26 | modules_path: .
27 | modules_from_gallery:
28 | - cChoco
29 | - xPSDesiredStateConfiguration
30 |
31 | verifier:
32 | name: pester
33 | test_folder: Tests
34 |
35 | platforms:
36 | - name: Win2016TP5-wmf5
37 | driver:
38 | parent_vhd: C:\VMs\Win2016TP5Core\Virtual Hard Disks\Win2016TP5Core.vhdx
39 | provisioner:
40 | dsc_local_configuration_manager_version: wmf5
41 |
42 | suites:
43 | - name: default
44 |
--------------------------------------------------------------------------------
/.kitchen.yml:
--------------------------------------------------------------------------------
1 | ---
2 | driver:
3 | name: vagrant
4 | communicator: winrm
5 | gui: true
6 |
7 | transport:
8 | name: winrm
9 |
10 | provisioner:
11 | name: dsc
12 | retry_on_exit_code:
13 | - 35
14 | dsc_local_configuration_manager_version: wmf5
15 | dsc_local_configuration_manager:
16 | action_after_reboot: StopConfiguration
17 | reboot_if_needed: false
18 | debug_mode: none
19 | allow_module_overwrite: true
20 | configuration_script_folder: examples
21 | configuration_script: dsc_configuration.ps1
22 | configuration_data_variable: configData
23 | configuration_name: Hubot
24 | modules_path: .
25 | modules_from_gallery:
26 | - xPSDesiredStateConfiguration
27 |
28 | platforms:
29 | - name: Win2016TP5Core
30 | driver:
31 | box: MattHodge/Win2016TP5Core
32 | gui: true
33 | provisioner:
34 | dsc_local_configuration_manager_version: wmf5
35 |
36 | verifier:
37 | name: pester
38 | test_folder: Tests
39 |
40 | suites:
41 | - name: default
42 |
--------------------------------------------------------------------------------
/DSCResources/HubotPrerequisites/HubotPrerequisites.psd1:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MattHodge/Hubot-DSC-Resource/6267c9fa6b5e724e734fbbb05c5bba4c0c2d67c2/DSCResources/HubotPrerequisites/HubotPrerequisites.psd1
--------------------------------------------------------------------------------
/DSCResources/HubotPrerequisites/HubotPrerequisites.schema.psm1:
--------------------------------------------------------------------------------
1 | Configuration HubotPrerequisites
2 | {
3 | param
4 | (
5 | [Parameter(Mandatory=$true)]
6 | [ValidateSet("Present", "Absent")]
7 | [string]
8 | $Ensure
9 | )
10 |
11 | # Import the module that defines custom resources
12 | Import-DscResource -ModuleName PSDesiredStateConfiguration
13 | Import-DscResource -Name MSFT_xRemoteFile -ModuleName xPSDesiredStateConfiguration
14 |
15 | $nodeFile = 'node-v5.12.0-x64.msi'
16 | $gitFile = 'Git-2.9.0-64-bit.exe'
17 | $nssmFile = 'nssm-2.24.zip'
18 |
19 | xRemoteFile dlNode
20 | {
21 | Uri = 'https://nodejs.org/dist/v5.12.0/node-v5.12.0-x64.msi'
22 | DestinationPath = "$($env:Temp)\$($nodeFile)"
23 | MatchSource = $false
24 | }
25 |
26 | Package nodejs
27 | {
28 | Ensure = $Ensure
29 | Path = "$($env:Temp)\$($nodeFile)"
30 | Name = "Node.js"
31 | ProductId = "CE9C30F7-140C-4A6B-95C8-8304CCBF0145"
32 | Arguments = '/qn ALLUSERS=1 REBOOT=ReallySuppress'
33 | DependsOn = '[xRemoteFile]dlNode'
34 | ReturnCode = 0
35 | }
36 |
37 | xRemoteFile dlGit
38 | {
39 | Uri = 'https://github.com/git-for-windows/git/releases/download/v2.9.0.windows.1/Git-2.9.0-64-bit.exe'
40 | DestinationPath = "$($env:Temp)\$($gitFile)"
41 | MatchSource = $false
42 | }
43 |
44 | Package git
45 | {
46 | Ensure = $Ensure
47 | Path = "$($env:Temp)\$($gitFile)"
48 | Name = "Git version 2.9.0"
49 | ProductId = ""
50 | Arguments = '/VERYSILENT /NORESTART /NOCANCEL /SP- /COMPONENTS="icons,icons\quicklaunch,ext,ext\shellhere,ext\guihere,assoc,assoc_sh" /LOG'
51 | DependsOn = '[xRemoteFile]dlGit'
52 | }
53 |
54 | xRemoteFile dlnssm
55 | {
56 | Uri = 'https://nssm.cc/release/nssm-2.24.zip'
57 | DestinationPath = "$($env:Temp)\$($nssmFile)"
58 | MatchSource = $false
59 | }
60 |
61 | Archive nssm
62 | {
63 | Ensure = $Ensure
64 | Path = "$($env:Temp)\$($nssmFile)"
65 | Destination = "C:\nssm"
66 | DependsOn = '[xRemoteFile]dlnssm'
67 | }
68 | }
--------------------------------------------------------------------------------
/Examples/HubotInstallService_Example.ps1:
--------------------------------------------------------------------------------
1 | # See dsc_configuration.ps1
--------------------------------------------------------------------------------
/Examples/HubotInstall_Example.ps1:
--------------------------------------------------------------------------------
1 | # See dsc_configuration.ps1
--------------------------------------------------------------------------------
/Examples/dsc_configuration.ps1:
--------------------------------------------------------------------------------
1 | configuration Hubot
2 | {
3 | Import-DscResource -ModuleName PSDesiredStateConfiguration
4 | Import-DscResource -Name MSFT_xRemoteFile -ModuleName xPSDesiredStateConfiguration
5 | Import-DscResource -ModuleName @{ModuleName="Hubot"; RequiredVersion="1.1.5"}
6 |
7 | node $AllNodes.Where{$_.Role -eq "Hubot"}.NodeName
8 | {
9 | # Set an adapter for hubot to use
10 | Environment hubotadapter
11 | {
12 | Name = 'HUBOT_ADAPTER'
13 | Value = $Node.HubotAdapter
14 | Ensure = 'Present'
15 | }
16 |
17 | # Set the hubot debug level - either debug or info
18 | Environment hubotdebug
19 | {
20 | Name = 'HUBOT_LOG_LEVEL'
21 | Value = 'debug'
22 | Ensure = 'Present'
23 | }
24 |
25 | # Set any other environment variables that may be required for the hubot scripts
26 | Environment hubotslacktoken
27 | {
28 | Name = 'HUBOT_SLACK_TOKEN'
29 | Value = $Node.SlackAPIKey
30 | Ensure = 'Present'
31 | }
32 |
33 | # Install the Prereqs using the same Hubot user
34 | HubotPrerequisites installPreqs
35 | {
36 | Ensure = 'Present'
37 | }
38 |
39 | # Download the HubotWindows Repo
40 | xRemoteFile hubotRepo
41 | {
42 | DestinationPath = "$($env:Temp)\HubotWindows.zip"
43 | Uri = "https://github.com/MattHodge/HubotWindows/releases/download/0.0.2/HubotWindows-0.0.2.zip"
44 | }
45 |
46 | # Extract the Hubot Repo
47 | Archive extractHubotRepo
48 | {
49 | Path = "$($env:Temp)\HubotWindows.zip"
50 | Destination = $Node.HubotBotPath
51 | Ensure = 'Present'
52 | DependsOn = '[xRemoteFile]hubotRepo'
53 | }
54 |
55 | # Install Hubot
56 | HubotInstall installHubot
57 | {
58 | BotPath = $Node.HubotBotPath
59 | Ensure = 'Present'
60 | DependsOn = '[Archive]extractHubotRepo','[HubotPrerequisites]installPreqs'
61 | }
62 |
63 | # Install Hubot as a service using NSSM
64 | HubotInstallService myhubotservice
65 | {
66 | BotPath = $Node.HubotBotPath
67 | ServiceName = "Hubot_$($Node.HubotBotName)"
68 | BotAdapter = $Node.HubotAdapter
69 | Ensure = 'Present'
70 | DependsOn = '[HubotInstall]installHubot','[HubotPrerequisites]installPreqs'
71 | }
72 | }
73 | }
74 |
75 | # Configuration Data
76 | $configData = @{
77 | AllNodes = @(
78 | @{
79 | NodeName = 'localhost';
80 | Role = 'Hubot'
81 | SlackAPIKey = 'xoxb-XXXXXXXXXXXXXXXX-XXXXXXXXXXXXXXXX'
82 | HubotAdapter = 'slack'
83 | HubotBotName = 'bender'
84 | HubotBotPath = 'C:\myhubot'
85 | }
86 | )
87 | }
--------------------------------------------------------------------------------
/Hubot.psd1:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MattHodge/Hubot-DSC-Resource/6267c9fa6b5e724e734fbbb05c5bba4c0c2d67c2/Hubot.psd1
--------------------------------------------------------------------------------
/Hubot.psm1:
--------------------------------------------------------------------------------
1 | # Defines the values for the resource's Ensure property.
2 | enum Ensure
3 | {
4 | # The resource must be absent.
5 | Absent
6 | # The resource must be present.
7 | Present
8 | }
9 |
10 | class HubotHelpers
11 | {
12 | [string] RefreshPathVariable ()
13 | {
14 | $updatedPath = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User")
15 | return $updatedPath
16 | }
17 |
18 | [bool] CheckPathExists ([string]$Path)
19 | {
20 | if (Test-Path -Path $Path)
21 | {
22 | Write-Verbose "Directory $($Path) exists."
23 | return $true
24 | }
25 | else
26 | {
27 | Write-Verbose "Directory $($Path) exists."
28 | return $false
29 | }
30 | }
31 |
32 | [PSCustomObject] RunProcess([string]$FilePath, [string]$ArgumentList, [string]$WorkingDirectory)
33 | {
34 | $env:Path = [HubotHelpers]::new().RefreshPathVariable()
35 |
36 | $pinfo = New-Object System.Diagnostics.ProcessStartInfo
37 |
38 | if (-not([string]::IsNullOrEmpty($WorkingDirectory)))
39 | {
40 | $pinfo.WorkingDirectory = $WorkingDirectory
41 | }
42 |
43 | $pinfo.FileName = $FilePath
44 | $pinfo.RedirectStandardError = $true
45 | $pinfo.RedirectStandardOutput = $true
46 | $pinfo.UseShellExecute = $false
47 | $pinfo.StandardOutputEncoding = [System.Text.Encoding]::Unicode
48 | $pinfo.StandardErrorEncoding = [System.Text.Encoding]::Unicode
49 | $pinfo.Arguments = $ArgumentList
50 | $p = New-Object System.Diagnostics.Process
51 | $p.StartInfo = $pinfo
52 | $p.Start() | Out-Null
53 | $p.WaitForExit()
54 | $stdout = $p.StandardOutput.ReadToEnd()
55 | $stderr = $p.StandardError.ReadToEnd()
56 |
57 | $output = @{}
58 | $output.filepath = $FilePath
59 | $output.arg = $ArgumentList
60 | $output.workingdirectory = $WorkingDirectory
61 | $output.stdout = $stdout
62 | $output.stderr = $stderr
63 | $output.exitcode = $p.ExitCode
64 |
65 | $returnObj = New-Object -Property $output -TypeName PSCustomObject
66 | Write-Verbose $returnObj.stdout
67 | return $returnObj
68 | }
69 |
70 | [string] GetNSSMPath ()
71 | {
72 | if (Test-Path -Path 'C:\nssm')
73 | {
74 | # get latest version installed
75 | $path = ((Get-ChildItem -Path C:\nssm\*\win64\nssm.exe)[-1]).FullName
76 | Write-Verbose "Found nssm at $($path)"
77 | return $path
78 | }
79 | else
80 | {
81 | throw 'NSSM folder cannot be found at C:\nssm'
82 | }
83 | }
84 | }
85 |
86 | [DscResource()]
87 | class HubotInstall
88 | {
89 |
90 | # A DSC resource must define at least one key property.
91 | [DscProperty(Key)]
92 | [string]$BotPath
93 |
94 | [DscProperty(Mandatory)]
95 | [Ensure]$Ensure
96 |
97 | [DscProperty(NotConfigurable)]
98 | [String]$NodeModulesPath
99 |
100 | # Gets the resource's current state.
101 | [HubotInstall] Get()
102 | {
103 | $GetObject = [HubotInstall]::new()
104 | $GetObject.BotPath = $this.BotPath
105 | $GetObject.Ensure = $this.Ensure
106 | $GetObject.NodeModulesPath = Join-Path -Path $this.BotPath -ChildPath 'node_modules'
107 |
108 |
109 | if([HubotHelpers]::new().CheckPathExists($GetObject.NodeModulesPath))
110 | {
111 | $GetObject.Ensure = [Ensure]::Present
112 | }
113 | else
114 | {
115 | $GetObject.Ensure = [Ensure]::Absent
116 | }
117 |
118 |
119 | return $GetObject
120 | }
121 |
122 | # Sets the desired state of the resource.
123 | [void] Set()
124 | {
125 | $Helpers = [HubotHelpers]::new()
126 |
127 | $env:Path = $Helpers.RefreshPathVariable()
128 |
129 | if (!($Helpers.CheckPathExists($this.BotPath)))
130 | {
131 | throw "The path $($this.BotPath) must exist and contain a Hubot installation in it. You can clone one from here: https://github.com/MattHodge/HubotWindows"
132 | }
133 |
134 | if (Get-Command -CommandType Application -Name npm -ErrorAction SilentlyContinue)
135 | {
136 | $npmPath = (Get-Command -CommandType Application -Name npm)[0].Source
137 | }
138 | else
139 | {
140 | throw "npm cannot be found. Cannot continue."
141 | }
142 |
143 |
144 | if ($this.Ensure -eq [Ensure]::Present)
145 | {
146 | $npmCmd = 'install'
147 | }
148 | else
149 | {
150 | $npmCmd = 'uninstall'
151 | }
152 |
153 | Write-Verbose -Message "$($npmCmd)ing CoffeeScript at $($this.BotPath)"
154 |
155 | Start-Process -FilePath $npmPath -ArgumentList "$($npmCmd) coffee-script" -Wait
156 |
157 | Write-Verbose "$($npmCmd)ing all required npm modules"
158 |
159 | Start-Process -FilePath $npmPath -ArgumentList $npmCmd -Wait
160 |
161 | if ($this.Ensure -eq [Ensure]::Absent)
162 | {
163 | Remove-Item -Path $this.NodeModulesPath -Force
164 | }
165 | }
166 |
167 | # Tests if the resource is in the desired state.
168 | [bool] Test()
169 | {
170 | $TestObject = $This.Get()
171 |
172 | # present case
173 | if ($TestObject.Ensure -eq [Ensure]::Present)
174 | {
175 | return $true
176 | }
177 | # absent case
178 | else
179 | {
180 | return $false
181 | }
182 | }
183 | }
184 |
185 | [DscResource()]
186 | class HubotInstallService
187 | {
188 |
189 | # Path where the Hubot is located
190 | [DscProperty(Key)]
191 | [string]$BotPath
192 |
193 | # Name for the Hubot service
194 | [DscProperty(Mandatory)]
195 | [string]$ServiceName
196 |
197 | # Credential to run the service under
198 | [DscProperty()]
199 | [PSCredential]$Credential
200 |
201 | # Bot adapter for Hubot to be used. Used as a paramater to start the server (-a $botadapter)
202 | [DscProperty(Mandatory)]
203 | [string]$BotAdapter
204 |
205 | [DscProperty(Mandatory)]
206 | [Ensure]$Ensure
207 |
208 | [DscProperty(NotConfigurable)]
209 | [Boolean]$State_ServiceExists
210 |
211 | [DscProperty(NotConfigurable)]
212 | [Boolean]$State_ServiceRunning
213 |
214 | [DscProperty(NotConfigurable)]
215 | [string]$NSSMAppParameters
216 |
217 | [DscProperty(NotConfigurable)]
218 | [string[]]$NSSMCmdsToRun
219 |
220 | [DscProperty(NotConfigurable)]
221 | [string]$BotLoggingPath
222 |
223 | [DscProperty(NotConfigurable)]
224 | [Boolean]$State_NSSMAppParameters
225 |
226 | # Gets the resource's current state.
227 | [HubotInstallService] Get()
228 | {
229 | $Helpers = [HubotHelpers]::new()
230 |
231 | $GetObject = [HubotInstallService]::new()
232 | $GetObject.BotPath = $this.BotPath
233 | $GetObject.Ensure = $this.Ensure
234 | $GetObject.ServiceName = $this.ServiceName
235 | $GetObject.Credential = $this.Credential
236 | $GetObject.BotAdapter = $this.BotAdapter
237 | $GetObject.NSSMAppParameters = "/c .\bin\hubot.cmd -a $($this.BotAdapter)"
238 |
239 | # set default states to save having nested if statements
240 | $GetObject.State_ServiceExists = $false
241 | $GetObject.State_ServiceRunning = $false
242 | $GetObject.State_NSSMAppParameters = $false
243 |
244 | if (Get-Service -Name $this.ServiceName -ErrorAction SilentlyContinue)
245 | {
246 | $GetObject.State_ServiceExists = $true
247 |
248 | # Check service is running
249 | if ((Get-Service -Name $this.ServiceName).Status -eq 'Running')
250 | {
251 | $GetObject.State_ServiceRunning = $true
252 | }
253 |
254 | $nssmPath = $Helpers.GetNSSMPath()
255 |
256 | # check if appparams set correctly
257 | $currentAppParams = ($Helpers.RunProcess($nssmPath,"get $($this.ServiceName) AppParameters",$null)).stdout
258 |
259 | # need to use trim to remove white spaces
260 | if ([string]$currentAppParams.Trim() -eq [string]$GetObject.NSSMAppParameters)
261 | {
262 | $GetObject.State_NSSMAppParameters = $true
263 | }
264 | }
265 |
266 | # get the bot logging path
267 | $GetObject.BotLoggingPath = Join-Path -Path $GetObject.BotPath -ChildPath 'Logs'
268 |
269 | # build an array of NSSM Commands to execute based upon user input
270 | $GetObject.NSSMCmdsToRun = @(
271 | "install $($this.ServiceName) cmd.exe"
272 | "set $($this.ServiceName) AppDirectory $($GetObject.BotPath)"
273 | "set $($this.ServiceName) AppParameters ""/c .\bin\hubot.cmd -a $($GetObject.BotAdapter)"""
274 | "set $($this.ServiceName) AppStdout ""$($GetObject.BotLoggingPath)\$($GetObject.ServiceName)_log.txt"""
275 | "set $($this.ServiceName) AppStderr ""$($GetObject.BotLoggingPath)\$($GetObject.ServiceName)_error.txt"""
276 | "set $($this.ServiceName) AppDirectory $($GetObject.BotPath)"
277 | "set $($this.ServiceName) AppRotateFiles 1"
278 | "set $($this.ServiceName) AppRotateOnline 1"
279 | "set $($this.ServiceName) AppRotateSeconds 86400"
280 | "set $($this.ServiceName) Description Hubot Service"
281 | "set $($this.ServiceName) Start SERVICE_AUTO_START"
282 | )
283 |
284 | # if a credetial is passed with no password assume LocalSystem
285 | if ([string]::IsNullOrEmpty($GetObject.Credential))
286 | {
287 | Write-Verbose "No credential passed, using LocalSystem."
288 | $GetObject.NSSMCmdsToRun += "set $($GetObject.ServiceName) ObjectName LocalSystem"
289 | }
290 | # if a credential is passed with a password
291 | else
292 | {
293 | Write-Verbose "Credential passed, using username $($GetObject.Credential.UserName)."
294 | $GetObject.NSSMCmdsToRun += "set $($GetObject.ServiceName) ObjectName .\$($GetObject.Credential.UserName) $($GetObject.Credential.GetNetworkCredential().Password)"
295 | }
296 |
297 | return $GetObject
298 | }
299 |
300 | [void] Set()
301 | {
302 | $Helpers = [HubotHelpers]::new()
303 |
304 | $env:Path = $Helpers.RefreshPathVariable()
305 |
306 | $TestObject = $This.Get()
307 |
308 | $nssmPath = $Helpers.GetNSSMPath()
309 |
310 | if ($this.Ensure -eq [Ensure]::Present)
311 | {
312 |
313 | if ($TestObject.State_ServiceExists)
314 | {
315 | Write-Verbose "Removing old service"
316 | Stop-Service -Name $this.ServiceName -Force
317 | $Helpers.RunProcess($nssmPath,"remove $($this.ServiceName) confirm",$null) | Out-Null
318 | }
319 |
320 | # Create bot logging path
321 | Write-Verbose "Creating bot logging path at $($TestObject.BotLoggingPath)"
322 | New-Item -Path $TestObject.BotLoggingPath -Force -ItemType Directory
323 |
324 | ForEach ($cmd in $TestObject.NSSMCmdsToRun)
325 | {
326 | $Helpers.RunProcess($nssmPath,$cmd,$null) | Out-Null
327 | }
328 |
329 | Start-Service -Name $this.ServiceName
330 | }
331 | else
332 | {
333 | Write-Verbose "Removing Bot Service $($this.ServiceName)"
334 | Stop-Service -Name $this.ServiceName -Force -ErrorAction SilentlyContinue
335 | $Helpers.RunProcess($nssmPath,"remove $($this.ServiceName) confirm",$null) | Out-Null
336 | }
337 | }
338 |
339 | # Tests if the resource is in the desired state.
340 | [bool] Test()
341 | {
342 | $TestObject = $This.Get()
343 |
344 | # present case
345 | if ($this.Ensure -eq [Ensure]::Present)
346 | {
347 | # If any of the possible states for the service are false, not in desired state
348 | return (-not($TestObject.psobject.Properties.Where({$PSItem.Name -like 'State_*'}).Value -contains $false))
349 | }
350 | # absent case
351 | else
352 | {
353 | return (-not($TestObject.State_ServiceExists))
354 | }
355 | }
356 | }
357 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Matthew Hodgkins
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Hubot (DSC Resource)
2 |
3 | 
4 |
5 | [](https://ci.appveyor.com/project/MattHodge/hubot-dsc-resource)
6 |
7 | The **Hubot** module contains the `HubotPrerequisites`, `HubotInstall` and `HubotInstallService` DSC Resources to install Hubot on Windows.
8 |
9 | This resource installs and runs Hubot as a service on Windows using NSSM.
10 |
11 | I recommend using the [HubotWindows](https://github.com/MattHodge/HubotWindows) repository to get the Hubot setup on your node and use the following DSC resources to configure it.
12 |
13 | For an introduction to using Hubot on Windows, take a look at [ChatOps on Windows with Hubot and PowerShell](https://hodgkins.io/chatops-on-windows-with-hubot-and-powershell).
14 |
15 | ## Resources
16 |
17 | ### HubotPrerequisites
18 |
19 | Parameter | Notes | Mandatory
20 | | --- | --- | --- |
21 | Ensure | Ensures that the prerequisites is Present or Absent | `Yes`
22 |
23 | ### HubotInstall
24 |
25 | Parameter | Notes | Mandatory
26 | | --- | --- | --- |
27 | BotPath | Path that the Windows Hubot package is installed. (Get from here: https://github.com/MattHodge/HubotWindows) | `Yes`
28 | Ensure | Ensures that the bot is Present or Absent | `Yes`
29 |
30 | ### HubotInstallService
31 |
32 | Parameter | Notes | Mandatory
33 | | --- | --- | --- |
34 | BotPath | Path that the Windows Hubot package is installed. (Get from here: https://github.com/MattHodge/HubotWindows) | `Yes`
35 | ServiceName | Name to give the Hubot Windows service | `Yes`
36 | Credential | Credential of account to run the Hubot service under. If left blank, the service will run under the `SYSTEM` account. | `No`
37 | BotAdapter | The name of the Hubot adapter to use. (https://github.com/github/hubot/blob/master/docs/adapters.md) | `Yes`
38 | Ensure | Ensures that the bot is Present or Absent | `Yes`
39 |
40 | ## Examples
41 |
42 | You can find an installation example here: [dsc_configuration.ps1](Examples/dsc_configuration.ps1)
43 |
44 | ## Installation
45 |
46 | To install the module, use:
47 |
48 | `Install-Module -Name Hubot`
49 |
50 | A video of the installation on a remote machine:
51 |
52 |
55 |
56 | ## Packaging
57 |
58 | The DSC Resource Module is called `Hubot` and is available on the PowerShell Gallery:
59 | * https://www.powershellgallery.com/packages/Hubot
60 |
61 | ## Testing Using Test-Kitchen
62 | * Make sure the repo is cloned as `Hubot` or Test-Kitchen will not work.
63 |
64 | ## Versions
65 |
66 | ### 1.1.5
67 |
68 | * Updated module dependencies so it pulls down `xPSDesiredStateConfiguration` on install.
69 |
70 | ### 1.1.4
71 |
72 | * Removing dependency on `cChoco` and `Chocolatey`. This requires the node to reboot after installing Node.js as part of the `HubotPrerequisites` resource unfortunately.
73 |
74 |
75 | ### 1.1.3
76 |
77 | * Initial Release
78 |
--------------------------------------------------------------------------------
/Tests/HubotHelpers_Unit_Tests.ps1:
--------------------------------------------------------------------------------
1 | #################
2 | # TEST HEADER #
3 | #################
4 |
5 | $dscModule = 'Hubot'
6 |
7 | $originalLocation = Get-Location
8 |
9 | $here = Split-Path -Parent $MyInvocation.MyCommand.Path
10 | Set-Location $here
11 |
12 | describe "HubotHelpers" {
13 |
14 | ######################################
15 | # USED FOR TESTING CLASS BASED DSC #
16 | ######################################
17 |
18 | # To support $root\Tests folder structure
19 | If (Test-Path "..\$dscModule.psm1") {Copy-Item "..\$dscModule.psm1" 'TestDrive:\script.ps1'}
20 | # To support $root\Tests\Unit folder structure
21 | ElseIf (Test-Path "..\..\$dscModule.psm1") {Copy-Item "..\..\$dscModule.psm1" 'TestDrive:\script.ps1'}
22 | # Or simply throw...
23 | Else {Throw 'Unable to find source .psm1 file to test against.'}
24 |
25 | # Dot source the file to brint classes into current session
26 | . 'TestDrive:\script.ps1'
27 |
28 | ################
29 | # TEST START #
30 | ################
31 |
32 | $guid = (New-Guid).Guid
33 |
34 | context "RefreshPathVariable" {
35 | it "returns something" {
36 | [HubotHelpers]::new().RefreshPathVariable() | Should Not Be Null
37 | }
38 |
39 | it "return should contain atleast one semicolon" {
40 | [HubotHelpers]::new().RefreshPathVariable() | Should BeLike "*;*"
41 | }
42 |
43 | it "when split into array should have 2 or more items" {
44 | $array = [HubotHelpers]::new().RefreshPathVariable() -split ';'
45 | $array.Count | Should BeGreaterThan 1
46 | }
47 | }
48 |
49 | context "CheckPathExists" {
50 | it "returns true when path that exists is passed" {
51 | [HubotHelpers]::new().CheckPathExists("TestDrive:\") | Should Be $true
52 | }
53 | it "returns false when path that does not exist is passed" {
54 | [HubotHelpers]::new().CheckPathExists("TestDrive:\$($guid)") | Should Be $false
55 | }
56 | }
57 | }
58 |
59 | Set-Location $originalLocation
--------------------------------------------------------------------------------
/Tests/HubotInstallService_Unit_Tests.ps1:
--------------------------------------------------------------------------------
1 | #################
2 | # TEST HEADER #
3 | #################
4 |
5 | $dscModule = 'Hubot'
6 |
7 | $originalLocation = Get-Location
8 |
9 | $here = Split-Path -Parent $MyInvocation.MyCommand.Path
10 | Set-Location $here
11 |
12 |
13 | describe "HubotInstallService" {
14 |
15 | ######################################
16 | # USED FOR TESTING CLASS BASED DSC #
17 | ######################################
18 |
19 | # To support $root\Tests folder structure
20 | If (Test-Path "..\$dscModule.psm1") {Copy-Item "..\$dscModule.psm1" 'TestDrive:\script.ps1'}
21 | # To support $root\Tests\Unit folder structure
22 | ElseIf (Test-Path "..\..\$dscModule.psm1") {Copy-Item "..\..\$dscModule.psm1" 'TestDrive:\script.ps1'}
23 | # Or simply throw...
24 | Else {Throw 'Unable to find source .psm1 file to test against.'}
25 |
26 | # Dot source the file to brint classes into current session
27 | . 'TestDrive:\script.ps1'
28 |
29 | ################
30 | # TEST START #
31 | ################
32 |
33 |
34 | context "Get" {
35 | BeforeEach {
36 | $TestClass = [HubotInstallService]::new()
37 |
38 | # Generate credentials to use
39 | $password = ConvertTo-SecureString -String 'vagrant' -AsPlainText -Force
40 | $cred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList 'vagrant', $password
41 | }
42 |
43 | it "does not throw" {
44 | {
45 | $TestClass.BotPath = 'C:\myhubot'
46 | $TestClass.ServiceName = 'hubot_service'
47 | $TestClass.BotAdapter = 'slack'
48 | $TestClass.Ensure = 'Present'
49 | $TestClass.Get()
50 | } | Should Not Throw
51 | }
52 |
53 | it "nssm cmds are propegated" {
54 |
55 | $TestClass = [HubotInstallService]::new()
56 | $TestClass.BotPath = 'C:\myhubot'
57 | $TestClass.ServiceName = 'hubot_service'
58 | $TestClass.BotAdapter = 'slack'
59 | $TestClass.Ensure = 'Present'
60 | $TestClass.Get().NSSMCmdsToRun | Should Not BeNullOrEmpty
61 | }
62 |
63 | it "hubot generates the correct logging path under the botpath" {
64 |
65 | $TestClass = [HubotInstallService]::new()
66 | $TestClass.BotPath = 'C:\myhubot'
67 | $TestClass.ServiceName = 'hubot_service'
68 | $TestClass.BotAdapter = 'slack'
69 | $TestClass.Ensure = 'Present'
70 | $TestClass.Get().BotLoggingPath | Should BeExactly 'C:\myhubot\Logs'
71 | }
72 |
73 | it "nssm uses the correct log path when creating service" {
74 |
75 | $TestClass = [HubotInstallService]::new()
76 | $TestClass.BotPath = 'C:\myhubot'
77 | $TestClass.ServiceName = 'hubot_service'
78 | $TestClass.BotAdapter = 'slack'
79 | $TestClass.Ensure = 'Present'
80 | $TestClass.Get().NSSMCmdsToRun -contains "set hubot_service AppStdout `"c:\myhubot\Logs\hubot_service_log.txt`"" | Should Be $true
81 | }
82 |
83 | it "nssm uses the correct errorlog path when creating service" {
84 |
85 | $TestClass = [HubotInstallService]::new()
86 | $TestClass.BotPath = 'C:\myhubot'
87 | $TestClass.ServiceName = 'hubot_service'
88 | $TestClass.BotAdapter = 'slack'
89 | $TestClass.Ensure = 'Present'
90 | $TestClass.Get().NSSMCmdsToRun -contains "set hubot_service AppStderr `"c:\myhubot\Logs\hubot_service_error.txt`"" | Should Be $true
91 | }
92 |
93 | it "nssm uses localsystem to create service when no credential passed" {
94 |
95 | $TestClass = [HubotInstallService]::new()
96 | $TestClass.BotPath = 'C:\myhubot'
97 | $TestClass.ServiceName = 'hubot_service'
98 | $TestClass.BotAdapter = 'slack'
99 | $TestClass.Ensure = 'Present'
100 | $TestClass.Get().NSSMCmdsToRun -contains "set hubot_service ObjectName LocalSystem" | Should Be $true
101 | }
102 |
103 | it "nssm uses credentials to create service when a is credential passed" {
104 |
105 | $TestClass = [HubotInstallService]::new()
106 | $TestClass.BotPath = 'C:\myhubot'
107 | $TestClass.ServiceName = 'hubot_service'
108 | $TestClass.BotAdapter = 'slack'
109 | $TestClass.Ensure = 'Present'
110 | $TestClass.Credential = $cred
111 | $TestClass.Get().NSSMCmdsToRun -contains "set hubot_service ObjectName .\$($cred.UserName) $($cred.GetNetworkCredential().Password)" | Should Be $true
112 | }
113 |
114 | it "nsssm uses the user provided hubot adapater" {
115 |
116 | $TestClass = [HubotInstallService]::new()
117 | $TestClass.BotPath = 'C:\myhubot'
118 | $TestClass.ServiceName = 'hubot_service'
119 | $TestClass.BotAdapter = 'slack'
120 | $TestClass.Ensure = 'Present'
121 | $TestClass.Get().NSSMAppParameters | Should Be '/c .\bin\hubot.cmd -a slack'
122 | }
123 | }
124 | }
125 |
126 | Set-Location $originalLocation
--------------------------------------------------------------------------------
/Tests/HubotInstall_Unit_Tests.ps1:
--------------------------------------------------------------------------------
1 | #################
2 | # TEST HEADER #
3 | #################
4 |
5 | $dscModule = 'Hubot'
6 |
7 | $originalLocation = Get-Location
8 |
9 | $here = Split-Path -Parent $MyInvocation.MyCommand.Path
10 | Set-Location $here
11 |
12 |
13 | describe "HubotInstall" {
14 |
15 | ######################################
16 | # USED FOR TESTING CLASS BASED DSC #
17 | ######################################
18 |
19 | # To support $root\Tests folder structure
20 | If (Test-Path "..\$dscModule.psm1") {Copy-Item "..\$dscModule.psm1" 'TestDrive:\script.ps1'}
21 | # To support $root\Tests\Unit folder structure
22 | ElseIf (Test-Path "..\..\$dscModule.psm1") {Copy-Item "..\..\$dscModule.psm1" 'TestDrive:\script.ps1'}
23 | # Or simply throw...
24 | Else {Throw 'Unable to find source .psm1 file to test against.'}
25 |
26 | # Dot source the file to brint classes into current session
27 | . 'TestDrive:\script.ps1'
28 |
29 | ################
30 | # TEST START #
31 | ################
32 |
33 | $guid = (New-Guid).Guid
34 |
35 | context "Get" {
36 | it "does not throw" {
37 | {
38 | $x = [HubotInstall]::new()
39 | $x.BotPath = 'C:\myhubot'
40 | $x.Ensure = 'Present'
41 | $x.Get()
42 | } | Should Not Throw
43 | }
44 |
45 | it "returns a [HubotInstall] class" {
46 | $x = [HubotInstall]::new()
47 | $x.BotPath = 'TestDrive:\'
48 | $x.Ensure = 'Present'
49 | $x.Get().GetType().Name | Should Be 'HubotInstall'
50 | }
51 |
52 | it "returns ensure present if BotPath exists" {
53 | $fakeModuleFolder = Join-Path $TestDrive 'node_modules'
54 | New-Item -Path $fakeModuleFolder -ItemType Directory -Force
55 | $x = [HubotInstall]::new()
56 | $x.BotPath = $TestDrive
57 | $x.Get().Ensure | Should Be 'Present'
58 | }
59 |
60 | it "node modules path should be valid" {
61 | $fakeModuleFolder = Join-Path $TestDrive 'node_modules'
62 | New-Item -Path $fakeModuleFolder -ItemType Directory -Force
63 | $x = [HubotInstall]::new()
64 | $x.BotPath = $TestDrive
65 | $x.Get().NodeModulesPath | Should Be $fakeModuleFolder
66 | }
67 |
68 | it "returns ensure absent if BotPath does not exist" {
69 | $x = [HubotInstall]::new()
70 | $x.BotPath = "TestDrive:\$($guid)"
71 | $x.Get().Ensure | Should Be 'Absent'
72 | }
73 | }
74 |
75 | context "Set" {
76 | BeforeEach {
77 | $TestClass = [HubotInstall]::new()
78 |
79 | Mock Start-Process { return $true }
80 |
81 | $getCmdMockObject = @"
82 |
83 |
84 |
85 | System.Management.Automation.ApplicationInfo
86 | System.Management.Automation.CommandInfo
87 | System.Object
88 |
89 | npm.cmd
90 |
91 | C:\Program Files\nodejs\npm.cmd
92 | .cmd
93 | C:\Program Files\nodejs\npm.cmd
94 | C:\Program Files\nodejs\npm.cmd
95 | 0.0.0.0
96 |
97 |
98 | System.Management.Automation.SessionStateEntryVisibility
99 | System.Enum
100 | System.ValueType
101 | System.Object
102 |
103 | Public
104 | 0
105 |
106 |
107 |
108 | System.Collections.ObjectModel.ReadOnlyCollection`1[[System.Management.Automation.PSTypeName, System.Management.Automation, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]
109 | System.Object
110 |
111 |
112 | System.String
113 |
114 |
115 | npm.cmd
116 |
117 |
118 | System.Management.Automation.CommandTypes
119 | System.Enum
120 | System.ValueType
121 | System.Object
122 |
123 | Application
124 | 32
125 |
126 |
127 |
128 |
129 |
130 | System.Management.Automation.RemotingCapability
131 | System.Enum
132 | System.ValueType
133 | System.Object
134 |
135 | PowerShell
136 | 1
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 | System.Diagnostics.FileVersionInfo
145 | System.Object
146 |
147 | File: C:\Program Files\nodejs\npm.cmd_x000D__x000A_InternalName: _x000D__x000A_OriginalFilename: _x000D__x000A_FileVersion: _x000D__x000A_FileDescription: _x000D__x000A_Product: _x000D__x000A_ProductVersion: _x000D__x000A_Debug: False_x000D__x000A_Patched: False_x000D__x000A_PreRelease: False_x000D__x000A_PrivateBuild: False_x000D__x000A_SpecialBuild: False_x000D__x000A_Language: _x000D__x000A_
148 |
149 |
150 |
151 | 0
152 |
153 | 0
154 | 0
155 | C:\Program Files\nodejs\npm.cmd
156 | 0
157 |
158 |
159 | false
160 | false
161 | false
162 | false
163 | false
164 |
165 |
166 |
167 |
168 |
169 | 0
170 | 0
171 | 0
172 |
173 | 0
174 |
175 |
176 |
177 |
178 | 0.0.0.0
179 | 0.0.0.0
180 |
181 |
182 |
183 |
184 |
185 | "@
186 |
187 | $myobj = [System.Management.Automation.PSSerializer]::Deserialize($getCmdMockObject)
188 |
189 | Mock Get-Command { return $myobj }
190 |
191 | }
192 |
193 | it "throws when botpath is not found" {
194 | {
195 | $TestClass.BotPath = "$TestDrive\$($guid)"
196 | $result = $TestClass.Get()
197 | $result.Set()
198 | } | Should Throw
199 | }
200 |
201 | it "does not throw when botpath is found and ensure is present" {
202 | {
203 | $TestClass.BotPath = "$TestDrive"
204 | $result = $TestClass.Get()
205 | $result.Ensure = 'Present'
206 | $result.Set()
207 | } | Should Not Throw
208 | }
209 |
210 | it "throws when npm not found" {
211 | Mock Get-Command { return $null }
212 |
213 | {
214 | $fakeModuleFolder = Join-Path $TestDrive 'node_modules'
215 | New-Item -Path $fakeModuleFolder -ItemType Directory -Force
216 | $TestClass.BotPath = $TestDrive
217 | $result = $TestClass.Get()
218 | $result.Set()
219 | } | Should Throw
220 | }
221 |
222 | it "does not throw when npm not found" {
223 | {
224 | $fakeModuleFolder = Join-Path $TestDrive 'node_modules'
225 | New-Item -Path $fakeModuleFolder -ItemType Directory -Force
226 | $TestClass.BotPath = $TestDrive
227 | $result = $TestClass.Get()
228 | $result.Set()
229 | } | Should Not Throw
230 | }
231 |
232 | it "calls start-process twice to uninstall coffee-script and then npm" {
233 | $fakeModuleFolder = Join-Path $TestDrive 'node_modules'
234 | New-Item -Path $fakeModuleFolder -ItemType Directory -Force
235 | $TestClass.BotPath = $TestDrive
236 | $result = $TestClass.Get()
237 | $result.Ensure = 'Absent'
238 | $result.Set()
239 | Assert-MockCalled Start-Process 2 -Exactly -ParameterFilter {$FilePath -like "*npm*" -and $ArgumentList -like "*uninstall*"} -Scope It
240 | }
241 |
242 |
243 | it "calls start-process twice to install coffee-script and then npm install" {
244 | $fakeModuleFolder = Join-Path $TestDrive 'node_modules'
245 | New-Item -Path $fakeModuleFolder -ItemType Directory -Force
246 | $TestClass.BotPath = $TestDrive
247 | $result = $TestClass.Get()
248 | $result.Set()
249 | Assert-MockCalled Start-Process 2 -Exactly -ParameterFilter {$FilePath -like "*npm*" -and $ArgumentList -like "*install*"} -Scope It
250 | }
251 | }
252 | }
253 |
254 | Set-Location $originalLocation
--------------------------------------------------------------------------------
/Tests/Hubot_MOF_Generation_Tests.ps1:
--------------------------------------------------------------------------------
1 | describe "Hubot DSC Module - MOF Testing" {
2 |
3 | $dscExamplePath = Join-path -Path '.\Examples' -ChildPath 'dsc_configuration.ps1'
4 |
5 | context "Get-DSCResource" {
6 | $res = Get-DscResource
7 |
8 | it "returns something" {
9 | $res | Should Not Be Null
10 | }
11 |
12 | $hubotRes = @(
13 | 'HubotInstall'
14 | 'HubotInstallService'
15 | 'HubotPrerequisites'
16 | )
17 |
18 | ForEach ($h in $hubotRes)
19 | {
20 | it "contains resource $($h)" {
21 | $res.Name -contains $h | Should Be $true
22 | }
23 | }
24 | }
25 |
26 | context "Example dsc_configuration" {
27 | it "is valid powershell" {
28 | $psfile = Get-Content -Path $dscExamplePath -Raw -ErrorAction Stop
29 | $errors = $null
30 | $null = [System.Management.Automation.PSParser]::Tokenize($psfile, [ref]$errors)
31 | $errors.Count | Should Be 0
32 | }
33 |
34 | . $dscExamplePath
35 |
36 | it "module version of Hubot.psd1 matches module version in $dscExamplePath" {
37 | $moduleVersion = Select-String -Path .\Hubot.psd1 -Pattern "ModuleVersion = '(.*)'"
38 | $moduleVersion = $moduleVersion.Matches.Groups[1].Value
39 |
40 | $exampleVersion = Select-String -Path $dscExamplePath -Pattern 'ModuleName=\"Hubot\"\; RequiredVersion=\"(.*)\"'
41 | $exampleVersion = $exampleVersion.Matches.Groups[1].Value
42 |
43 | $exampleVersion | Should BeExactly $moduleVersion
44 | }
45 |
46 | it "does not have a real api key" {
47 | $configData.AllNodes.SlackAPIKey | Should Be 'xoxb-XXXXXXXXXXXXXXXX-XXXXXXXXXXXXXXXX'
48 | }
49 |
50 | it "does not throw on mof generation" {
51 | { Hubot -OutputPath TestDrive:\mof -ConfigurationData $configData } | Should Not Throw
52 | }
53 |
54 | it "mof file is created on disk" {
55 | Test-Path -Path "TestDrive:\mof\localhost.mof" | Should Be $true
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/Tests/appveyor.pester.ps1:
--------------------------------------------------------------------------------
1 | # This script will invoke pester tests
2 | # It should invoke on PowerShell v2 and later
3 | # We serialize XML results and pull them in appveyor.yml
4 |
5 | #If Finalize is specified, we collect XML output, upload tests, and indicate build errors
6 | param(
7 | [switch]$Finalize,
8 | [switch]$Test,
9 | [string]$ProjectRoot = $ENV:APPVEYOR_BUILD_FOLDER,
10 | [string]$TestPath = "$ProjectRoot\Tests"
11 | )
12 |
13 | #Initialize some variables, move to the project root
14 | $Timestamp = Get-date -uformat "%Y%m%d-%H%M%S"
15 | $PSVersion = $PSVersionTable.PSVersion.Major
16 | $TestFile = "TestResults_PS$PSVersion`_$TimeStamp.xml"
17 |
18 | $Address = "https://ci.appveyor.com/api/testresults/nunit/$($env:APPVEYOR_JOB_ID)"
19 | Set-Location $ProjectRoot
20 |
21 | $Verbose = @{}
22 | if($env:APPVEYOR_REPO_BRANCH -and $env:APPVEYOR_REPO_BRANCH -notlike "master")
23 | {
24 | $Verbose.add("Verbose",$True)
25 | }
26 |
27 | #Run a test with the current version of PowerShell, upload results
28 | if($Test)
29 | {
30 | "`n`tSTATUS: Testing with PowerShell $PSVersion`n"
31 |
32 | Import-Module Pester
33 |
34 | if ($env:APPVEYOR)
35 | {
36 | Invoke-Pester @Verbose -Path $TestPath -OutputFormat NUnitXml -OutputFile "$ProjectRoot\$TestFile" -PassThru |
37 | Export-Clixml -Path "$ProjectRoot\PesterResults_PS$PSVersion`_$Timestamp.xml"
38 | }
39 | else
40 | {
41 | Invoke-Pester @Verbose -Path $TestPath
42 | }
43 |
44 |
45 | If($env:APPVEYOR_JOB_ID)
46 | {
47 | (New-Object 'System.Net.WebClient').UploadFile( $Address, "$ProjectRoot\$TestFile" )
48 | }
49 | }
50 |
51 | #If finalize is specified, display errors and fail build if we ran into any
52 | If($Finalize)
53 | {
54 | #Show status...
55 | $AllFiles = Get-ChildItem -Path $ProjectRoot\PesterResults*.xml | Select -ExpandProperty FullName
56 | "`n`tSTATUS: Finalizing results`n"
57 | "COLLATING FILES:`n$($AllFiles | Out-String)"
58 |
59 | #What failed?
60 | $Results = @( Get-ChildItem -Path "$ProjectRoot\PesterResults_PS*.xml" | Import-Clixml )
61 |
62 | $FailedCount = $Results |
63 | Select -ExpandProperty FailedCount |
64 | Measure-Object -Sum |
65 | Select -ExpandProperty Sum
66 |
67 | if ($FailedCount -gt 0) {
68 |
69 | $FailedItems = $Results |
70 | Select -ExpandProperty TestResult |
71 | Where {$_.Passed -notlike $True}
72 |
73 | "FAILED TESTS SUMMARY:`n"
74 | $FailedItems | ForEach-Object {
75 | $Item = $_
76 | [pscustomobject]@{
77 | Describe = $Item.Describe
78 | Context = $Item.Context
79 | Name = "It $($Item.Name)"
80 | Result = $Item.Result
81 | }
82 | } |
83 | Sort Describe, Context, Name, Result |
84 | Format-List
85 |
86 | throw "$FailedCount tests failed."
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/Tests/integration/default/pester/default.tests.ps1:
--------------------------------------------------------------------------------
1 | describe "Hubot" {
2 |
3 | Context "File Paths and Installs" {
4 | $pathsToTest = @(
5 | 'C:\Program Files\Git\bin\git.exe'
6 | 'C:\Program Files\nodejs\node.exe'
7 | 'C:\nssm'
8 | 'C:\myhubot'
9 | 'C:\myhubot\node_modules'
10 | 'C:\myhubot\node_modules\edge'
11 | 'C:\myhubot\node_modules\edge-ps'
12 | 'C:\myhubot\node_modules\coffee-script'
13 | )
14 |
15 | ForEach ($p in $pathsToTest)
16 | {
17 | it "$($p) exists" {
18 | Test-Path -Path $p | Should Be $true
19 | }
20 | }
21 | }
22 |
23 | Context "Environment Variables" {
24 | $envVarsToTest = @(
25 | 'HUBOT_ADAPTER'
26 | 'HUBOT_LOG_LEVEL'
27 | 'HUBOT_SLACK_TOKEN'
28 | )
29 |
30 | ForEach ($e in $envVarsToTest)
31 | {
32 | It "environment variable $($e) should exist" {
33 | Test-Path -Path "Env:\$($e)" | Should Be $true
34 | }
35 |
36 | }
37 | }
38 |
39 | Context "Hubot Service" {
40 | It "should exist" {
41 | { Get-Service -Name Hubot_bender } | Should Not throw
42 | }
43 | It "should be running" {
44 | (Get-Service -Name Hubot_bender).Status | Should BeExactly 'Running'
45 | }
46 |
47 | $svc = Get-WMIObject -Class Win32_Service -Filter "Name='hubot_bender'" | Select-Object *
48 |
49 | It "should have a description of Hubot Service" {
50 | $svc.Description | Should BeExactly 'Hubot Service'
51 | }
52 |
53 | It "should be running under Hubot account" {
54 | $svc.StartName | Should BeExactly 'LocalSystem'
55 | }
56 |
57 | It "should have a startmode of Auto" {
58 | $svc.StartMode | Should BeExactly 'Auto'
59 | }
60 |
61 | It "should create stdout log file at c:\myhubot\Logs\hubot_bender_log.txt" {
62 | 'c:\myhubot\Logs\hubot_bender_log.txt' | Should Exist
63 | }
64 |
65 | It "should create stderr log file at c:\myhubot\Logs\hubot_bender_error.txt" {
66 | 'c:\myhubot\Logs\hubot_bender_error.txt' | Should Exist
67 | }
68 |
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | # See http://www.appveyor.com/docs/appveyor-yml for many more options
2 |
3 | # Skip on updates to the readme.
4 | # We can force this by adding [skip ci] or [ci skip] anywhere in commit message
5 | skip_commits:
6 | message: /updated readme.*/
7 | files:
8 | - README.md
9 |
10 | os: "WMF 5"
11 |
12 | install:
13 | - ps: Write-Output "Build Number $($env:APPVEYOR_BUILD_NUMBER)"
14 | - ps: Write-Output "Build Version $($env:APPVEYOR_BUILD_VERSION)"
15 |
16 | build: false
17 | version: '1.1.{build}'
18 |
19 | before_test:
20 | - ps: .\build.ps1
21 |
22 | test_script:
23 | # Test with native PS version
24 | # - ps: . .\Tests\appveyor.pester.ps1 -Test
25 | # Finalize pass - collect and upload results
26 | - ps: . .\Tests\appveyor.pester.ps1 -Finalize
27 |
28 | branches:
29 | # whitelist
30 | only:
31 | - master
32 |
33 | #on_finish:
34 | #- ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
35 |
--------------------------------------------------------------------------------
/build.ps1:
--------------------------------------------------------------------------------
1 | #Requires -RunAsAdministrator
2 | [cmdletbinding()]
3 | param(
4 | [string[]]$Task = 'default'
5 | )
6 |
7 | if (!(Get-PackageProvider -Name Nuget -ErrorAction SilentlyContinue))
8 | {
9 | Install-PackageProvider -Name NuGet -Force
10 | }
11 |
12 | $modulesToInstall = @(
13 | 'Pester',
14 | 'psake',
15 | 'PSDeploy',
16 | 'PSScriptAnalyzer'
17 | 'cChoco' # Required by DSC Resource
18 | 'xPSDesiredStateConfiguration' # Required by DSC Resource
19 | )
20 |
21 | ForEach ($module in $modulesToInstall)
22 | {
23 | if (!(Get-Module -Name $module -ListAvailable))
24 | {
25 | Install-Module -Name $module -Force -Scope CurrentUser
26 | }
27 | }
28 |
29 | if (-not($env:APPVEYOR))
30 | {
31 | $env:appveyor_build_version = '10.10.10'
32 | Write-Verbose "Not on AppVeyor, using fake version of $($env:appveyor_build_version)."
33 | }
34 |
35 | # Invoke PSake
36 | Invoke-psake -buildFile "$PSScriptRoot\psakeBuild.ps1" -taskList $Task -parameters @{'build_version' = $env:appveyor_build_version} -Verbose:$VerbosePreference
--------------------------------------------------------------------------------
/psakeBuild.ps1:
--------------------------------------------------------------------------------
1 | properties {
2 | # $unitTests = "$PSScriptRoot\Tests\unit"
3 | $unitTests = Get-ChildItem .\Tests\*Unit_Tests.ps1
4 | $mofTests = Get-ChildItem .\Tests\*MOF_Generation_Tests.ps1
5 | $DSCResources = Get-ChildItem *.psd1,*.psm1 -Recurse
6 |
7 | # originalPath is the one containing the .psm1 and .psd1
8 | $originalPath = $PSScriptRoot
9 |
10 | # pathInModuleDir is the path where the symbolic link will be created which points to your repo
11 | $pathInModuleDir = 'C:\Program Files\WindowsPowerShell\Modules\Hubot'
12 | }
13 |
14 | task default -depends Analyze, Test, MOFTestDeploy, MOFTest, BuildArtifact
15 |
16 | task TestProperties {
17 | Assert ($build_version -ne $null) "build_version should not be null"
18 | }
19 |
20 | task Analyze {
21 | ForEach ($resource in $DSCResources)
22 | {
23 | try
24 | {
25 | Write-Output "Running ScriptAnalyzer on $($resource)"
26 |
27 | if ($env:APPVEYOR)
28 | {
29 | Add-AppveyorTest -Name "PsScriptAnalyzer" -Outcome Running
30 | $timer = [System.Diagnostics.Stopwatch]::StartNew()
31 | }
32 |
33 | $saResults = Invoke-ScriptAnalyzer -Path $resource.FullName -Verbose:$false
34 | if ($saResults) {
35 | $saResults | Format-Table
36 | $saResultsString = $saResults | Out-String
37 | if ($saResults.Severity -contains 'Error' -or $saResults.Severity -contains 'Warning')
38 | {
39 | if ($env:APPVEYOR)
40 | {
41 | Add-AppveyorMessage -Message "PSScriptAnalyzer output contained one or more result(s) with 'Error or Warning' severity.`
42 | Check the 'Tests' tab of this build for more details." -Category Error
43 | Update-AppveyorTest -Name "PsScriptAnalyzer" -Outcome Failed -ErrorMessage $saResultsString
44 | }
45 |
46 | Write-Error -Message "One or more Script Analyzer errors/warnings where found in $($resource). Build cannot continue!"
47 | }
48 | else
49 | {
50 | Write-Output "All ScriptAnalyzer tests passed"
51 |
52 | if ($env:APPVEYOR)
53 | {
54 | Update-AppveyorTest -Name "PsScriptAnalyzer" -Outcome Passed -StdOut $saResultsString -Duration $timer.ElapsedMilliseconds
55 | }
56 | }
57 | }
58 | }
59 | catch
60 | {
61 | $ErrorMessage = $_.Exception.Message
62 | $FailedItem = $_.Exception.ItemName
63 | Write-Output $ErrorMessage
64 | Write-Output $FailedItem
65 | Write-Error "The build failed when working with $($resource)."
66 | }
67 |
68 | }
69 | }
70 |
71 | task Test {
72 | ForEach ($unitTest in $unitTests)
73 | {
74 | $testResults = .\Tests\appveyor.pester.ps1 -Test -TestPath $unitTest
75 |
76 | if ($testResults.FailedCount -gt 0) {
77 | $testResults | Format-List
78 | Write-Error -Message 'One or more Pester unit tests failed. Build cannot continue!'
79 | }
80 | }
81 | }
82 |
83 | task MOFTestDeploy -depends Analyze, Test {
84 | try
85 | {
86 | if ($env:APPVEYOR)
87 | {
88 | # copy into the userprofile in appveyor so the module can be loaded
89 | Start-Process -FilePath 'robocopy.exe' -ArgumentList "$PSScriptRoot $env:USERPROFILE\Documents\WindowsPowerShell\Modules\Hubot /S /R:1 /W:1" -Wait -NoNewWindow
90 | }
91 | else
92 | {
93 | # on a local system just create a symlink
94 | New-Item -ItemType SymbolicLink -Path $pathInModuleDir -Target $originalPath -Force | Out-Null
95 | }
96 | }
97 | catch
98 | {
99 | $ErrorMessage = $_.Exception.Message
100 | $FailedItem = $_.Exception.ItemName
101 | Write-Output $ErrorMessage
102 | Write-Output $FailedItem
103 | throw "The build failed when trying prepare files for MOF tests."
104 | }
105 | }
106 |
107 | task MOFTest -depends Analyze, Test, MOFTestDeploy {
108 | ForEach ($moftest in $mofTests)
109 | {
110 | $testResults = .\Tests\appveyor.pester.ps1 -Test -TestPath $moftest
111 | if ($testResults.FailedCount -gt 0) {
112 | $testResults | Format-List
113 | Write-Error -Message 'One or more Pester unit tests failed. Build cannot continue!'
114 | }
115 | }
116 | }
117 |
118 | task BuildArtifact -depends Analyze, Test, MOFTestDeploy, MOFTest {
119 | # Create a clean to build the artifact
120 | New-Item -Path "$PSScriptRoot\Artifact" -ItemType Directory -Force
121 |
122 | # Copy the correct items into the artifacts directory, filtering out the junk
123 | Start-Process -FilePath 'robocopy.exe' -ArgumentList "`"$($PSScriptRoot)`" `"$($PSScriptRoot)\Artifact\Hubot`" /S /R:1 /W:1 /XD Artifact .kitchen .git /XF .gitignore build.ps1 psakeBuild.ps1 *.yml *.xml" -Wait -NoNewWindow
124 |
125 | # Create a zip file artifact
126 | Compress-Archive -Path $PSScriptRoot\Artifact\Hubot -DestinationPath $PSScriptRoot\Artifact\Hubot-$build_version.zip -Force
127 |
128 | if ($env:APPVEYOR)
129 | {
130 | # Push the artifact into appveyor
131 | $zip = Get-ChildItem -Path $PSScriptRoot\Artifact\*.zip | % { Push-AppveyorArtifact $_.FullName -FileName $_.Name }
132 | }
133 | }
--------------------------------------------------------------------------------