rundeck 0.0.3.pre → 0.0.3

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.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +10 -0
  3. data/.rubocop_todo.yml +0 -10
  4. data/.travis.yml +1 -1
  5. data/lib/rundeck/client.rb +61 -3
  6. data/lib/rundeck/client/execution.rb +194 -0
  7. data/lib/rundeck/client/{jobs.rb → job.rb} +19 -36
  8. data/lib/rundeck/client/{keys.rb → key.rb} +42 -20
  9. data/lib/rundeck/configuration.rb +5 -1
  10. data/lib/rundeck/objectified_hash.rb +13 -3
  11. data/lib/rundeck/request.rb +7 -1
  12. data/lib/rundeck/version.rb +1 -1
  13. data/rundeck.gemspec +2 -1
  14. data/spec/cassettes/12/abort_execution_not_running.yml +41 -0
  15. data/spec/cassettes/12/abort_execution_valid.yml +41 -0
  16. data/spec/cassettes/12/abort_executions_invalid.yml +41 -0
  17. data/spec/cassettes/12/bulk_delete_executions_invalid.yml +46 -0
  18. data/spec/cassettes/12/bulk_delete_executions_valid.yml +41 -0
  19. data/spec/cassettes/12/create_private_key.yml +64 -0
  20. data/spec/cassettes/12/create_public_key.yml +38 -0
  21. data/spec/cassettes/12/delete_execution_invalid.yml +41 -0
  22. data/spec/cassettes/12/delete_execution_valid.yml +32 -0
  23. data/spec/cassettes/12/delete_job_executions.yml +41 -0
  24. data/spec/cassettes/12/delete_job_executions_invalid.yml +41 -0
  25. data/spec/cassettes/12/delete_job_invalid.yml +41 -0
  26. data/spec/cassettes/12/delete_job_valid.yml +32 -0
  27. data/spec/cassettes/12/delete_key_invalid.yml +41 -0
  28. data/spec/cassettes/12/delete_key_private.yml +32 -0
  29. data/spec/cassettes/12/delete_key_public.yml +32 -0
  30. data/spec/cassettes/12/execution_invalid.yml +41 -0
  31. data/spec/cassettes/12/execution_query_invalid.yml +39 -0
  32. data/spec/cassettes/12/execution_query_no_params_valid.yml +124 -0
  33. data/spec/cassettes/12/execution_state_invalid.yml +41 -0
  34. data/spec/cassettes/12/execution_state_valid.yml +39 -0
  35. data/spec/cassettes/12/execution_valid.yml +60 -0
  36. data/spec/cassettes/12/export_job_xml.yml +234 -0
  37. data/spec/cassettes/12/export_job_yaml.yml +228 -0
  38. data/spec/cassettes/12/import_job_xml.yml +69 -0
  39. data/spec/cassettes/12/import_job_yaml.yml +63 -0
  40. data/spec/cassettes/12/job.yml +84 -0
  41. data/spec/cassettes/12/job_executions.yml +96 -0
  42. data/spec/cassettes/12/jobs.yml +76 -0
  43. data/spec/cassettes/12/key_contents_direct.yml +67 -0
  44. data/spec/cassettes/12/key_contents_multiple.yml +38 -0
  45. data/spec/cassettes/12/key_contents_private.yml +35 -0
  46. data/spec/cassettes/12/key_metadata_direct.yml +35 -0
  47. data/spec/cassettes/12/key_metadata_multiple.yml +38 -0
  48. data/spec/cassettes/12/keys_direct.yml +35 -0
  49. data/spec/cassettes/12/keys_multiple.yml +38 -0
  50. data/spec/cassettes/12/keys_none.yml +34 -0
  51. data/spec/cassettes/12/run_job_invalid.yml +44 -0
  52. data/spec/cassettes/12/run_job_valid.yml +77 -0
  53. data/spec/cassettes/12/running_jobs_multiple.yml +70 -0
  54. data/spec/cassettes/12/running_jobs_none.yml +39 -0
  55. data/spec/cassettes/12/running_jobs_single.yml +55 -0
  56. data/spec/cassettes/12/update_private_key.yml +96 -0
  57. data/spec/cassettes/12/update_public_key.yml +70 -0
  58. data/spec/rundeck/client/execution_spec.rb +346 -0
  59. data/spec/rundeck/client/job_spec.rb +175 -0
  60. data/spec/rundeck/client/key_spec.rb +223 -0
  61. data/spec/spec_helper.rb +15 -60
  62. data/spec/support/helpers.rb +136 -0
  63. metadata +118 -35
  64. data/spec/fixtures/empty.xml +0 -0
  65. data/spec/fixtures/job.xml +0 -17
  66. data/spec/fixtures/job_executions.xml +0 -31
  67. data/spec/fixtures/job_run.xml +0 -16
  68. data/spec/fixtures/jobs_import.xml +0 -23
  69. data/spec/fixtures/jobs_my_project.xml +0 -14
  70. data/spec/fixtures/jobs_xml.xml +0 -32
  71. data/spec/fixtures/jobs_yaml.xml +0 -22
  72. data/spec/fixtures/key_contents_public.xml +0 -1
  73. data/spec/fixtures/key_private.xml +0 -8
  74. data/spec/fixtures/key_public.xml +0 -7
  75. data/spec/fixtures/keys.xml +0 -27
  76. data/spec/rundeck/client/jobs_spec.rb +0 -181
  77. data/spec/rundeck/client/keys_spec.rb +0 -216
@@ -0,0 +1,175 @@
1
+ require 'spec_helper'
2
+
3
+ describe Rundeck::Client do
4
+ describe '.jobs', vcr: { cassette_name: 'jobs' } do
5
+ before do
6
+ @jobs = Rundeck.jobs('anvils')
7
+ end
8
+ subject { @jobs }
9
+
10
+ it { is_expected.to be_an Array }
11
+ its(:length) { is_expected.to eq(6) }
12
+
13
+ # Only tests the first element, but it's just a sanity check
14
+ its('first') { is_expected.to respond_to(:name) }
15
+ its('first') { is_expected.to respond_to(:group) }
16
+ its('first') { is_expected.to respond_to(:project) }
17
+ its('first') { is_expected.to respond_to(:description) }
18
+
19
+ it 'expects a get to have been made' do
20
+ expect(a_get('/project/anvils/jobs')).to have_been_made
21
+ end
22
+ end
23
+
24
+ describe '.job', vcr: { cassette_name: 'job' } do
25
+ before do
26
+ @job = Rundeck.job('2')
27
+ end
28
+ subject { @job }
29
+
30
+ it { is_expected.to be_a Rundeck::ObjectifiedHash }
31
+ it { is_expected.to respond_to(:id) }
32
+ it { is_expected.to respond_to(:name) }
33
+ it { is_expected.to respond_to(:description) }
34
+ it { is_expected.to respond_to(:loglevel) }
35
+ it { is_expected.to respond_to(:sequence) }
36
+ its(:sequence) { is_expected.to respond_to(:command) }
37
+
38
+ it 'expects a get to have been made' do
39
+ expect(a_get('/job/2')).to have_been_made
40
+ end
41
+ end
42
+
43
+ describe '.delete_job' do
44
+ context 'when a job exists',
45
+ vcr: { cassette_name: 'delete_job_valid' } do
46
+ before do
47
+ @job = Rundeck.delete_job(job_id)
48
+ end
49
+ let(:job_id) { '3' }
50
+ subject { @job }
51
+
52
+ it { is_expected.to be_nil }
53
+
54
+ it 'expects a delete to have been made' do
55
+ expect(a_delete('/job/3')).to have_been_made
56
+ end
57
+ end
58
+
59
+ context 'when a job does not exist',
60
+ vcr: { cassette_name: 'delete_job_invalid' } do
61
+ specify do
62
+ expect do
63
+ Rundeck.delete_job('123456')
64
+ end.to raise_error(Rundeck::Error::NotFound,
65
+ /Job ID does not exist: 123456/)
66
+ end
67
+ end
68
+ end
69
+
70
+ describe '.import_job' do
71
+ context 'with valid format' do
72
+ before do
73
+ @import = Rundeck.import_jobs(content, format)
74
+ end
75
+ subject { @import }
76
+
77
+ context 'yaml', vcr: { cassette_name: 'import_job_yaml' } do
78
+ let(:format) { 'yaml' }
79
+ let(:content) { job_yaml }
80
+
81
+ it { is_expected.to be_a Rundeck::ObjectifiedHash }
82
+ its('succeeded.count') { is_expected.to eq('1') }
83
+ its('failed.count') { is_expected.to eq('0') }
84
+
85
+ its('succeeded') { is_expected.to respond_to(:job) }
86
+ its('succeeded.job') { is_expected.to respond_to(:id) }
87
+ its('succeeded.job') { is_expected.to respond_to(:name) }
88
+ its('succeeded.job') { is_expected.to respond_to(:group) }
89
+ its('succeeded.job') { is_expected.to respond_to(:project) }
90
+ its('succeeded.job') { is_expected.to respond_to(:url) }
91
+
92
+ it 'expects a post to have been made' do
93
+ expect(a_post('/jobs/import?format=yaml')).to have_been_made
94
+ end
95
+ end
96
+
97
+ context 'xml', vcr: { cassette_name: 'import_job_xml' } do
98
+ let(:format) { 'xml' }
99
+ let(:content) { job_xml }
100
+
101
+ it { is_expected.to be_a Rundeck::ObjectifiedHash }
102
+ its('succeeded.count') { is_expected.to eq('1') }
103
+ its('failed.count') { is_expected.to eq('0') }
104
+
105
+ its('succeeded') { is_expected.to respond_to(:job) }
106
+ its('succeeded.job') { is_expected.to respond_to(:id) }
107
+ its('succeeded.job') { is_expected.to respond_to(:name) }
108
+ its('succeeded.job') { is_expected.to respond_to(:group) }
109
+ its('succeeded.job') { is_expected.to respond_to(:project) }
110
+ its('succeeded.job') { is_expected.to respond_to(:url) }
111
+
112
+ it 'expects a post to have been made' do
113
+ expect(a_post('/jobs/import?format=xml')).to have_been_made
114
+ end
115
+ end
116
+ end
117
+
118
+ context 'with invalid format', vcr: { cassette_name: 'import_job_invalid' } do
119
+ specify do
120
+ expect do
121
+ Rundeck.import_jobs('content', 'invalid_format')
122
+ end.to raise_error(Rundeck::Error::InvalidAttributes,
123
+ 'format must be yaml or xml')
124
+ end
125
+ end
126
+ end
127
+
128
+ describe '.export_job' do
129
+ context 'with valid format' do
130
+ before do
131
+ @jobs = Rundeck.export_jobs('anvils', format)
132
+ end
133
+ subject { @jobs }
134
+
135
+ context 'yaml', vcr: { cassette_name: 'export_job_yaml' } do
136
+ let(:format) { 'yaml' }
137
+
138
+ it { is_expected.to be_a String }
139
+ it { is_expected.to include 'project: anvils' }
140
+ it { is_expected.to include 'loglevel: INFO' }
141
+ it { is_expected.to include 'sequence:' }
142
+
143
+ it 'expects a get to have been made' do
144
+ expect(
145
+ a_get('/jobs/export?project=anvils&format=yaml')
146
+ ).to have_been_made
147
+ end
148
+ end
149
+
150
+ context 'xml', vcr: { cassette_name: 'export_job_xml' } do
151
+ let(:format) { 'xml' }
152
+
153
+ it { is_expected.to be_a String }
154
+ it { is_expected.to include '<project>anvils</project>' }
155
+ it { is_expected.to include '<loglevel>INFO</loglevel>' }
156
+ it { is_expected.to include '<sequence' }
157
+
158
+ it 'expects a get to have been made' do
159
+ expect(
160
+ a_get('/jobs/export?project=anvils&format=xml')
161
+ ).to have_been_made
162
+ end
163
+ end
164
+ end
165
+
166
+ context 'with invalid format', vcr: { cassette_name: 'export_job_invalid' } do
167
+ specify do
168
+ expect do
169
+ Rundeck.export_jobs('anvils', 'invalid_format')
170
+ end.to raise_error(Rundeck::Error::InvalidAttributes,
171
+ 'format must be yaml or xml')
172
+ end
173
+ end
174
+ end
175
+ end
@@ -0,0 +1,223 @@
1
+ require 'spec_helper'
2
+
3
+ describe Rundeck::Client do
4
+ describe '.keys' do
5
+ # The anvils demo doesn't have any keys by default.
6
+ # Create a public key at path /path/to/key and a private key
7
+ # at /path/to/private_key using the data from the helper methods.
8
+ # Create the public key first.
9
+ context 'with a path containing multiple keys',
10
+ vcr: { cassette_name: 'keys_multiple' } do
11
+ before do
12
+ @keys = Rundeck.keys('path/to')
13
+ end
14
+ subject { @keys }
15
+
16
+ it { is_expected.to be_an Array }
17
+ its('first.name') { is_expected.to eq('key') }
18
+ its('first.resource_meta.rundeck_content_type') { is_expected.to eq('application/pgp-key') }
19
+
20
+ it 'expects a get to have been made' do
21
+ expect(a_get('/storage/keys/path/to')).to have_been_made
22
+ end
23
+ end
24
+
25
+ context 'with a path containing no keys',
26
+ vcr: { cassette_name: 'keys_none' } do
27
+ specify do
28
+ expect do
29
+ Rundeck.keys('path/to/nowhere')
30
+ end.to raise_error Rundeck::Error::NotFound
31
+ end
32
+ end
33
+
34
+ context 'with a direct path to a key',
35
+ vcr: { cassette_name: 'keys_direct' } do
36
+ specify do
37
+ expect do
38
+ Rundeck.keys('path/to/key')
39
+ end.to raise_error(Rundeck::Error::InvalidAttributes,
40
+ 'Please provide a key storage path that ' \
41
+ 'isn\'t a direct path to a key')
42
+ end
43
+ end
44
+ end
45
+
46
+ describe '.key_metadata' do
47
+ context 'with a direct path to a key',
48
+ vcr: { cassette_name: 'key_metadata_direct' } do
49
+ before do
50
+ @key = Rundeck.key_metadata('path/to/key')
51
+ end
52
+ subject { @key }
53
+
54
+ it { is_expected.to be_a Rundeck::ObjectifiedHash }
55
+ its(:rundeck_content_type) { is_expected.to eq('application/pgp-key') }
56
+
57
+ it 'expects a get to have been made' do
58
+ expect(a_get('/storage/keys/path/to/key')).to have_been_made
59
+ end
60
+ end
61
+
62
+ context 'with a path containing multiple keys',
63
+ vcr: { cassette_name: 'key_metadata_multiple' } do
64
+ specify do
65
+ expect do
66
+ Rundeck.key_metadata('path/to')
67
+ end.to raise_error(Rundeck::Error::InvalidAttributes,
68
+ 'Please provide a key storage path that ' \
69
+ 'is a direct path to a key')
70
+ end
71
+ end
72
+ end
73
+
74
+ describe '.key_contents' do
75
+ context 'with a direct path to a key',
76
+ vcr: { cassette_name: 'key_contents_direct' } do
77
+ before do
78
+ @key = Rundeck.key_contents('path/to/key')
79
+ end
80
+ subject { @key }
81
+
82
+ it { is_expected.to be_a Rundeck::ObjectifiedHash }
83
+ its(:public_key) { is_expected.to include('ssh-rsa') }
84
+
85
+ it 'expects a get to have been made' do
86
+ expect(a_get('/storage/keys/path/to/key')).to have_been_made
87
+ end
88
+ it 'expects a get to have been made' do
89
+ expect(a_get('/storage/keys/path/to/key', 'pgp-keys')).to have_been_made
90
+ end
91
+ end
92
+
93
+ context 'with a path containing multiple keys',
94
+ vcr: { cassette_name: 'key_contents_multiple' } do
95
+ specify do
96
+ expect do
97
+ Rundeck.key_contents('path/to')
98
+ end.to raise_error(Rundeck::Error::InvalidAttributes,
99
+ 'Please provide a key storage path that ' \
100
+ 'is a direct path to a key')
101
+ end
102
+ end
103
+
104
+ context 'with a path to a private key',
105
+ vcr: { cassette_name: 'key_contents_private' } do
106
+ specify do
107
+ expect do
108
+ Rundeck.key_contents('path/to/private_key')
109
+ end.to raise_error(Rundeck::Error::Unauthorized,
110
+ 'You are not allowed to retrieve the contents ' \
111
+ 'of a private key')
112
+ end
113
+ end
114
+ end
115
+
116
+ describe '.create_private_key',
117
+ vcr: { cassette_name: 'create_private_key' } do
118
+ before do
119
+ @key = Rundeck.create_private_key('path/to/private_key2', private_key)
120
+ end
121
+ subject { @key }
122
+
123
+ it { is_expected.to be_a Rundeck::ObjectifiedHash }
124
+ its(:rundeck_key_type) { is_expected.to eq('private') }
125
+
126
+ it 'expects a post to have been made' do
127
+ expect(
128
+ a_post('/storage/keys/path/to/private_key2')
129
+ ).to have_been_made
130
+ end
131
+ end
132
+
133
+ describe '.update_private_key',
134
+ vcr: { cassette_name: 'update_private_key' } do
135
+ before do
136
+ @key = Rundeck.update_private_key('path/to/private_key2', private_key)
137
+ end
138
+ subject { @key }
139
+
140
+ it { is_expected.to be_a Rundeck::ObjectifiedHash }
141
+ its(:rundeck_key_type) { is_expected.to eq('private') }
142
+
143
+ it 'expects a get to have been made' do
144
+ expect(a_get('/storage/keys/path/to/private_key2')).to have_been_made
145
+ end
146
+ it 'expects a put to have been made' do
147
+ expect(a_put('/storage/keys/path/to/private_key2')).to have_been_made
148
+ end
149
+ end
150
+
151
+ describe '.create_public_key',
152
+ vcr: { cassette_name: 'create_public_key' } do
153
+ before do
154
+ @key = Rundeck.create_public_key('path/to/public_key2', public_key)
155
+ end
156
+ subject { @key }
157
+
158
+ it { is_expected.to be_a Rundeck::ObjectifiedHash }
159
+ its(:rundeck_content_type) { is_expected.to eq('application/pgp-key') }
160
+
161
+ it 'expects a post to have been made' do
162
+ expect(
163
+ a_post('/storage/keys/path/to/public_key2')
164
+ ).to have_been_made
165
+ end
166
+ end
167
+
168
+ describe '.update_public_key',
169
+ vcr: { cassette_name: 'update_public_key' } do
170
+ before do
171
+ @key = Rundeck.update_public_key('path/to/public_key2', public_key)
172
+ end
173
+ subject { @key }
174
+
175
+ it { is_expected.to be_a Rundeck::ObjectifiedHash }
176
+ its(:rundeck_content_type) { is_expected.to eq('application/pgp-key') }
177
+
178
+ it 'expects a get to have been made' do
179
+ expect(a_get('/storage/keys/path/to/public_key2')).to have_been_made
180
+ end
181
+ it 'expects a put to have been made' do
182
+ expect(a_put('/storage/keys/path/to/public_key2')).to have_been_made
183
+ end
184
+ end
185
+
186
+ describe '.delete_key' do
187
+ context 'with a valid' do
188
+ before do
189
+ @key = Rundeck.delete_key(path)
190
+ end
191
+ subject { @key }
192
+
193
+ context 'public key path',
194
+ vcr: { cassette_name: 'delete_key_public' } do
195
+ let(:path) { 'path/to/public_key2' }
196
+
197
+ it { is_expected.to be_nil }
198
+ it 'expects a delete to have been made' do
199
+ expect(a_delete('/storage/keys/path/to/public_key2')).to have_been_made
200
+ end
201
+ end
202
+
203
+ context 'private key path',
204
+ vcr: { cassette_name: 'delete_key_private' } do
205
+ let(:path) { 'path/to/private_key2' }
206
+
207
+ it { is_expected.to be_nil }
208
+ it 'expects a delete to have been made' do
209
+ expect(a_delete('/storage/keys/path/to/private_key2')).to have_been_made
210
+ end
211
+ end
212
+ end
213
+
214
+ context 'with a bad path',
215
+ vcr: { cassette_name: 'delete_key_invalid' } do
216
+ specify do
217
+ expect do
218
+ Rundeck.delete_key('path/to/nowhere')
219
+ end.to raise_error Rundeck::Error::NotFound
220
+ end
221
+ end
222
+ end
223
+ end
@@ -1,77 +1,32 @@
1
1
  require 'rspec'
2
2
  require 'rspec/its'
3
3
  require 'webmock/rspec'
4
+ require 'vcr'
4
5
  require 'codeclimate-test-reporter'
5
6
 
7
+ # Must be called before code is included/required!
6
8
  CodeClimate::TestReporter.start
7
9
 
8
- WebMock.disable_net_connect!(allow: 'codeclimate.com')
10
+ require 'support/helpers'
9
11
 
10
12
  require File.expand_path('../../lib/rundeck', __FILE__)
11
13
 
12
- def load_fixture(name)
13
- File.new(File.dirname(__FILE__) + "/fixtures/#{name}.xml")
14
- end
14
+ WebMock.disable_net_connect!(allow: 'codeclimate.com')
15
15
 
16
16
  RSpec.configure do |config|
17
+ config.include Helpers
18
+
17
19
  config.before(:each) do
18
- Rundeck.endpoint = 'https://api.example.com'
19
- Rundeck.api_token = 'secret'
20
+ # Configuration for Anvils Demo Vagrant Box. API token might change.
21
+ Rundeck.endpoint = 'http://192.168.50.2:4440'
22
+ Rundeck.api_token = 'cmJQYoy9EAsSd0905yNjKDNGs0ESIwEd'
23
+ WebMock.reset!
20
24
  end
21
25
  end
22
26
 
23
- # GET
24
- def stub_get(path, fixture, accept = 'xml')
25
- stub_request(:get, "#{Rundeck.endpoint}#{path}")
26
- .with(headers: { 'Accept' => "application/#{accept}",
27
- 'X-Rundeck-Auth-Token' => Rundeck.api_token })
28
- .to_return(body: load_fixture(fixture))
29
- end
30
-
31
- def a_get(path, accept = 'xml')
32
- a_request(:get, "#{Rundeck.endpoint}#{path}")
33
- .with(headers: { 'Accept' => "application/#{accept}",
34
- 'X-Rundeck-Auth-Token' => Rundeck.api_token })
35
- end
36
-
37
- # POST
38
- def stub_post(path, fixture, status_code = 200, accept = 'xml')
39
- stub_request(:post, "#{Rundeck.endpoint}#{path}")
40
- .with(headers: { 'Accept' => "application/#{accept}",
41
- 'X-Rundeck-Auth-Token' => Rundeck.api_token })
42
- .to_return(body: load_fixture(fixture), status: status_code)
43
- end
44
-
45
- def a_post(path, accept = 'xml')
46
- a_request(:post, "#{Rundeck.endpoint}#{path}")
47
- .with(headers: { 'Accept' => "application/#{accept}",
48
- 'X-Rundeck-Auth-Token' => Rundeck.api_token })
49
- end
50
-
51
- # PUT
52
- def stub_put(path, fixture, accept = 'xml')
53
- stub_request(:put, "#{Rundeck.endpoint}#{path}")
54
- .with(headers: { 'Accept' => "application/#{accept}",
55
- 'X-Rundeck-Auth-Token' => Rundeck.api_token })
56
- .to_return(body: load_fixture(fixture))
57
- end
58
-
59
- def a_put(path, accept = 'xml')
60
- a_request(:put, "#{Rundeck.endpoint}#{path}")
61
- .with(headers: { 'Accept' => "application/#{accept}",
62
- 'X-Rundeck-Auth-Token' => Rundeck.api_token })
63
- end
64
-
65
- # DELETE
66
- def stub_delete(path, fixture, accept = 'xml')
67
- stub_request(:delete, "#{Rundeck.endpoint}#{path}")
68
- .with(headers: { 'Accept' => "application/#{accept}",
69
- 'X-Rundeck-Auth-Token' => Rundeck.api_token })
70
- .to_return(body: load_fixture(fixture))
71
- end
72
-
73
- def a_delete(path, accept = 'xml')
74
- a_request(:delete, "#{Rundeck.endpoint}#{path}")
75
- .with(headers: { 'Accept' => "application/#{accept}",
76
- 'X-Rundeck-Auth-Token' => Rundeck.api_token })
27
+ VCR.configure do |config|
28
+ config.cassette_library_dir = "spec/cassettes/#{Rundeck.api_version}"
29
+ config.hook_into :webmock
30
+ config.allow_http_connections_when_no_cassette = true
31
+ config.configure_rspec_metadata!
77
32
  end