ruby-pwsh 0.10.2 → 0.11.0.rc.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|