ruby-pwsh 0.10.2 → 0.10.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c0192c9fd1e6f719c01cfbc185c847943ec0878c1fbfda56749a52dfd5d7fca7
4
- data.tar.gz: 5d40c16d7f2efe2585a0c65d0e6050aaa72e4083510cbac376038a85ec725264
3
+ metadata.gz: adc4879de83f4b7756c10664ab59c44ab3b34e93e848ebedcefb049c969137f8
4
+ data.tar.gz: 7ffc7b8c36775a252075fe3c0bffbb60c8b2e287bcbec65d61c1594f6ff4522a
5
5
  SHA512:
6
- metadata.gz: 1e1356eb57e96fa78906861bdd6bf887c5f9eb14175f5408e4db779a3b66ac1bc9bf92d1436f36d019da8b3e18f0bfbc175170d8e3058eff6c3e51235b433ac0
7
- data.tar.gz: de4bdce317e05453eb9179df50f065c09c16da4529b304ed0adb7e4becc3c8378ea0f67d69444209ac72d6a5f19881b594f624c311412e8fa32fdcb4bdb9b34d
6
+ metadata.gz: 638fb1d09d7c0ec4fe29ee0473dba74cdbf2aa142a730eed6c43b0e472ec2f3a7452a87e18ff8c36a4dd95f0897ec8388d30357406481aea8772d409087183b7
7
+ data.tar.gz: d5de2cb597e68c7c911b36c1cd92cbfc50a13916570f744bc8674b138ed62c30cc728ac2dba8f50a2856e6915ceb48ceede851c6f11a91cf8cca1af2d6cdfe9c
data/README.md CHANGED
@@ -74,47 +74,51 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
74
74
 
75
75
  Steps to release an update to the gem and module include:
76
76
 
77
- 1. Ensure that the release branch is up to date with the main:
77
+ 1. From main, checkout a new working branch for the release prep (where xyz is the appropriate version, sans periods):
78
78
  ```bash
79
- git push upstream upstream/main:release --force
79
+ git checkout -b maint-release_prep_xyz
80
80
  ```
81
- 1. Checkout a new working branch for the release prep (where xyz is the appropriate version, sans periods):
82
- ```bash
83
- git checkout -b maint/release/prep-xyz upstream/release
84
- ```
85
- 1. Update the version in `lib/pwsh/version.rb` and `metadata.json` to the appropriate version for the new release.
86
- 1. Run the changelog update task (make sure to verify the changelog, correctly tagging PRs as needed):
81
+
82
+ 2. Update the version in `lib/pwsh/version.rb` and `metadata.json` to the appropriate version for the new release.
83
+
84
+ 3. Run the changelog update task (make sure to verify the changelog, correctly tagging PRs as needed):
87
85
  ```bash
88
86
  bundle exec rake changelog
89
87
  ```
90
- 1. Commit your changes with a short, sensible commit message, like:
91
- ```text
88
+
89
+ 4. Commit your changes with a short, sensible commit message, like:
90
+ ```bash
92
91
  git add lib/pwsh/version.rb
93
92
  git add metadata.json
94
93
  git add CHANGELOG.md
95
94
  git commit -m '(MAINT) Prep for x.y.z release'
96
95
  ```
97
- 1. Push your changes and submit a pull request for review _against the **release** branch_:
98
- ```bash
99
- git push -u origin maint/release/prep-xyz
100
- ```
101
- 1. Ensure tests pass and the code is merged to `release`.
102
- 1. Grab the commit hash from the merge commit on release, use that as the tag for the version (replacing `x.y.z` with the appropriate version and `commithash` with the relevant one), then push the tags to upstream:
96
+
97
+ 5. Push your changes and submit a pull request for review _against main:
103
98
  ```bash
104
- bundle exec rake tag['x.y.z', 'commithash']
99
+ git push -u origin maint_release_prep_xyz
105
100
  ```
106
- 1. Build the Ruby gem and publish:
101
+
102
+ 6. Ensure tests pass and the code is merged to `main`.
103
+
104
+ 7. Once the release_prep PR has been merged, checkout main and pull down the latests changes.
107
105
  ```bash
108
- bundle exec rake build
109
- bundle exec rake push['ruby-pwsh-x.y.z.gem']
106
+ git checkout main
107
+ git pull
110
108
  ```
111
- 1. Verify that the correct version now exists on [RubyGems](https://rubygems.org/search?query=ruby-pwsh)
112
- 1. Build the Puppet module:
109
+
110
+ 8. Assuming that the release_prep merge commit is at the HEAD of main we can simply create and push a tag as follows (replacing xyz with the appropriate version).
113
111
  ```bash
114
- bundle exec rake build_module
112
+ git tag -a xyx -m "Release xyz"
113
+ git push --follow-tags
115
114
  ```
116
- 1. Publish the updated module version (found in the `pkg` folder) to [the Forge](https://forge.puppet.com/puppetlabs/pwshlib).
117
- 1. Submit the [mergeback PR from the release branch to main](https://github.com/puppetlabs/ruby-pwsh/compare/main...release).
115
+
116
+ 9. Execute the publish workflow. This will:
117
+ - Create a GitHub release
118
+ - Build and publish the Gem
119
+ - Build and publish the Puppet module
120
+
121
+ 10. Finally check that the expected versions are present on rubygems.org and the Forge.
118
122
 
119
123
  ## Known Issues
120
124
 
@@ -79,6 +79,9 @@ class Puppet::Provider::DscBaseProvider
79
79
  end
80
80
  downcased_result = recursively_downcase(canonicalized)
81
81
  downcased_resource = recursively_downcase(r)
82
+ # Ensure that metaparameters are preserved when we canonicalize the resource.
83
+ metaparams = downcased_resource.select { |key, _value| Puppet::Type.metaparam?(key) }
84
+ canonicalized.merge!(metaparams) unless metaparams.nil?
82
85
  downcased_result.each do |key, value|
83
86
  # Canonicalize to the manifest value unless the downcased strings match and the attribute is not an enum:
84
87
  # - When the values don't match at all, the manifest value is desired;
data/lib/pwsh/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Pwsh
4
4
  # The version of the ruby-pwsh gem
5
- VERSION = '0.10.2'
5
+ VERSION = '0.10.3'
6
6
  end
@@ -0,0 +1,205 @@
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
+ context 'Creating' do
68
+ let(:manifest) do
69
+ [
70
+ "dsc_psmodule { 'Install BurntToast':",
71
+ "dsc_name => 'BurntToast',",
72
+ "dsc_ensure => 'Present',",
73
+ '}'
74
+ ].join(' ')
75
+ end
76
+
77
+ before(:all) do
78
+ reset_command = <<~RESET_COMMAND
79
+ $ErrorActionPreference = 'Stop'
80
+ Import-Module PowerShellGet
81
+ Get-InstalledModule -Name BurntToast -ErrorAction SilentlyContinue |
82
+ Uninstall-Module -Force
83
+ RESET_COMMAND
84
+ execute_reset_command(reset_command)
85
+ end
86
+
87
+ it 'applies idempotently' do
88
+ first_run_result = powershell.execute(command)
89
+ expect(first_run_result[:exitcode]).to be(2)
90
+ expect(first_run_result[:native_stdout]).to match(/dsc_ensure changed 'Absent' to 'Present'/)
91
+ expect(first_run_result[:native_stdout]).to match(/Creating: Finished/)
92
+ expect(first_run_result[:native_stdout]).to match(/Applied catalog/)
93
+ second_run_result = powershell.execute(command)
94
+ expect(second_run_result[:exitcode]).to be(0)
95
+ end
96
+ end
97
+
98
+ context 'Deleting' do
99
+ let(:manifest) do
100
+ [
101
+ "dsc_psmodule { 'Install BurntToast':",
102
+ "dsc_name => 'BurntToast',",
103
+ "dsc_ensure => 'Absent',",
104
+ '}'
105
+ ].join(' ')
106
+ end
107
+
108
+ before(:all) do
109
+ reset_command = <<~RESET_COMMAND
110
+ $ErrorActionPreference = 'Stop'
111
+ Import-Module PowerShellGet
112
+ $Installed = Get-InstalledModule -Name BurntToast -ErrorAction SilentlyContinue
113
+ If($null -eq $Installed) {
114
+ Install-Module -Name BurntToast -Scope AllUsers -Force
115
+ }
116
+ RESET_COMMAND
117
+ execute_reset_command(reset_command)
118
+ end
119
+
120
+ it 'applies idempotently' do
121
+ first_run_result = powershell.execute(command)
122
+ expect(first_run_result[:exitcode]).to be(2)
123
+ expect(first_run_result[:native_stdout]).to match(/dsc_ensure changed 'Present' to 'Absent'/)
124
+ expect(first_run_result[:native_stdout]).to match(/Deleting: Finished/)
125
+ expect(first_run_result[:native_stdout]).to match(/Applied catalog/)
126
+ second_run_result = powershell.execute(command)
127
+ expect(second_run_result[:exitcode]).to be(0)
128
+ end
129
+ end
130
+ context 'PSDscRunAsCredential' do
131
+ before(:all) do
132
+ prep_command = <<~PREP_USER.strip
133
+ $ErrorActionPreference = 'Stop'
134
+ $User = Get-LocalUser -Name #{local_user} -ErrorAction SilentlyContinue
135
+ If ($null -eq $User) {
136
+ $Secure = ConvertTo-SecureString -String '#{local_pw}' -AsPlainText -Force
137
+ $User = New-LocalUser -Name #{local_user} -Password $Secure -Verbose
138
+ }
139
+ If ($User.Name -notin (Get-LocalGroupMember -Group Administrators).Name) {
140
+ Add-LocalGroupMember -Group Administrators -Member $User -Verbose
141
+ }
142
+ Get-LocalGroupMember -Group Administrators |
143
+ Where-Object Name -match '#{local_user}'
144
+ PREP_USER
145
+ execute_reset_command(prep_command)
146
+ end
147
+ after(:all) do
148
+ cleanup_command = <<~CLEANUP_USER.strip
149
+ Remove-LocalUser -Name #{local_user} -ErrorAction Stop
150
+ CLEANUP_USER
151
+ execute_reset_command(cleanup_command)
152
+ end
153
+
154
+ context 'with a valid credential' do
155
+ let(:manifest) do
156
+ [
157
+ "dsc_psrepository { 'Trust PSGallery':",
158
+ "dsc_name => 'PSGallery',",
159
+ "dsc_ensure => 'Present',",
160
+ "dsc_installationpolicy => 'Trusted',",
161
+ 'dsc_psdscrunascredential => {',
162
+ "'user' => '#{local_user}',",
163
+ "'password' => Sensitive('#{local_pw}')",
164
+ '}',
165
+ '}'
166
+ ].join(' ')
167
+ end
168
+
169
+ it 'applies idempotently without leaking secrets' do
170
+ first_run_result = powershell.execute(command)
171
+ expect(first_run_result[:exitcode]).to be(2)
172
+ expect(first_run_result[:native_stdout]).to match(/dsc_installationpolicy changed 'Untrusted' to 'Trusted'/)
173
+ expect(first_run_result[:native_stdout]).to match(/Updating: Finished/)
174
+ expect(first_run_result[:native_stdout]).to match(/Applied catalog/)
175
+ expect(first_run_result[:native_stdout]).to match(/'#<Sensitive \[value redacted\]>'/)
176
+ expect(first_run_result[:native_stdout]).not_to match(local_pw)
177
+ second_run_result = powershell.execute(command)
178
+ expect(second_run_result[:exitcode]).to be(0)
179
+ end
180
+ end
181
+ context 'with an invalid credential' do
182
+ let(:manifest) do
183
+ [
184
+ "dsc_psrepository { 'Trust PSGallery':",
185
+ "dsc_name => 'PSGallery',",
186
+ "dsc_ensure => 'Present',",
187
+ "dsc_installationpolicy => 'Trusted',",
188
+ 'dsc_psdscrunascredential => {',
189
+ "'user' => 'definitely_do_not_exist_here',",
190
+ "'password' => Sensitive('#{local_pw}')",
191
+ '}',
192
+ '}'
193
+ ].join(' ')
194
+ end
195
+
196
+ it 'errors loudly without leaking secrets' do
197
+ first_run_result = powershell.execute(command)
198
+ expect(first_run_result[:exitcode]).to be(4)
199
+ expect(first_run_result[:stderr].first).to match(/dsc_psrepository: The user name or password is incorrect/)
200
+ expect(first_run_result[:native_stdout]).to match(/'#<Sensitive \[value redacted\]>'/)
201
+ expect(first_run_result[:native_stdout]).not_to match(local_pw)
202
+ end
203
+ end
204
+ end
205
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+ # TODO: Test against mcollera/AccessControlDsc for CIM instance behavior
3
+ # 1. Make sure valid nested CIM instances can be passed to Invoke-DscResource
4
+ # 2. Make sure nested CIM instances can be read back from Invoke-DscResource
5
+
6
+ # frozen_string_literal: true
7
+
8
+ require 'spec_helper'
9
+ require 'ruby-pwsh'
10
+
11
+ # Needs to be declared here so it is usable in before and it blocks alike
12
+ test_manifest = File.expand_path('../../fixtures/test.pp', File.dirname(__FILE__))
13
+ fixtures_path = File.expand_path('../../fixtures', File.dirname(__FILE__))
14
+
15
+ def execute_reset_command(reset_command)
16
+ manager = Pwsh::Manager.instance(Pwsh::Manager.powershell_path, Pwsh::Manager.powershell_args)
17
+ result = manager.execute(reset_command)
18
+ raise result[:errormessage] unless result[:errormessage].nil?
19
+ end
20
+
21
+ RSpec.describe 'DSC Acceptance: Complex' do
22
+ let(:powershell) { Pwsh::Manager.instance(Pwsh::Manager.powershell_path, Pwsh::Manager.powershell_args) }
23
+ let(:module_path) { File.expand_path('../../fixtures/modules', File.dirname(__FILE__)) }
24
+ let(:puppet_apply) do
25
+ "bundle exec puppet apply #{test_manifest} --modulepath #{module_path} --detailed-exitcodes --trace"
26
+ end
27
+
28
+ context 'Managing the access control list of a folder' do
29
+ before(:each) do
30
+ reset_command = <<~RESET_COMMAND
31
+ $TestFolderPath = Join-Path -Path "#{fixtures_path}" -Childpath access_control
32
+ # Delete the test folder if it exists (to clear access control modifications)
33
+ If (Test-Path -Path $TestFolderPath -PathType Container) {
34
+ Remove-Item $TestFolderPath -Recurse -Force
35
+ }
36
+ # Create the test folder
37
+ New-Item $TestFolderPath -ItemType Directory
38
+ RESET_COMMAND
39
+ execute_reset_command(reset_command)
40
+ end
41
+
42
+ it 'applies idempotently' do
43
+ content = <<~MANIFEST.strip
44
+ $test_folder_path = "#{fixtures_path}/access_control"
45
+ # Configure access to the test folder
46
+ dsc_ntfsaccessentry {'Test':
47
+ dsc_path => $test_folder_path,
48
+ dsc_accesscontrollist => [
49
+ {
50
+ principal => 'Everyone',
51
+ forceprincipal => true,
52
+ accesscontrolentry => [
53
+ {
54
+ accesscontroltype => 'Allow',
55
+ filesystemrights => ['FullControl'],
56
+ inheritance => 'This folder and files',
57
+ ensure => 'Present',
58
+ cim_instance_type => 'NTFSAccessControlEntry',
59
+ }
60
+ ]
61
+ }
62
+ ]
63
+ }
64
+ MANIFEST
65
+ File.open(test_manifest, 'w') { |file| file.write(content) }
66
+ # Apply the test manifest
67
+ first_run_result = powershell.execute(puppet_apply)
68
+ expect(first_run_result[:exitcode]).to be(2)
69
+ # Access Control Set
70
+ expect(first_run_result[:native_stdout]).to match(/dsc_accesscontrollist: dsc_accesscontrollist changed/)
71
+ expect(first_run_result[:native_stdout]).to match(%r{dsc_ntfsaccessentry\[{:name=>"Test", :dsc_path=>".+/spec/fixtures/access_control"}\]: Updating: Finished})
72
+ expect(first_run_result[:stderr]).not_to match(/Error/)
73
+ expect(first_run_result[:stderr]).not_to match(/Warning: Provider returned data that does not match the Type Schema/)
74
+ expect(first_run_result[:stderr]).not_to match(/Value type mismatch/)
75
+ # Run finished
76
+ expect(first_run_result[:native_stdout]).to match(/Applied catalog/)
77
+ # Second run is idempotent
78
+ second_run_result = powershell.execute(puppet_apply)
79
+ expect(second_run_result[:exitcode]).to be(0)
80
+ end
81
+ end
82
+ 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(:each) 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(:each) 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(:each) 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(:each) 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.open(test_manifest, 'w') { |file| file.write(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
data/spec/exit-27.ps1 ADDED
@@ -0,0 +1 @@
1
+ exit 27