ruby-pwsh 0.10.2 → 0.11.0.rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +16 -38
- data/README.md +29 -25
- data/lib/puppet/provider/dsc_base_provider/dsc_base_provider.rb +23 -20
- data/lib/pwsh/version.rb +1 -1
- data/lib/pwsh/windows_powershell.rb +2 -2
- data/lib/pwsh.rb +31 -34
- data/spec/acceptance/dsc/basic.rb +209 -0
- data/spec/acceptance/dsc/cim_instances.rb +81 -0
- data/spec/acceptance/dsc/class.rb +129 -0
- data/spec/acceptance/dsc/complex.rb +139 -0
- data/spec/acceptance/support/setup_winrm.ps1 +6 -0
- data/spec/exit-27.ps1 +1 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/unit/puppet/provider/dsc_base_provider/dsc_base_provider_spec.rb +2084 -0
- data/spec/unit/pwsh/util_spec.rb +293 -0
- data/spec/unit/pwsh/version_spec.rb +10 -0
- data/spec/unit/pwsh/windows_powershell_spec.rb +121 -0
- data/spec/unit/pwsh_spec.rb +821 -0
- metadata +18 -22
- data/.gitattributes +0 -2
- data/.github/workflows/ci.yml +0 -109
- data/.gitignore +0 -23
- data/.pmtignore +0 -21
- data/.rspec +0 -3
- data/CHANGELOG.md +0 -204
- data/CODEOWNERS +0 -2
- data/CONTRIBUTING.md +0 -155
- data/DESIGN.md +0 -70
- data/Gemfile +0 -54
- data/LICENSE.txt +0 -21
- data/Rakefile +0 -188
- data/design-comms.png +0 -0
- data/metadata.json +0 -82
- data/pwshlib.md +0 -92
- data/ruby-pwsh.gemspec +0 -39
@@ -0,0 +1,209 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
require 'ruby-pwsh'
|
5
|
+
require 'securerandom'
|
6
|
+
|
7
|
+
powershell = Pwsh::Manager.instance(Pwsh::Manager.powershell_path, Pwsh::Manager.powershell_args)
|
8
|
+
module_path = File.expand_path('../../fixtures/modules', File.dirname(__FILE__))
|
9
|
+
powershellget_path = File.expand_path('powershellget/lib/puppet_x/powershellget/dsc_resources/PowerShellGet', module_path)
|
10
|
+
local_user = ['dsc', SecureRandom.uuid.slice(0, 7)].join('_')
|
11
|
+
local_pw = SecureRandom.uuid
|
12
|
+
|
13
|
+
def execute_reset_command(reset_command)
|
14
|
+
manager = Pwsh::Manager.instance(Pwsh::Manager.powershell_path, Pwsh::Manager.powershell_args)
|
15
|
+
result = manager.execute(reset_command)
|
16
|
+
raise result[:errormessage] unless result[:errormessage].nil?
|
17
|
+
end
|
18
|
+
|
19
|
+
RSpec.describe 'DSC Acceptance: Basic' do
|
20
|
+
let(:puppet_apply) do
|
21
|
+
"bundle exec puppet apply --modulepath #{module_path} --detailed-exitcodes --debug --trace"
|
22
|
+
end
|
23
|
+
let(:command) { "#{puppet_apply} -e \"#{manifest}\"" }
|
24
|
+
|
25
|
+
context 'Updating' do
|
26
|
+
let(:manifest) do
|
27
|
+
# This very awkward pattern is because we're not writing
|
28
|
+
# manifest files and need to pass them directly to puppet apply.
|
29
|
+
[
|
30
|
+
"dsc_psrepository { 'Trust PSGallery':",
|
31
|
+
"dsc_name => 'PSGallery',",
|
32
|
+
"dsc_ensure => 'Present',",
|
33
|
+
"dsc_installationpolicy => 'Trusted'",
|
34
|
+
'}'
|
35
|
+
].join(' ')
|
36
|
+
end
|
37
|
+
|
38
|
+
before(:all) do
|
39
|
+
reset_command = <<~RESET_COMMAND
|
40
|
+
$ErrorActionPreference = 'Stop'
|
41
|
+
Import-Module PowerShellGet
|
42
|
+
$ResetParameters = @{
|
43
|
+
Name = 'PSRepository'
|
44
|
+
ModuleName = '#{powershellget_path}'
|
45
|
+
Method = 'Set'
|
46
|
+
Property = @{
|
47
|
+
Name = 'PSGallery'
|
48
|
+
Ensure = 'Present'
|
49
|
+
InstallationPolicy = 'Untrusted'
|
50
|
+
}
|
51
|
+
}
|
52
|
+
Invoke-DscResource @ResetParameters | ConvertTo-Json -Compress
|
53
|
+
RESET_COMMAND
|
54
|
+
execute_reset_command(reset_command)
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'applies idempotently' do
|
58
|
+
first_run_result = powershell.execute(command)
|
59
|
+
expect(first_run_result[:exitcode]).to be(2)
|
60
|
+
expect(first_run_result[:native_stdout]).to match(/dsc_installationpolicy changed 'Untrusted' to 'Trusted'/)
|
61
|
+
expect(first_run_result[:native_stdout]).to match(/Updating: Finished/)
|
62
|
+
expect(first_run_result[:native_stdout]).to match(/Applied catalog/)
|
63
|
+
second_run_result = powershell.execute(command)
|
64
|
+
expect(second_run_result[:exitcode]).to be(0)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
context 'Creating' do
|
69
|
+
let(:manifest) do
|
70
|
+
[
|
71
|
+
"dsc_psmodule { 'Install BurntToast':",
|
72
|
+
"dsc_name => 'BurntToast',",
|
73
|
+
"dsc_ensure => 'Present',",
|
74
|
+
'}'
|
75
|
+
].join(' ')
|
76
|
+
end
|
77
|
+
|
78
|
+
before(:all) do
|
79
|
+
reset_command = <<~RESET_COMMAND
|
80
|
+
$ErrorActionPreference = 'Stop'
|
81
|
+
Import-Module PowerShellGet
|
82
|
+
Get-InstalledModule -Name BurntToast -ErrorAction SilentlyContinue |
|
83
|
+
Uninstall-Module -Force
|
84
|
+
RESET_COMMAND
|
85
|
+
execute_reset_command(reset_command)
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'applies idempotently' do
|
89
|
+
first_run_result = powershell.execute(command)
|
90
|
+
expect(first_run_result[:exitcode]).to be(2)
|
91
|
+
expect(first_run_result[:native_stdout]).to match(/dsc_ensure changed 'Absent' to 'Present'/)
|
92
|
+
expect(first_run_result[:native_stdout]).to match(/Creating: Finished/)
|
93
|
+
expect(first_run_result[:native_stdout]).to match(/Applied catalog/)
|
94
|
+
second_run_result = powershell.execute(command)
|
95
|
+
expect(second_run_result[:exitcode]).to be(0)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
context 'Deleting' do
|
100
|
+
let(:manifest) do
|
101
|
+
[
|
102
|
+
"dsc_psmodule { 'Install BurntToast':",
|
103
|
+
"dsc_name => 'BurntToast',",
|
104
|
+
"dsc_ensure => 'Absent',",
|
105
|
+
'}'
|
106
|
+
].join(' ')
|
107
|
+
end
|
108
|
+
|
109
|
+
before(:all) do
|
110
|
+
reset_command = <<~RESET_COMMAND
|
111
|
+
$ErrorActionPreference = 'Stop'
|
112
|
+
Import-Module PowerShellGet
|
113
|
+
$Installed = Get-InstalledModule -Name BurntToast -ErrorAction SilentlyContinue
|
114
|
+
If($null -eq $Installed) {
|
115
|
+
Install-Module -Name BurntToast -Scope AllUsers -Force
|
116
|
+
}
|
117
|
+
RESET_COMMAND
|
118
|
+
execute_reset_command(reset_command)
|
119
|
+
end
|
120
|
+
|
121
|
+
it 'applies idempotently' do
|
122
|
+
first_run_result = powershell.execute(command)
|
123
|
+
expect(first_run_result[:exitcode]).to be(2)
|
124
|
+
expect(first_run_result[:native_stdout]).to match(/dsc_ensure changed 'Present' to 'Absent'/)
|
125
|
+
expect(first_run_result[:native_stdout]).to match(/Deleting: Finished/)
|
126
|
+
expect(first_run_result[:native_stdout]).to match(/Applied catalog/)
|
127
|
+
second_run_result = powershell.execute(command)
|
128
|
+
expect(second_run_result[:exitcode]).to be(0)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
context 'PSDscRunAsCredential' do
|
133
|
+
before(:all) do
|
134
|
+
prep_command = <<~PREP_USER.strip
|
135
|
+
$ErrorActionPreference = 'Stop'
|
136
|
+
$User = Get-LocalUser -Name #{local_user} -ErrorAction SilentlyContinue
|
137
|
+
If ($null -eq $User) {
|
138
|
+
$Secure = ConvertTo-SecureString -String '#{local_pw}' -AsPlainText -Force
|
139
|
+
$User = New-LocalUser -Name #{local_user} -Password $Secure -Verbose
|
140
|
+
}
|
141
|
+
If ($User.Name -notin (Get-LocalGroupMember -Group Administrators).Name) {
|
142
|
+
Add-LocalGroupMember -Group Administrators -Member $User -Verbose
|
143
|
+
}
|
144
|
+
Get-LocalGroupMember -Group Administrators |
|
145
|
+
Where-Object Name -match '#{local_user}'
|
146
|
+
PREP_USER
|
147
|
+
execute_reset_command(prep_command)
|
148
|
+
end
|
149
|
+
|
150
|
+
after(:all) do
|
151
|
+
cleanup_command = <<~CLEANUP_USER.strip
|
152
|
+
Remove-LocalUser -Name #{local_user} -ErrorAction Stop
|
153
|
+
CLEANUP_USER
|
154
|
+
execute_reset_command(cleanup_command)
|
155
|
+
end
|
156
|
+
|
157
|
+
context 'with a valid credential' do
|
158
|
+
let(:manifest) do
|
159
|
+
[
|
160
|
+
"dsc_psrepository { 'Trust PSGallery':",
|
161
|
+
"dsc_name => 'PSGallery',",
|
162
|
+
"dsc_ensure => 'Present',",
|
163
|
+
"dsc_installationpolicy => 'Trusted',",
|
164
|
+
'dsc_psdscrunascredential => {',
|
165
|
+
"'user' => '#{local_user}',",
|
166
|
+
"'password' => Sensitive('#{local_pw}')",
|
167
|
+
'}',
|
168
|
+
'}'
|
169
|
+
].join(' ')
|
170
|
+
end
|
171
|
+
|
172
|
+
it 'applies idempotently without leaking secrets' do
|
173
|
+
first_run_result = powershell.execute(command)
|
174
|
+
expect(first_run_result[:exitcode]).to be(2)
|
175
|
+
expect(first_run_result[:native_stdout]).to match(/dsc_installationpolicy changed 'Untrusted' to 'Trusted'/)
|
176
|
+
expect(first_run_result[:native_stdout]).to match(/Updating: Finished/)
|
177
|
+
expect(first_run_result[:native_stdout]).to match(/Applied catalog/)
|
178
|
+
expect(first_run_result[:native_stdout]).to match(/'#<Sensitive \[value redacted\]>'/)
|
179
|
+
expect(first_run_result[:native_stdout]).not_to match(local_pw)
|
180
|
+
second_run_result = powershell.execute(command)
|
181
|
+
expect(second_run_result[:exitcode]).to be(0)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
context 'with an invalid credential' do
|
186
|
+
let(:manifest) do
|
187
|
+
[
|
188
|
+
"dsc_psrepository { 'Trust PSGallery':",
|
189
|
+
"dsc_name => 'PSGallery',",
|
190
|
+
"dsc_ensure => 'Present',",
|
191
|
+
"dsc_installationpolicy => 'Trusted',",
|
192
|
+
'dsc_psdscrunascredential => {',
|
193
|
+
"'user' => 'definitely_do_not_exist_here',",
|
194
|
+
"'password' => Sensitive('#{local_pw}')",
|
195
|
+
'}',
|
196
|
+
'}'
|
197
|
+
].join(' ')
|
198
|
+
end
|
199
|
+
|
200
|
+
it 'errors loudly without leaking secrets' do
|
201
|
+
first_run_result = powershell.execute(command)
|
202
|
+
expect(first_run_result[:exitcode]).to be(4)
|
203
|
+
expect(first_run_result[:stderr].first).to match(/dsc_psrepository: The user name or password is incorrect/)
|
204
|
+
expect(first_run_result[:native_stdout]).to match(/'#<Sensitive \[value redacted\]>'/)
|
205
|
+
expect(first_run_result[:native_stdout]).not_to match(local_pw)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# TODO: Test against mcollera/AccessControlDsc for CIM instance behavior
|
4
|
+
# 1. Make sure valid nested CIM instances can be passed to Invoke-DscResource
|
5
|
+
# 2. Make sure nested CIM instances can be read back from Invoke-DscResource
|
6
|
+
|
7
|
+
require 'spec_helper'
|
8
|
+
require 'ruby-pwsh'
|
9
|
+
|
10
|
+
# Needs to be declared here so it is usable in before and it blocks alike
|
11
|
+
test_manifest = File.expand_path('../../fixtures/test.pp', File.dirname(__FILE__))
|
12
|
+
fixtures_path = File.expand_path('../../fixtures', File.dirname(__FILE__))
|
13
|
+
|
14
|
+
def execute_reset_command(reset_command)
|
15
|
+
manager = Pwsh::Manager.instance(Pwsh::Manager.powershell_path, Pwsh::Manager.powershell_args)
|
16
|
+
result = manager.execute(reset_command)
|
17
|
+
raise result[:errormessage] unless result[:errormessage].nil?
|
18
|
+
end
|
19
|
+
|
20
|
+
RSpec.describe 'DSC Acceptance: Complex' do
|
21
|
+
let(:powershell) { Pwsh::Manager.instance(Pwsh::Manager.powershell_path, Pwsh::Manager.powershell_args) }
|
22
|
+
let(:module_path) { File.expand_path('../../fixtures/modules', File.dirname(__FILE__)) }
|
23
|
+
let(:puppet_apply) do
|
24
|
+
"bundle exec puppet apply #{test_manifest} --modulepath #{module_path} --detailed-exitcodes --trace"
|
25
|
+
end
|
26
|
+
|
27
|
+
context 'Managing the access control list of a folder' do
|
28
|
+
before do
|
29
|
+
reset_command = <<~RESET_COMMAND
|
30
|
+
$TestFolderPath = Join-Path -Path "#{fixtures_path}" -Childpath access_control
|
31
|
+
# Delete the test folder if it exists (to clear access control modifications)
|
32
|
+
If (Test-Path -Path $TestFolderPath -PathType Container) {
|
33
|
+
Remove-Item $TestFolderPath -Recurse -Force
|
34
|
+
}
|
35
|
+
# Create the test folder
|
36
|
+
New-Item $TestFolderPath -ItemType Directory
|
37
|
+
RESET_COMMAND
|
38
|
+
execute_reset_command(reset_command)
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'applies idempotently' do
|
42
|
+
content = <<~MANIFEST.strip
|
43
|
+
$test_folder_path = "#{fixtures_path}/access_control"
|
44
|
+
# Configure access to the test folder
|
45
|
+
dsc_ntfsaccessentry {'Test':
|
46
|
+
dsc_path => $test_folder_path,
|
47
|
+
dsc_accesscontrollist => [
|
48
|
+
{
|
49
|
+
principal => 'Everyone',
|
50
|
+
forceprincipal => true,
|
51
|
+
accesscontrolentry => [
|
52
|
+
{
|
53
|
+
accesscontroltype => 'Allow',
|
54
|
+
filesystemrights => ['FullControl'],
|
55
|
+
inheritance => 'This folder and files',
|
56
|
+
ensure => 'Present',
|
57
|
+
cim_instance_type => 'NTFSAccessControlEntry',
|
58
|
+
}
|
59
|
+
]
|
60
|
+
}
|
61
|
+
]
|
62
|
+
}
|
63
|
+
MANIFEST
|
64
|
+
File.write(test_manifest, content)
|
65
|
+
# Apply the test manifest
|
66
|
+
first_run_result = powershell.execute(puppet_apply)
|
67
|
+
expect(first_run_result[:exitcode]).to be(2)
|
68
|
+
# Access Control Set
|
69
|
+
expect(first_run_result[:native_stdout]).to match(/dsc_accesscontrollist: dsc_accesscontrollist changed/)
|
70
|
+
expect(first_run_result[:native_stdout]).to match(%r{dsc_ntfsaccessentry\[{:name=>"Test", :dsc_path=>".+/spec/fixtures/access_control"}\]: Updating: Finished})
|
71
|
+
expect(first_run_result[:stderr]).not_to match(/Error/)
|
72
|
+
expect(first_run_result[:stderr]).not_to match(/Warning: Provider returned data that does not match the Type Schema/)
|
73
|
+
expect(first_run_result[:stderr]).not_to match(/Value type mismatch/)
|
74
|
+
# Run finished
|
75
|
+
expect(first_run_result[:native_stdout]).to match(/Applied catalog/)
|
76
|
+
# Second run is idempotent
|
77
|
+
second_run_result = powershell.execute(puppet_apply)
|
78
|
+
expect(second_run_result[:exitcode]).to be(0)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
require 'ruby-pwsh'
|
5
|
+
|
6
|
+
powershell = Pwsh::Manager.instance(Pwsh::Manager.powershell_path, Pwsh::Manager.powershell_args)
|
7
|
+
module_path = File.expand_path('../../fixtures/modules', File.dirname(__FILE__))
|
8
|
+
psrc_path = File.expand_path('../../fixtures/example.psrc', File.dirname(__FILE__))
|
9
|
+
|
10
|
+
def execute_reset_command(reset_command)
|
11
|
+
manager = Pwsh::Manager.instance(Pwsh::Manager.powershell_path, Pwsh::Manager.powershell_args)
|
12
|
+
result = manager.execute(reset_command)
|
13
|
+
raise result[:errormessage] unless result[:errormessage].nil?
|
14
|
+
end
|
15
|
+
|
16
|
+
RSpec.describe 'DSC Acceptance: Class-Based Resource' do
|
17
|
+
let(:puppet_apply) do
|
18
|
+
"bundle exec puppet apply --modulepath #{module_path} --detailed-exitcodes --debug --trace"
|
19
|
+
end
|
20
|
+
let(:command) { "#{puppet_apply} -e \"#{manifest}\"" }
|
21
|
+
|
22
|
+
context 'Creating' do
|
23
|
+
let(:manifest) do
|
24
|
+
# This very awkward pattern is because we're not writing
|
25
|
+
# manifest files and need to pass them directly to puppet apply.
|
26
|
+
[
|
27
|
+
"dsc_jearolecapabilities { 'ExampleRoleCapability':",
|
28
|
+
"dsc_ensure => 'Present',",
|
29
|
+
"dsc_path => '#{psrc_path}',",
|
30
|
+
"dsc_description => 'Example role capability file'",
|
31
|
+
'}'
|
32
|
+
].join(' ')
|
33
|
+
end
|
34
|
+
|
35
|
+
before do
|
36
|
+
reset_command = <<~RESET_COMMAND
|
37
|
+
$PsrcPath = '#{psrc_path}'
|
38
|
+
# Delete the test PSRC fixture if it exists
|
39
|
+
If (Test-Path -Path $PsrcPath -PathType Leaf) {
|
40
|
+
Remove-Item $PsrcPath -Force
|
41
|
+
}
|
42
|
+
RESET_COMMAND
|
43
|
+
execute_reset_command(reset_command)
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'applies idempotently' do
|
47
|
+
first_run_result = powershell.execute(command)
|
48
|
+
expect(first_run_result[:exitcode]).to be(2)
|
49
|
+
expect(first_run_result[:native_stdout]).to match(//)
|
50
|
+
expect(first_run_result[:native_stdout]).to match(/dsc_description changed to 'Example role capability file'/)
|
51
|
+
expect(first_run_result[:native_stdout]).to match(/Creating: Finished/)
|
52
|
+
expect(first_run_result[:native_stdout]).to match(/Applied catalog/)
|
53
|
+
second_run_result = powershell.execute(command)
|
54
|
+
expect(second_run_result[:exitcode]).to be(0)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
context 'Updating' do
|
59
|
+
let(:manifest) do
|
60
|
+
# This very awkward pattern is because we're not writing
|
61
|
+
# manifest files and need to pass them directly to puppet apply.
|
62
|
+
[
|
63
|
+
"dsc_jearolecapabilities { 'ExampleRoleCapability':",
|
64
|
+
"dsc_ensure => 'Present',",
|
65
|
+
"dsc_path => '#{psrc_path}',",
|
66
|
+
"dsc_description => 'Updated role capability file'",
|
67
|
+
'}'
|
68
|
+
].join(' ')
|
69
|
+
end
|
70
|
+
|
71
|
+
before do
|
72
|
+
reset_command = <<~RESET_COMMAND
|
73
|
+
$PsrcPath = '#{psrc_path}'
|
74
|
+
# Delete the test PSRC fixture if it exists
|
75
|
+
If (Test-Path -Path $PsrcPath -PathType Leaf) {
|
76
|
+
Remove-Item $PsrcPath -Force
|
77
|
+
}
|
78
|
+
# Create the test PSRC fixture
|
79
|
+
New-Item $PsrcPath -ItemType File -Value "@{'Description' = 'Example role capability file'}"
|
80
|
+
RESET_COMMAND
|
81
|
+
execute_reset_command(reset_command)
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'applies idempotently' do
|
85
|
+
first_run_result = powershell.execute(command)
|
86
|
+
expect(first_run_result[:exitcode]).to be(2)
|
87
|
+
expect(first_run_result[:native_stdout]).to match(/dsc_description changed 'Example role capability file' to 'Updated role capability file'/)
|
88
|
+
expect(first_run_result[:native_stdout]).to match(/Updating: Finished/)
|
89
|
+
expect(first_run_result[:native_stdout]).to match(/Applied catalog/)
|
90
|
+
second_run_result = powershell.execute(command)
|
91
|
+
expect(second_run_result[:exitcode]).to be(0)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
context 'Deleting' do
|
96
|
+
let(:manifest) do
|
97
|
+
# This very awkward pattern is because we're not writing
|
98
|
+
# manifest files and need to pass them directly to puppet apply.
|
99
|
+
[
|
100
|
+
"dsc_jearolecapabilities { 'ExampleRoleCapability':",
|
101
|
+
"dsc_ensure => 'Absent',",
|
102
|
+
"dsc_path => '#{psrc_path}'",
|
103
|
+
'}'
|
104
|
+
].join(' ')
|
105
|
+
end
|
106
|
+
|
107
|
+
before do
|
108
|
+
reset_command = <<~RESET_COMMAND
|
109
|
+
$PsrcPath = '#{psrc_path}'
|
110
|
+
# Delete the test PSRC fixture if it exists
|
111
|
+
If (!(Test-Path -Path $PsrcPath -PathType Leaf)) {
|
112
|
+
# Create the test PSRC fixture
|
113
|
+
New-Item $PsrcPath -ItemType File -Value "@{'Description' = 'Updated'}"
|
114
|
+
}
|
115
|
+
RESET_COMMAND
|
116
|
+
execute_reset_command(reset_command)
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'applies idempotently' do
|
120
|
+
first_run_result = powershell.execute(command)
|
121
|
+
expect(first_run_result[:exitcode]).to be(2)
|
122
|
+
expect(first_run_result[:native_stdout]).to match(/dsc_ensure changed 'Present' to 'Absent'/)
|
123
|
+
expect(first_run_result[:native_stdout]).to match(/Deleting: Finished/)
|
124
|
+
expect(first_run_result[:native_stdout]).to match(/Applied catalog/)
|
125
|
+
second_run_result = powershell.execute(command)
|
126
|
+
expect(second_run_result[:exitcode]).to be(0)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
require 'ruby-pwsh'
|
5
|
+
|
6
|
+
# Needs to be declared here so it is usable in before and it blocks alike
|
7
|
+
test_manifest = File.expand_path('../../fixtures/test.pp', File.dirname(__FILE__))
|
8
|
+
fixtures_path = File.expand_path('../../fixtures', File.dirname(__FILE__))
|
9
|
+
|
10
|
+
def execute_reset_command(reset_command)
|
11
|
+
manager = Pwsh::Manager.instance(Pwsh::Manager.powershell_path, Pwsh::Manager.powershell_args)
|
12
|
+
result = manager.execute(reset_command)
|
13
|
+
raise result[:errormessage] unless result[:errormessage].nil?
|
14
|
+
end
|
15
|
+
|
16
|
+
RSpec.describe 'DSC Acceptance: Complex' do
|
17
|
+
let(:powershell) { Pwsh::Manager.instance(Pwsh::Manager.powershell_path, Pwsh::Manager.powershell_args) }
|
18
|
+
let(:module_path) { File.expand_path('../../fixtures/modules', File.dirname(__FILE__)) }
|
19
|
+
let(:puppet_apply) do
|
20
|
+
"bundle exec puppet apply #{test_manifest} --modulepath #{module_path} --detailed-exitcodes --trace"
|
21
|
+
end
|
22
|
+
|
23
|
+
context 'Adding a new website' do
|
24
|
+
before do
|
25
|
+
reset_command = <<~RESET_COMMAND
|
26
|
+
# Ensure IIS is not installed
|
27
|
+
$Feature = Get-WindowsFeature -Name 'Web-Asp-Net45'
|
28
|
+
If ($Feature.Installed) {
|
29
|
+
Remove-WindowsFeature -Name $Feature.Name -ErrorAction Stop
|
30
|
+
}
|
31
|
+
$DefaultSite = Get-Website 'Default Web Site' -ErrorAction Continue
|
32
|
+
$ExampleSite = Get-Website 'Puppet DSC Site' -ErrorAction Continue
|
33
|
+
If ($DefaultSite.State -eq 'Stopped') {
|
34
|
+
Start-Website -Name $DefaultSite.Name
|
35
|
+
}
|
36
|
+
If ($ExampleSite) {
|
37
|
+
Stop-Website -Name $ExampleSite.Name
|
38
|
+
Remove-Website -Name $ExampleSite.Name
|
39
|
+
Remove-Item -Path '#{fixtures_path}/website' -Recurse -Force -ErrorAction SilentlyContinue
|
40
|
+
}
|
41
|
+
RESET_COMMAND
|
42
|
+
execute_reset_command(reset_command)
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'applies idempotently' do
|
46
|
+
content = <<~MANIFEST.strip
|
47
|
+
$destination_path = '#{fixtures_path}/website'
|
48
|
+
$website_name = 'Puppet DSC Site'
|
49
|
+
$site_id = 7
|
50
|
+
$index_html = @(INDEXHTML)
|
51
|
+
<!doctype html>
|
52
|
+
<html lang=en>
|
53
|
+
|
54
|
+
<head>
|
55
|
+
<meta charset=utf-8>
|
56
|
+
<title>blah</title>
|
57
|
+
</head>
|
58
|
+
|
59
|
+
<body>
|
60
|
+
<p>I'm the content</p>
|
61
|
+
</body>
|
62
|
+
|
63
|
+
</html>
|
64
|
+
| INDEXHTML
|
65
|
+
# Install the IIS role
|
66
|
+
dsc_xwindowsfeature { 'IIS':
|
67
|
+
dsc_ensure => 'Present',
|
68
|
+
dsc_name => 'Web-Server',
|
69
|
+
}
|
70
|
+
|
71
|
+
# Stop the default website
|
72
|
+
dsc_xwebsite { 'DefaultSite':
|
73
|
+
dsc_ensure => 'Present',
|
74
|
+
dsc_name => 'Default Web Site',
|
75
|
+
dsc_state => 'Stopped',
|
76
|
+
dsc_serverautostart => false,
|
77
|
+
dsc_physicalpath => 'C:\inetpub\wwwroot',
|
78
|
+
require => Dsc_xwindowsfeature['IIS'],
|
79
|
+
}
|
80
|
+
|
81
|
+
# Install the ASP .NET 4.5 role
|
82
|
+
dsc_xwindowsfeature { 'AspNet45':
|
83
|
+
dsc_ensure => 'Present',
|
84
|
+
dsc_name => 'Web-Asp-Net45',
|
85
|
+
}
|
86
|
+
|
87
|
+
file { 'WebContentFolder':
|
88
|
+
ensure => directory,
|
89
|
+
path => $destination_path,
|
90
|
+
require => Dsc_xwindowsfeature['AspNet45'],
|
91
|
+
}
|
92
|
+
|
93
|
+
# Copy the website content
|
94
|
+
file { 'WebContentIndex':
|
95
|
+
path => "${destination_path}/index.html",
|
96
|
+
content => $index_html,
|
97
|
+
require => File['WebContentFolder'],
|
98
|
+
}
|
99
|
+
|
100
|
+
# Create the new Website
|
101
|
+
dsc_xwebsite { 'NewWebsite':
|
102
|
+
dsc_ensure => 'Present',
|
103
|
+
dsc_name => $website_name,
|
104
|
+
dsc_siteid => $site_id,
|
105
|
+
dsc_state => 'Started',
|
106
|
+
dsc_serverautostart => true,
|
107
|
+
dsc_physicalpath => $destination_path,
|
108
|
+
require => File['WebContentIndex'],
|
109
|
+
}
|
110
|
+
MANIFEST
|
111
|
+
File.write(test_manifest, content)
|
112
|
+
# Puppet apply the test manifest
|
113
|
+
first_run_result = powershell.execute(puppet_apply)
|
114
|
+
expect(first_run_result[:exitcode]).to be(2)
|
115
|
+
# The Default Site is stopped
|
116
|
+
expect(first_run_result[:native_stdout]).to match(%r{Dsc_xwebsite\[DefaultSite\]/dsc_state: dsc_state changed 'Started' to 'Stopped'})
|
117
|
+
expect(first_run_result[:native_stdout]).to match(/dsc_xwebsite\[{:name=>"DefaultSite", :dsc_name=>"Default Web Site"}\]: Updating: Finished/)
|
118
|
+
# AspNet45 is installed
|
119
|
+
expect(first_run_result[:native_stdout]).to match(%r{Dsc_xwindowsfeature\[AspNet45\]/dsc_ensure: dsc_ensure changed 'Absent' to 'Present'})
|
120
|
+
expect(first_run_result[:native_stdout]).to match(/dsc_xwindowsfeature\[{:name=>"AspNet45", :dsc_name=>"Web-Asp-Net45"}\]: Creating: Finished/)
|
121
|
+
# Web content folder created
|
122
|
+
expect(first_run_result[:native_stdout]).to match(%r{File\[WebContentFolder\]/ensure: created})
|
123
|
+
# Web content index created
|
124
|
+
expect(first_run_result[:native_stdout]).to match(%r{File\[WebContentIndex\]/ensure: defined content as '.+'})
|
125
|
+
# Web site created
|
126
|
+
expect(first_run_result[:native_stdout]).to match(%r{Dsc_xwebsite\[NewWebsite\]/dsc_siteid: dsc_siteid changed to 7})
|
127
|
+
expect(first_run_result[:native_stdout]).to match(%r{Dsc_xwebsite\[NewWebsite\]/dsc_ensure: dsc_ensure changed 'Absent' to 'Present'})
|
128
|
+
expect(first_run_result[:native_stdout]).to match(%r{Dsc_xwebsite\[NewWebsite\]/dsc_physicalpath: dsc_physicalpath changed to '.+fixtures/website'})
|
129
|
+
expect(first_run_result[:native_stdout]).to match(%r{Dsc_xwebsite\[NewWebsite\]/dsc_state: dsc_state changed to 'Started'})
|
130
|
+
expect(first_run_result[:native_stdout]).to match(%r{Dsc_xwebsite\[NewWebsite\]/dsc_serverautostart: dsc_serverautostart changed to 'true'})
|
131
|
+
expect(first_run_result[:native_stdout]).to match(/dsc_xwebsite\[{:name=>"NewWebsite", :dsc_name=>"Puppet DSC Site"}\]: Creating: Finished/)
|
132
|
+
# Run finished
|
133
|
+
expect(first_run_result[:native_stdout]).to match(/Applied catalog/)
|
134
|
+
# Second run is idempotent
|
135
|
+
second_run_result = powershell.execute(puppet_apply)
|
136
|
+
expect(second_run_result[:exitcode]).to be(0)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
@@ -0,0 +1,6 @@
|
|
1
|
+
Get-ChildItem WSMan:\localhost\Listener\ -OutVariable Listeners | Format-List * -Force
|
2
|
+
$HTTPListener = $Listeners | Where-Object -FilterScript { $_.Keys.Contains('Transport=HTTP') }
|
3
|
+
If ($HTTPListener.Count -eq 0) {
|
4
|
+
winrm create winrm/config/Listener?Address=*+Transport=HTTP
|
5
|
+
winrm e winrm/config/listener
|
6
|
+
}
|
data/spec/exit-27.ps1
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
exit 27
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'ruby-pwsh'
|
5
|
+
|
6
|
+
RSpec.configure do |config|
|
7
|
+
# Enable flags like --only-failures and --next-failure
|
8
|
+
config.example_status_persistence_file_path = '.rspec_status'
|
9
|
+
|
10
|
+
# Disable RSpec exposing methods globally on `Module` and `main`
|
11
|
+
config.disable_monkey_patching!
|
12
|
+
|
13
|
+
config.expect_with :rspec do |c|
|
14
|
+
c.syntax = :expect
|
15
|
+
end
|
16
|
+
end
|