pdk 1.16.0 → 1.17.0
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/CHANGELOG.md +24 -0
- data/lib/pdk.rb +25 -18
- data/lib/pdk/answer_file.rb +2 -93
- data/lib/pdk/cli.rb +1 -5
- data/lib/pdk/cli/config.rb +3 -1
- data/lib/pdk/cli/config/get.rb +3 -1
- data/lib/pdk/cli/convert.rb +1 -1
- data/lib/pdk/cli/exec/command.rb +13 -0
- data/lib/pdk/cli/exec_group.rb +78 -43
- data/lib/pdk/cli/get.rb +20 -0
- data/lib/pdk/cli/get/config.rb +24 -0
- data/lib/pdk/cli/util.rb +6 -3
- data/lib/pdk/cli/validate.rb +26 -44
- data/lib/pdk/config.rb +178 -4
- data/lib/pdk/config/ini_file.rb +183 -0
- data/lib/pdk/config/ini_file_setting.rb +39 -0
- data/lib/pdk/config/namespace.rb +25 -5
- data/lib/pdk/config/setting.rb +3 -2
- data/lib/pdk/context.rb +96 -0
- data/lib/pdk/context/control_repo.rb +60 -0
- data/lib/pdk/context/module.rb +28 -0
- data/lib/pdk/context/none.rb +22 -0
- data/lib/pdk/control_repo.rb +40 -0
- data/lib/pdk/generate/module.rb +8 -12
- data/lib/pdk/module/release.rb +2 -8
- data/lib/pdk/util.rb +35 -5
- data/lib/pdk/util/bundler.rb +1 -0
- data/lib/pdk/util/changelog_generator.rb +6 -1
- data/lib/pdk/util/template_uri.rb +4 -3
- data/lib/pdk/validate.rb +72 -25
- data/lib/pdk/validate/external_command_validator.rb +208 -0
- data/lib/pdk/validate/internal_ruby_validator.rb +100 -0
- data/lib/pdk/validate/invokable_validator.rb +216 -0
- data/lib/pdk/validate/metadata/metadata_json_lint_validator.rb +86 -0
- data/lib/pdk/validate/metadata/metadata_syntax_validator.rb +78 -0
- data/lib/pdk/validate/metadata/metadata_validator_group.rb +20 -0
- data/lib/pdk/validate/puppet/puppet_epp_validator.rb +133 -0
- data/lib/pdk/validate/puppet/puppet_lint_validator.rb +66 -0
- data/lib/pdk/validate/puppet/puppet_syntax_validator.rb +137 -0
- data/lib/pdk/validate/puppet/puppet_validator_group.rb +21 -0
- data/lib/pdk/validate/ruby/ruby_rubocop_validator.rb +80 -0
- data/lib/pdk/validate/ruby/ruby_validator_group.rb +19 -0
- data/lib/pdk/validate/tasks/tasks_metadata_lint_validator.rb +88 -0
- data/lib/pdk/validate/tasks/tasks_name_validator.rb +50 -0
- data/lib/pdk/validate/tasks/tasks_validator_group.rb +20 -0
- data/lib/pdk/validate/validator.rb +111 -0
- data/lib/pdk/validate/validator_group.rb +103 -0
- data/lib/pdk/validate/yaml/yaml_syntax_validator.rb +95 -0
- data/lib/pdk/validate/yaml/yaml_validator_group.rb +19 -0
- data/lib/pdk/version.rb +1 -1
- data/locales/pdk.pot +161 -125
- metadata +29 -17
- data/lib/pdk/validate/base_validator.rb +0 -215
- data/lib/pdk/validate/metadata/metadata_json_lint.rb +0 -82
- data/lib/pdk/validate/metadata/metadata_syntax.rb +0 -111
- data/lib/pdk/validate/metadata_validator.rb +0 -26
- data/lib/pdk/validate/puppet/puppet_epp.rb +0 -135
- data/lib/pdk/validate/puppet/puppet_lint.rb +0 -64
- data/lib/pdk/validate/puppet/puppet_syntax.rb +0 -135
- data/lib/pdk/validate/puppet_validator.rb +0 -26
- data/lib/pdk/validate/ruby/rubocop.rb +0 -72
- data/lib/pdk/validate/ruby_validator.rb +0 -26
- data/lib/pdk/validate/tasks/metadata_lint.rb +0 -130
- data/lib/pdk/validate/tasks/name.rb +0 -90
- data/lib/pdk/validate/tasks_validator.rb +0 -29
- data/lib/pdk/validate/yaml/syntax.rb +0 -125
- data/lib/pdk/validate/yaml_validator.rb +0 -28
@@ -1,26 +0,0 @@
|
|
1
|
-
require 'pdk'
|
2
|
-
|
3
|
-
module PDK
|
4
|
-
module Validate
|
5
|
-
class MetadataValidator < BaseValidator
|
6
|
-
def self.name
|
7
|
-
'metadata'
|
8
|
-
end
|
9
|
-
|
10
|
-
def self.metadata_validators
|
11
|
-
[MetadataSyntax, MetadataJSONLint]
|
12
|
-
end
|
13
|
-
|
14
|
-
def self.invoke(report, options = {})
|
15
|
-
exit_code = 0
|
16
|
-
|
17
|
-
metadata_validators.each do |validator|
|
18
|
-
exit_code = validator.invoke(report, options)
|
19
|
-
break if exit_code != 0
|
20
|
-
end
|
21
|
-
|
22
|
-
exit_code
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
@@ -1,135 +0,0 @@
|
|
1
|
-
require 'pdk'
|
2
|
-
|
3
|
-
module PDK
|
4
|
-
module Validate
|
5
|
-
class PuppetEPP < BaseValidator
|
6
|
-
# In Puppet >= 5.3.4, the error context formatting was changed to facilitate localization
|
7
|
-
ERROR_CONTEXT = %r{(?:file:\s(?<file>.+?)|line:\s(?<line>.+?)|column:\s(?<column>.+?))}
|
8
|
-
# In Puppet < 5.3.3, the error context was formatted in these variations:
|
9
|
-
# - "at file_path:line_num:col_num"
|
10
|
-
# - "at file_path:line_num"
|
11
|
-
# - "at line line_num"
|
12
|
-
# - "in file_path"
|
13
|
-
ERROR_CONTEXT_LEGACY = %r{(?:at\sline\s(?<line>\d+)|at\s(?<file>.+?):(?<line>\d+):(?<column>\d+)|at\s(?<file>.+?):(?<line>\d+)|in\s(?<file>.+?))}
|
14
|
-
|
15
|
-
PUPPET_LOGGER_PREFIX = %r{^(debug|info|notice|warning|error|alert|critical):\s.+?$}i
|
16
|
-
PUPPET_SYNTAX_PATTERN = %r{^
|
17
|
-
(?<severity>.+?):\s
|
18
|
-
(?<message>.+?)
|
19
|
-
(?:
|
20
|
-
\s\(#{ERROR_CONTEXT}(,\s#{ERROR_CONTEXT})*\)| # attempt to match the new localisation friendly location
|
21
|
-
\s#{ERROR_CONTEXT_LEGACY}| # attempt to match the old " at file:line:column" location
|
22
|
-
$ # handle cases where the output has no location
|
23
|
-
)
|
24
|
-
$}x
|
25
|
-
|
26
|
-
def self.name
|
27
|
-
'puppet-epp'
|
28
|
-
end
|
29
|
-
|
30
|
-
def self.cmd
|
31
|
-
'puppet'
|
32
|
-
end
|
33
|
-
|
34
|
-
def self.pattern
|
35
|
-
'**/*.epp'
|
36
|
-
end
|
37
|
-
|
38
|
-
def self.pattern_ignore
|
39
|
-
''
|
40
|
-
end
|
41
|
-
|
42
|
-
def self.spinner_text(_targets = nil)
|
43
|
-
_('Checking Puppet EPP syntax (%{pattern}).') % { pattern: pattern }
|
44
|
-
end
|
45
|
-
|
46
|
-
def self.parse_options(_options, targets)
|
47
|
-
# Due to PDK-1266 we need to run `puppet parser validate` with an empty
|
48
|
-
# modulepath. On *nix, Ruby treats `/dev/null` as an empty directory
|
49
|
-
# however it doesn't do so with `NUL` on Windows. The workaround for
|
50
|
-
# this to ensure consistent behaviour is to create an empty temporary
|
51
|
-
# directory and use that as the modulepath.
|
52
|
-
['epp', 'validate', '--config', null_file, '--modulepath', validate_tmpdir].concat(targets)
|
53
|
-
end
|
54
|
-
|
55
|
-
def self.invoke(report, options)
|
56
|
-
super
|
57
|
-
ensure
|
58
|
-
remove_validate_tmpdir
|
59
|
-
end
|
60
|
-
|
61
|
-
def self.validate_tmpdir
|
62
|
-
require 'tmpdir'
|
63
|
-
|
64
|
-
@validate_tmpdir ||= Dir.mktmpdir('puppet-epp-validate')
|
65
|
-
end
|
66
|
-
|
67
|
-
def self.remove_validate_tmpdir
|
68
|
-
return unless @validate_tmpdir
|
69
|
-
return unless PDK::Util::Filesystem.directory?(@validate_tmpdir)
|
70
|
-
|
71
|
-
PDK::Util::Filesystem.remove_entry_secure(@validate_tmpdir)
|
72
|
-
@validate_tmpdir = nil
|
73
|
-
end
|
74
|
-
|
75
|
-
def self.null_file
|
76
|
-
Gem.win_platform? ? 'NUL' : '/dev/null'
|
77
|
-
end
|
78
|
-
|
79
|
-
def self.parse_output(report, result, targets)
|
80
|
-
# Due to PUP-7504, we will have to programmatically construct the json
|
81
|
-
# object from the text output for now.
|
82
|
-
output = result[:stderr].split(%r{\r?\n}).reject { |entry| entry.empty? }
|
83
|
-
|
84
|
-
results_data = []
|
85
|
-
output.each do |offense|
|
86
|
-
offense_data = parse_offense(offense)
|
87
|
-
results_data << offense_data
|
88
|
-
end
|
89
|
-
|
90
|
-
# puppet parser validate does not include files without problems in its
|
91
|
-
# output, so we need to go through the list of targets and add passing
|
92
|
-
# events to the report for any target not listed in the output.
|
93
|
-
targets.reject { |target| results_data.any? { |j| j[:file] =~ %r{#{target}} } }.each do |target|
|
94
|
-
report.add_event(
|
95
|
-
file: target,
|
96
|
-
source: name,
|
97
|
-
severity: :ok,
|
98
|
-
state: :passed,
|
99
|
-
)
|
100
|
-
end
|
101
|
-
|
102
|
-
results_data.each do |offense|
|
103
|
-
report.add_event(offense)
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
def self.parse_offense(offense)
|
108
|
-
sanitize_console_output(offense)
|
109
|
-
|
110
|
-
offense_data = {
|
111
|
-
source: name,
|
112
|
-
state: :failure,
|
113
|
-
}
|
114
|
-
|
115
|
-
if offense.match(PUPPET_LOGGER_PREFIX)
|
116
|
-
attributes = offense.match(PUPPET_SYNTAX_PATTERN)
|
117
|
-
|
118
|
-
unless attributes.nil?
|
119
|
-
attributes.names.each do |name|
|
120
|
-
offense_data[name.to_sym] = attributes[name] unless attributes[name].nil?
|
121
|
-
end
|
122
|
-
end
|
123
|
-
else
|
124
|
-
offense_data[:message] = offense
|
125
|
-
end
|
126
|
-
|
127
|
-
offense_data
|
128
|
-
end
|
129
|
-
|
130
|
-
def self.sanitize_console_output(line)
|
131
|
-
line.gsub!(%r{\e\[([;\d]+)?m}, '')
|
132
|
-
end
|
133
|
-
end
|
134
|
-
end
|
135
|
-
end
|
@@ -1,64 +0,0 @@
|
|
1
|
-
require 'pdk'
|
2
|
-
|
3
|
-
module PDK
|
4
|
-
module Validate
|
5
|
-
class PuppetLint < BaseValidator
|
6
|
-
def self.name
|
7
|
-
'puppet-lint'
|
8
|
-
end
|
9
|
-
|
10
|
-
def self.cmd
|
11
|
-
'puppet-lint'
|
12
|
-
end
|
13
|
-
|
14
|
-
def self.pattern
|
15
|
-
'**/*.pp'
|
16
|
-
end
|
17
|
-
|
18
|
-
def self.spinner_text(_targets = nil)
|
19
|
-
_('Checking Puppet manifest style (%{pattern}).') % { pattern: pattern }
|
20
|
-
end
|
21
|
-
|
22
|
-
def self.parse_options(options, targets)
|
23
|
-
cmd_options = ['--json', '--relative']
|
24
|
-
|
25
|
-
cmd_options << '--fix' if options[:auto_correct]
|
26
|
-
|
27
|
-
cmd_options.concat(targets)
|
28
|
-
end
|
29
|
-
|
30
|
-
def self.parse_output(report, result, targets)
|
31
|
-
begin
|
32
|
-
json_data = JSON.parse(result[:stdout]).flatten
|
33
|
-
rescue JSON::ParserError
|
34
|
-
raise PDK::Validate::ParseOutputError, result[:stdout]
|
35
|
-
end
|
36
|
-
|
37
|
-
# puppet-lint does not include files without problems in its JSON
|
38
|
-
# output, so we need to go through the list of targets and add passing
|
39
|
-
# events to the report for any target not listed in the JSON output.
|
40
|
-
targets.reject { |target| json_data.any? { |j| j['path'] == target } }.each do |target|
|
41
|
-
report.add_event(
|
42
|
-
file: target,
|
43
|
-
source: name,
|
44
|
-
severity: 'ok',
|
45
|
-
state: :passed,
|
46
|
-
)
|
47
|
-
end
|
48
|
-
|
49
|
-
json_data.each do |offense|
|
50
|
-
report.add_event(
|
51
|
-
file: offense['path'],
|
52
|
-
source: name,
|
53
|
-
line: offense['line'],
|
54
|
-
column: offense['column'],
|
55
|
-
message: offense['message'],
|
56
|
-
test: offense['check'],
|
57
|
-
severity: (offense['kind'] == 'fixed') ? 'corrected' : offense['kind'],
|
58
|
-
state: :failure,
|
59
|
-
)
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
64
|
-
end
|
@@ -1,135 +0,0 @@
|
|
1
|
-
require 'pdk'
|
2
|
-
|
3
|
-
module PDK
|
4
|
-
module Validate
|
5
|
-
class PuppetSyntax < BaseValidator
|
6
|
-
# In Puppet >= 5.3.4, the error context formatting was changed to facilitate localization
|
7
|
-
ERROR_CONTEXT = %r{(?:file:\s(?<file>.+?)|line:\s(?<line>.+?)|column:\s(?<column>.+?))}
|
8
|
-
# In Puppet < 5.3.3, the error context was formatted in these variations:
|
9
|
-
# - "at file_path:line_num:col_num"
|
10
|
-
# - "at file_path:line_num"
|
11
|
-
# - "at line line_num"
|
12
|
-
# - "in file_path"
|
13
|
-
ERROR_CONTEXT_LEGACY = %r{(?:at\sline\s(?<line>\d+)|at\s(?<file>.+?):(?<line>\d+):(?<column>\d+)|at\s(?<file>.+?):(?<line>\d+)|in\s(?<file>.+?))}
|
14
|
-
|
15
|
-
PUPPET_LOGGER_PREFIX = %r{^(debug|info|notice|warning|error|alert|critical):\s.+?$}i
|
16
|
-
PUPPET_SYNTAX_PATTERN = %r{^
|
17
|
-
(?<severity>.+?):\s
|
18
|
-
(?<message>.+?)
|
19
|
-
(?:
|
20
|
-
\s\(#{ERROR_CONTEXT}(,\s#{ERROR_CONTEXT})*\)| # attempt to match the new localisation friendly location
|
21
|
-
\s#{ERROR_CONTEXT_LEGACY}| # attempt to match the old " at file:line:column" location
|
22
|
-
$ # handle cases where the output has no location
|
23
|
-
)
|
24
|
-
$}x
|
25
|
-
|
26
|
-
def self.name
|
27
|
-
'puppet-syntax'
|
28
|
-
end
|
29
|
-
|
30
|
-
def self.cmd
|
31
|
-
'puppet'
|
32
|
-
end
|
33
|
-
|
34
|
-
def self.pattern
|
35
|
-
'**/*.pp'
|
36
|
-
end
|
37
|
-
|
38
|
-
def self.pattern_ignore
|
39
|
-
'/plans/**/*.pp'
|
40
|
-
end
|
41
|
-
|
42
|
-
def self.spinner_text(_targets = nil)
|
43
|
-
_('Checking Puppet manifest syntax (%{pattern}).') % { pattern: pattern }
|
44
|
-
end
|
45
|
-
|
46
|
-
def self.parse_options(_options, targets)
|
47
|
-
# Due to PDK-1266 we need to run `puppet parser validate` with an empty
|
48
|
-
# modulepath. On *nix, Ruby treats `/dev/null` as an empty directory
|
49
|
-
# however it doesn't do so with `NUL` on Windows. The workaround for
|
50
|
-
# this to ensure consistent behaviour is to create an empty temporary
|
51
|
-
# directory and use that as the modulepath.
|
52
|
-
['parser', 'validate', '--config', null_file, '--modulepath', validate_tmpdir].concat(targets)
|
53
|
-
end
|
54
|
-
|
55
|
-
def self.invoke(report, options)
|
56
|
-
super
|
57
|
-
ensure
|
58
|
-
remove_validate_tmpdir
|
59
|
-
end
|
60
|
-
|
61
|
-
def self.validate_tmpdir
|
62
|
-
require 'tmpdir'
|
63
|
-
|
64
|
-
@validate_tmpdir ||= Dir.mktmpdir('puppet-parser-validate')
|
65
|
-
end
|
66
|
-
|
67
|
-
def self.remove_validate_tmpdir
|
68
|
-
return unless @validate_tmpdir
|
69
|
-
return unless PDK::Util::Filesystem.directory?(@validate_tmpdir)
|
70
|
-
|
71
|
-
PDK::Util::Filesystem.remove_entry_secure(@validate_tmpdir)
|
72
|
-
@validate_tmpdir = nil
|
73
|
-
end
|
74
|
-
|
75
|
-
def self.null_file
|
76
|
-
Gem.win_platform? ? 'NUL' : '/dev/null'
|
77
|
-
end
|
78
|
-
|
79
|
-
def self.parse_output(report, result, targets)
|
80
|
-
# Due to PUP-7504, we will have to programmatically construct the json
|
81
|
-
# object from the text output for now.
|
82
|
-
output = result[:stderr].split(%r{\r?\n}).reject { |entry| entry.empty? }
|
83
|
-
|
84
|
-
results_data = []
|
85
|
-
output.each do |offense|
|
86
|
-
offense_data = parse_offense(offense)
|
87
|
-
results_data << offense_data
|
88
|
-
end
|
89
|
-
|
90
|
-
# puppet parser validate does not include files without problems in its
|
91
|
-
# output, so we need to go through the list of targets and add passing
|
92
|
-
# events to the report for any target not listed in the output.
|
93
|
-
targets.reject { |target| results_data.any? { |j| j[:file] =~ %r{#{target}} } }.each do |target|
|
94
|
-
report.add_event(
|
95
|
-
file: target,
|
96
|
-
source: name,
|
97
|
-
severity: :ok,
|
98
|
-
state: :passed,
|
99
|
-
)
|
100
|
-
end
|
101
|
-
|
102
|
-
results_data.each do |offense|
|
103
|
-
report.add_event(offense)
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
def self.parse_offense(offense)
|
108
|
-
sanitize_console_output(offense)
|
109
|
-
|
110
|
-
offense_data = {
|
111
|
-
source: name,
|
112
|
-
state: :failure,
|
113
|
-
}
|
114
|
-
|
115
|
-
if offense.match(PUPPET_LOGGER_PREFIX)
|
116
|
-
attributes = offense.match(PUPPET_SYNTAX_PATTERN)
|
117
|
-
|
118
|
-
unless attributes.nil?
|
119
|
-
attributes.names.each do |name|
|
120
|
-
offense_data[name.to_sym] = attributes[name] unless attributes[name].nil?
|
121
|
-
end
|
122
|
-
end
|
123
|
-
else
|
124
|
-
offense_data[:message] = offense
|
125
|
-
end
|
126
|
-
|
127
|
-
offense_data
|
128
|
-
end
|
129
|
-
|
130
|
-
def self.sanitize_console_output(line)
|
131
|
-
line.gsub!(%r{\e\[([;\d]+)?m}, '')
|
132
|
-
end
|
133
|
-
end
|
134
|
-
end
|
135
|
-
end
|
@@ -1,26 +0,0 @@
|
|
1
|
-
require 'pdk'
|
2
|
-
|
3
|
-
module PDK
|
4
|
-
module Validate
|
5
|
-
class PuppetValidator < BaseValidator
|
6
|
-
def self.name
|
7
|
-
'puppet'
|
8
|
-
end
|
9
|
-
|
10
|
-
def self.puppet_validators
|
11
|
-
[PuppetSyntax, PuppetLint, PuppetEPP]
|
12
|
-
end
|
13
|
-
|
14
|
-
def self.invoke(report, options = {})
|
15
|
-
exit_code = 0
|
16
|
-
|
17
|
-
puppet_validators.each do |validator|
|
18
|
-
exit_code = validator.invoke(report, options)
|
19
|
-
break if exit_code != 0
|
20
|
-
end
|
21
|
-
|
22
|
-
exit_code
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
@@ -1,72 +0,0 @@
|
|
1
|
-
require 'pdk'
|
2
|
-
|
3
|
-
module PDK
|
4
|
-
module Validate
|
5
|
-
class Rubocop < BaseValidator
|
6
|
-
ALLOW_EMPTY_TARGETS = true
|
7
|
-
|
8
|
-
def self.name
|
9
|
-
'rubocop'
|
10
|
-
end
|
11
|
-
|
12
|
-
def self.cmd
|
13
|
-
'rubocop'
|
14
|
-
end
|
15
|
-
|
16
|
-
def self.pattern
|
17
|
-
'**/**.rb'
|
18
|
-
end
|
19
|
-
|
20
|
-
def self.spinner_text(_targets = nil)
|
21
|
-
_('Checking Ruby code style (%{pattern}).') % { pattern: pattern }
|
22
|
-
end
|
23
|
-
|
24
|
-
def self.parse_options(options, targets)
|
25
|
-
cmd_options = ['--format', 'json']
|
26
|
-
|
27
|
-
if options[:auto_correct]
|
28
|
-
cmd_options << '--auto-correct'
|
29
|
-
end
|
30
|
-
|
31
|
-
cmd_options.concat(targets)
|
32
|
-
end
|
33
|
-
|
34
|
-
def self.parse_output(report, result, _targets)
|
35
|
-
return if result[:stdout].empty?
|
36
|
-
|
37
|
-
begin
|
38
|
-
json_data = JSON.parse(result[:stdout])
|
39
|
-
rescue JSON::ParserError
|
40
|
-
raise PDK::Validate::ParseOutputError, result[:stdout]
|
41
|
-
end
|
42
|
-
|
43
|
-
return unless json_data.key?('files')
|
44
|
-
|
45
|
-
json_data['files'].each do |file_info|
|
46
|
-
next unless file_info.key?('offenses')
|
47
|
-
result = {
|
48
|
-
file: file_info['path'],
|
49
|
-
source: 'rubocop',
|
50
|
-
}
|
51
|
-
|
52
|
-
if file_info['offenses'].empty?
|
53
|
-
report.add_event(result.merge(state: :passed, severity: :ok))
|
54
|
-
else
|
55
|
-
file_info['offenses'].each do |offense|
|
56
|
-
report.add_event(
|
57
|
-
result.merge(
|
58
|
-
line: offense['location']['line'],
|
59
|
-
column: offense['location']['column'],
|
60
|
-
message: offense['message'],
|
61
|
-
severity: offense['corrected'] ? 'corrected' : offense['severity'],
|
62
|
-
test: offense['cop_name'],
|
63
|
-
state: :failure,
|
64
|
-
),
|
65
|
-
)
|
66
|
-
end
|
67
|
-
end
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
72
|
-
end
|