abide_dev_utils 0.5.0 → 0.8.1

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: a27976b9f740b67261fa080eeba927bc6fc98e73ffb592fafdd91d4d612cc51f
4
- data.tar.gz: 64778a0d476e2f96d70a14ab318b517c597aa3c5e33afd8939173385b013fe6c
3
+ metadata.gz: 550d53a5583b4befefc9cb5b2cd2ae41dab12dac51d6fe564067d8ad6113daf4
4
+ data.tar.gz: 62edfb6d0145ec674ad2cf70eb09b37008075bbe7215c3aba7b0c82988b49cb7
5
5
  SHA512:
6
- metadata.gz: e31df40e6dd34a57156ff517d39fcc036f5655ce2f31bf86b1ebb66754304575b7379e0a3751ae378bc81bc1520dbe095255012dc7e10b24e4f6c3f4a85e6551
7
- data.tar.gz: c9dd204d55a37d389c0c8a4d3d281310b87241cbbd149ca029a3fa6a38fd8414bcb350467cd348cdd330fe946e6ee75625bfe2828319ad25d9eb8ed5cc37a9a3
6
+ metadata.gz: 336b80d3b41db5c839de238d7b3308bcd02b4cad1e339fb2b5879cbb7e132cbf3b8b8432f2ba6c0c2fa0825491fb042817a6225594433fba058eacfdeb6f510c
7
+ data.tar.gz: a8e34b36f2bdd51d4d7bf467b91c3f01e7507ea5315a864a564f4c830d43768f98de88075569643205d8c5c43ea8113a1c0afed011a708367a3c66771e10534f
data/.gitignore CHANGED
@@ -6,7 +6,8 @@
6
6
  /pkg/
7
7
  /spec/reports/
8
8
  /tmp/
9
-
9
+ w10_20h2.xml
10
+ w10_2004.xml
10
11
  # rspec failure tracking
11
12
  .rspec_status
12
13
  Gemfile.lock
data/.rubocop.yml CHANGED
@@ -12,7 +12,7 @@ AllCops:
12
12
  - 'tmp/**/*'
13
13
  - '.git/**/*'
14
14
  - 'bin/*'
15
- TargetRubyVersion: 2.5
15
+ TargetRubyVersion: 2.7
16
16
  SuggestExtensions: false
17
17
 
18
18
  Naming/PredicateName:
data/CODEOWNERS ADDED
@@ -0,0 +1 @@
1
+ * @puppetlabs/abide-team
data/README.md CHANGED
@@ -88,6 +88,8 @@ Install the gem:
88
88
 
89
89
  ### Overview of Commands
90
90
 
91
+ * `abide comply` - Command namespace for Puppet Comply commands
92
+ * `abide comply report` - Creates a scan report in YAML format by scraping Puppet Comply
91
93
  * `abide jira` - Command namespace for Jira commands
92
94
  * `abide jira auth` - Authenticate with Jira. Only useful as a stand-alone command to test authentication
93
95
  * `abide jira from_coverage` - Creates a parent issue with subtasks from a Puppet coverage report
@@ -100,6 +102,34 @@ Install the gem:
100
102
  * `abide xccdf` - Command namespace for XCCDF commands
101
103
  * `abide xccdf to_hiera` - Converts a benchmark XCCDF file to a Hiera yaml file
102
104
 
105
+ ### Comply Command Reference
106
+
107
+ #### report
108
+
109
+ * Required positional parameters:
110
+ * `COMPLY_URL` - The URL of Puppet Comply
111
+ * `COMPLY_PASSWORD` - The password for the Puppet Comply user
112
+ * Options:
113
+ * `--out-file`, `-o` - The path to save the scan report. Defaults to `./comply_scan_report.yaml`
114
+ * `--username`, `-u` - The Puppet Comply username. Defaults to `comply`
115
+ * `--status`, `-s` - A comma-separated list of check statuses to ONLY include in the report. Valid statuses are: `pass`, `fail`, `error`, `notapplicable`, `notchecked`, `unknown`, `informational`
116
+ * `--only`, `-O` - A comma-separated list of node certnames to ONLY build reports for. No other nodes will have reports built for them except the ones specified. This option is mutually exclusive with `--ignore` and, if both are set, this options will take precedence over `--ignore`.
117
+ * `--ignore`, `-I` - A comma-separated list of node certnames to ignore building reports for. This options is mutually exclusive with `--only` and, if both are set, `--only` will take precedence over this option.
118
+
119
+ Examples:
120
+
121
+ Generating a report of all failed and err'd scan checks
122
+
123
+ ```sh
124
+ abide comply report https://comply.my.instance 'my_comply_password!' -s fail,error
125
+ ```
126
+
127
+ Generating a report for certain nodes only
128
+
129
+ ```sh
130
+ abide comply report https://comply.my.instance 'my_comply_password!' -O specific-node.my.instance
131
+ ```
132
+
103
133
  ### Jira Command Reference
104
134
 
105
135
  #### from_coverage
@@ -7,14 +7,14 @@ require "abide_dev_utils/version"
7
7
  Gem::Specification.new do |spec|
8
8
  spec.name = "abide_dev_utils"
9
9
  spec.version = AbideDevUtils::VERSION
10
- spec.authors = ["Heston Snodgrass"]
11
- spec.email = ["hsnodgrass3@gmail.com"]
10
+ spec.authors = ["abide-team"]
11
+ spec.email = ["abide-team@puppet.com"]
12
12
 
13
- spec.summary = "Helper utilities for developing Abide"
14
- spec.description = "Provides a CLI with helpful utilities for developing Abide"
15
- spec.homepage = "https://github.com/hsnodgrass/abide_dev_utils"
13
+ spec.summary = "Helper utilities for developing compliance Puppet code"
14
+ spec.description = "Provides a CLI with helpful utilities for developing compliance Puppet code"
15
+ spec.homepage = "https://github.com/puppetlabs/abide_dev_utils"
16
16
  spec.license = "MIT"
17
- spec.required_ruby_version = Gem::Requirement.new(">= 2.5.0")
17
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.7.0")
18
18
 
19
19
  # spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
20
20
 
@@ -34,10 +34,12 @@ Gem::Specification.new do |spec|
34
34
  # Prod dependencies
35
35
  spec.add_dependency 'nokogiri', '~> 1.11'
36
36
  spec.add_dependency 'cmdparse', '~> 3.0'
37
- spec.add_dependency 'puppet', '>= 6.19'
37
+ spec.add_dependency 'puppet', '>= 6.23'
38
38
  spec.add_dependency 'jira-ruby', '~> 2.1'
39
39
  spec.add_dependency 'ruby-progressbar', '~> 1.11'
40
40
  spec.add_dependency 'selenium-webdriver', '~> 4.0.0.beta4'
41
+ spec.add_dependency 'google-cloud-storage', '~> 1.34'
42
+ spec.add_dependency 'hashdiff', '~> 1.0'
41
43
 
42
44
  # Dev dependencies
43
45
  spec.add_development_dependency 'bundler'
@@ -45,6 +47,7 @@ Gem::Specification.new do |spec|
45
47
  spec.add_development_dependency 'console'
46
48
  spec.add_development_dependency 'github_changelog_generator'
47
49
  spec.add_development_dependency 'gem-release'
50
+ spec.add_development_dependency 'pry'
48
51
  spec.add_development_dependency 'rspec', '~> 3.10'
49
52
  spec.add_development_dependency 'rubocop', '~> 1.8'
50
53
  spec.add_development_dependency 'rubocop-rspec', '~> 2.1'
data/itests.rb ADDED
@@ -0,0 +1,138 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'json'
5
+ require 'yaml'
6
+ require 'abide_dev_utils/comply'
7
+ require 'abide_dev_utils/ppt/api'
8
+
9
+ OS_BENCHMARK_MAP = {
10
+ 'centos-7' => 'CIS_CentOS_Linux_7_Benchmark_v3.1.1-xccdf.xml',
11
+ 'centos-8' => 'CIS_CentOS_Linux_8_Benchmark_v1.0.1-xccdf.xml',
12
+ 'rhel-7' => 'CIS_Red_Hat_Enterprise_Linux_7_Benchmark_v3.1.1-xccdf.xml',
13
+ 'rhel-8' => 'CIS_Red_Hat_Enterprise_Linux_8_Benchmark_v1.0.1-xccdf.xml',
14
+ 'serv-2016' => 'CIS_Microsoft_Windows_Server_2016_RTM_(Release_1607)_Benchmark_v1.3.0-xccdf.xml',
15
+ 'serv-2019' => 'CIS_Microsoft_Windows_Server_2019_Benchmark_v1.2.1-xccdf.xml'
16
+ }
17
+ EL_PROFILE_1_SERVER = 'xccdf_org.cisecurity.benchmarks_profile_Level_1_-_Server'
18
+ WIN_PROFILE_1_MS = 'xccdf_org.cisecurity.benchmarks_profile_Level_1_-_Member_Server'
19
+ NIX_SCAN_HASH = {
20
+ 'nix-centos-7.c.team-sse.internal' => {
21
+ 'benchmark' => OS_BENCHMARK_MAP['centos-7'],
22
+ 'profile' => EL_PROFILE_1_SERVER
23
+ },
24
+ 'nix-centos-8.c.team-sse.internal' => {
25
+ 'benchmark' => OS_BENCHMARK_MAP['centos-8'],
26
+ 'profile' => EL_PROFILE_1_SERVER
27
+ },
28
+ 'nix-rhel-7.c.team-sse.internal' => {
29
+ 'benchmark' => OS_BENCHMARK_MAP['rhel-7'],
30
+ 'profile' => EL_PROFILE_1_SERVER
31
+ },
32
+ 'nix-rhel-8.c.team-sse.internal' => {
33
+ 'benchmark' => OS_BENCHMARK_MAP['rhel-8'],
34
+ 'profile' => EL_PROFILE_1_SERVER
35
+ }
36
+ }.freeze
37
+ WIN_SCAN_HASH = {
38
+ 'win-server-2016.c.team-sse.internal' => {
39
+ 'benchmark' => OS_BENCHMARK_MAP['serv-2016'],
40
+ 'profile' => EL_PROFILE_1_SERVER
41
+ },
42
+ 'win-serv-2019.c.team-sse.internal' => {
43
+ 'benchmark' => OS_BENCHMARK_MAP['serv-2019'],
44
+ 'profile' => WIN_PROFILE_1_MS
45
+ }
46
+ }.freeze
47
+
48
+ scan_hash = ENV['ABIDE_OS'] == 'nix' ? NIX_SCAN_HASH : WIN_SCAN_HASH
49
+ node_group_name = ENV['ABIDE_OS'] == 'nix' ? 'CEM Linux Nodes' : 'CEM Windows Nodes'
50
+
51
+ puts 'Creating client...'
52
+ client = AbideDevUtils::Ppt::ApiClient.new(ENV['PUPPET_HOST'], auth_token: ENV['PE_ACCESS_TOKEN'])
53
+ puts 'Starting code deploy...'
54
+ code_manager_deploy = client.post_codemanager_deploys('environments' => ['production'], 'wait' => true)
55
+ raise 'Code manager deployment failed!' unless code_manager_deploy['status'] == 'complete'
56
+
57
+ puts 'Code deploy successful...'
58
+ puts 'Gathering node group ID...'
59
+ node_groups = client.get_classifier1_groups
60
+ node_group_id = nil
61
+ node_groups.each { |x| node_group_id = x['id'] if x['name'] == node_group_name }
62
+ raise 'Failed to find requested node group!' if node_group_id.nil?
63
+
64
+ puts 'Running Puppet on nodes...'
65
+ puppet_run = client.post_orchestrator_command_deploy('environment' => 'production', 'scope' => { 'node_group' => node_group_id })
66
+ puts "Started job #{puppet_run['job']['name']}..."
67
+ timeout = 0
68
+ run_complete = false
69
+ until run_complete || timeout >= 30
70
+ puts "Waiting on job #{puppet_run['job']['name']} to complete..."
71
+ status = client.get_orchestrator_jobs(puppet_run['job']['name'])
72
+ case status['state']
73
+ when 'failed'
74
+ raise "Job #{puppet_run['job']['name']} finished with failures!"
75
+ when 'finished'
76
+ run_complete = true
77
+ break
78
+ else
79
+ timeout += 1
80
+ sleep(10)
81
+ end
82
+ end
83
+ raise 'Job timed out waiting for completion' unless run_complete
84
+
85
+ puts 'Starting node scans...'
86
+ scan_job = client.post_orchestrator_command_task(
87
+ 'environment' => 'production',
88
+ 'task' => 'comply::ciscat_scan',
89
+ 'params' => {
90
+ 'comply_port' => '443',
91
+ 'comply_server' => ENV['COMPLY_FQDN'],
92
+ 'ssl_verify_mode' => 'none',
93
+ 'scan_type' => 'desired',
94
+ 'scan_hash' => JSON.generate(scan_hash)
95
+ },
96
+ 'scope' => {
97
+ 'node_group' => node_group_id
98
+ }
99
+ )
100
+ puts "Started scan #{scan_job['job']['name']}..."
101
+ timeout = 0
102
+ scan_complete = false
103
+ until scan_complete || timeout >= 30
104
+ puts "Waiting on scan #{scan_job['job']['name']} to complete..."
105
+ status = client.get_orchestrator_jobs(scan_job['job']['name'])
106
+ case status['state']
107
+ when 'failed'
108
+ raise "Task #{scan_job['job']['name']} finished with failures!"
109
+ when 'finished'
110
+ scan_complete = true
111
+ break
112
+ else
113
+ timeout += 1
114
+ sleep(10)
115
+ end
116
+ end
117
+ raise 'Job timed out waiting for completion' unless scan_complete
118
+
119
+ puts 'Collecting scan report from Comply...'
120
+ onlylist = scan_hash.keys
121
+ scan_report = AbideDevUtils::Comply.build_report("https://#{ENV['COMPLY_FQDN']}", ENV['COMPLY_PASSWORD'], nil, onlylist: onlylist)
122
+ puts 'Saving report to nix_report.yaml...'
123
+ File.open('nix_report.yaml', 'w') { |f| f.write(scan_report.to_yaml) }
124
+
125
+ puts 'Comparing current report to last report...'
126
+ opts = {
127
+ report_name: 'nix_report.yaml',
128
+ remote_storage: 'gcloud',
129
+ upload: true
130
+ }
131
+ result = AbideDevUtils::Comply.compare_reports(File.expand_path('./nix_report.yaml'), 'nix_report.yaml', opts)
132
+ if result
133
+ puts 'Success!'
134
+ exit(0)
135
+ else
136
+ puts 'Failure!'
137
+ exit(1)
138
+ end
@@ -12,6 +12,7 @@ module Abide
12
12
  def initialize
13
13
  super(CMD_NAME, CMD_SHORT, CMD_LONG, takes_commands: true)
14
14
  add_command(ComplyReportCommand.new)
15
+ add_command(ComplyCompareReportCommand.new)
15
16
  end
16
17
  end
17
18
 
@@ -28,6 +29,10 @@ module Abide
28
29
  LONGCMD
29
30
  CMD_COMPLY_URL = 'The URL (including https://) of Puppet Comply'
30
31
  CMD_COMPLY_PASSWORD = 'The password for Puppet Comply'
32
+ OPT_TIMEOUT_DESC = <<~EOTO
33
+ The number of seconds you would like requests to wait before timing out. Defaults
34
+ to 10 seconds.
35
+ EOTO
31
36
  OPT_STATUS_DESC = <<~EODESC
32
37
  A comma-separated list of check statuses to ONLY include in the report.
33
38
  Valid statuses are: pass, fail, error, notapplicable, notchecked, unknown, informational
@@ -50,18 +55,21 @@ module Abide
50
55
  options.on('-u [USERNAME]', '--username [USERNAME]', 'The username for Comply (defaults to comply)') do |u|
51
56
  @data[:username] = u
52
57
  end
53
- options.on('-s [STATUS]', '--status [STATUS]', OPT_STATUS_DESC) do |s|
54
- status_array = s.nil? ? nil : s.split(',').map(&:downcase)
55
- status_array&.map! { |i| i == 'notchecked' ? 'not checked' : i }
56
- @data[:status] = status_array
58
+ options.on('-t [SECONDS]', '--timeout [SECONDS]', OPT_TIMEOUT_DESC) do |t|
59
+ @data[:timeout] = t
57
60
  end
58
- options.on('-O [CERTNAME]', '--only [CERTNAME]', OPT_ONLY_NODES) do |o|
59
- only_array = o.nil? ? nil : s.split(',').map(&:downcase)
60
- @data[:only] = only_array
61
+ options.on('-s [X,Y,Z]', '--status [X,Y,Z]',
62
+ %w[pass fail error notapplicable notchecked unknown informational],
63
+ Array,
64
+ OPT_STATUS_DESC) do |s|
65
+ s&.map! { |i| i == 'notchecked' ? 'not checked' : i }
66
+ @data[:status] = s
61
67
  end
62
- options.on('-I [CERTNAME]', '--ignore [CERTNAME]', OPT_IGNORE_NODES) do |i|
63
- ignore_array = i.nil? ? nil : i.split(',').map(&:downcase)
64
- @data[:ignore] = ignore_array
68
+ options.on('--only [X,Y,Z]', Array, OPT_ONLY_NODES) do |o|
69
+ @data[:onlylist] = o
70
+ end
71
+ options.on('--ignore [X,Y,Z]', Array, OPT_IGNORE_NODES) do |i|
72
+ @data[:ignorelist] = i
65
73
  end
66
74
  end
67
75
 
@@ -79,19 +87,29 @@ module Abide
79
87
  conf = config_section('comply')
80
88
  comply_url = conf.fetch(:url) if comply_url.nil?
81
89
  comply_password = comply_password.nil? ? conf.fetch(:password, Abide::CLI::PROMPT.password) : comply_password
82
- username = @data.fetch(:username, nil).nil? ? conf.fetch(:username, 'comply') : @data[:username]
83
- status = @data.fetch(:status, nil).nil? ? conf.fecth(:status, nil) : @data[:status]
84
- ignorelist = @data.fetch(:ignore, nil).nil? ? conf.fetch(:ignore, nil) : @data[:ignore]
85
- onlylist = @data.fetch(:only, nil).nil? ? conf.fetch(:only, nil) : @data[:only]
86
- report = AbideDevUtils::Comply.scan_report(comply_url,
87
- comply_password,
88
- username: username,
89
- status: status,
90
- ignorelist: ignorelist,
91
- onlylist: onlylist)
90
+ report = AbideDevUtils::Comply.build_report(comply_url, comply_password, conf, **@data)
92
91
  outfile = @data.fetch(:file, nil).nil? ? conf.fetch(:report_path, 'comply_scan_report.yaml') : @data[:file]
93
92
  Abide::CLI::OUTPUT.yaml(report, file: outfile)
94
93
  end
95
94
  end
95
+
96
+ class ComplyCompareReportCommand < AbideCommand
97
+ CMD_NAME = 'compare-report'
98
+ CMD_SHORT = 'Compare two Comply reports and get the differences.'
99
+ CMD_LONG = 'Compare two Comply reports and get the differences. Report A is compared to report B, showing what changes it would take for A to equal B.'
100
+ CMD_REPORT_A = 'The current Comply report yaml file'
101
+ CMD_REPORT_B = 'The old Comply report yaml file name or full path'
102
+ def initialize
103
+ super(CMD_NAME, CMD_SHORT, CMD_LONG, takes_commands: false)
104
+ argument_desc(REPORT_A: CMD_REPORT_A, REPORT_B: CMD_REPORT_B)
105
+ options.on('-u', '--upload-new', 'If you want to upload the new scan report') { @data[:upload] = true }
106
+ options.on('-s [STORAGE]', '--remote-storage [STORAGE]', 'Remote storage to upload the report to. (Only supports "gcloud")') { |x| @data[:remote_storage] = x }
107
+ options.on('-r [NAME]', '--name [NAME]', 'The name to upload the report as') { |x| @data[:report_name] = x }
108
+ end
109
+
110
+ def execute(report_a, report_b)
111
+ AbideDevUtils::Comply.compare_reports(report_a, report_b, @data)
112
+ end
113
+ end
96
114
  end
97
115
  end
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'abide_dev_utils/cli/abstract'
4
+ require 'abide_dev_utils/output'
5
+ require 'abide_dev_utils/ppt'
4
6
 
5
7
  module Abide
6
8
  module CLI
@@ -12,6 +14,10 @@ module Abide
12
14
  super(CMD_NAME, CMD_SHORT, CMD_LONG, takes_commands: true)
13
15
  add_command(PuppetCoverageCommand.new)
14
16
  add_command(PuppetNewCommand.new)
17
+ add_command(PuppetRenameCommand.new)
18
+ add_command(PuppetFixClassNamesCommand.new)
19
+ add_command(PuppetAuditClassNamesCommand.new)
20
+ add_command(PuppetAddCISCommentCommand.new)
15
21
  end
16
22
  end
17
23
 
@@ -38,12 +44,12 @@ module Abide
38
44
  end
39
45
 
40
46
  def execute(class_dir, hiera_file)
41
- require 'abide_dev_utils/ppt'
47
+ require 'abide_dev_utils/ppt/coverage'
42
48
  Abide::CLI::VALIDATE.directory(class_dir)
43
49
  Abide::CLI::VALIDATE.file(hiera_file)
44
- coverage = AbideDevUtils::Ppt::CoverageReport.generate(class_dir, hiera_file, @data[:profile])
50
+ coverage = AbideDevUtils::Ppt.generate_coverage_report(class_dir, hiera_file, @data[:profile])
45
51
  coverage.each do |k, v|
46
- next if ['classes', 'benchmark'].include?(k)
52
+ next if k.match?(/classes|benchmark/)
47
53
 
48
54
  Abide::CLI::OUTPUT.simple("#{k} coverage: #{v[:coverage]}%")
49
55
  end
@@ -100,14 +106,133 @@ module Abide
100
106
  end
101
107
 
102
108
  def execute(type, name)
103
- require 'abide_dev_utils/ppt/new_obj'
104
- builder = AbideDevUtils::Ppt::NewObjectBuilder.new(
105
- type,
106
- name,
107
- opts: @data,
108
- vars: @data.fetch(:vars, '').split(',').map { |i| i.split('=') }.to_h # makes the str a hash
109
- )
110
- builder.build
109
+ AbideDevUtils::Ppt.build_new_object(type, name, @data)
110
+ end
111
+ end
112
+
113
+ class PuppetRenameCommand < AbideCommand
114
+ CMD_NAME = 'rename'
115
+ CMD_SHORT = 'Renames a Puppet class'
116
+ CMD_LONG = 'Renames a Puppet class. It does this by renaming the file and also the class name in the file. This command can also move class files based on the new class name.'
117
+ CMD_FROM_ARG = 'The current full class name'
118
+ CMD_TO_ARG = 'The new full class name'
119
+ def initialize
120
+ super(CMD_NAME, CMD_SHORT, CMD_LONG, takes_commands: false)
121
+ argument_desc(FROM: CMD_FROM_ARG, TO: CMD_TO_ARG)
122
+ options.on(
123
+ '-d',
124
+ '--declaration-only',
125
+ 'Will not rename the class file, only the class declaration in the file'
126
+ ) { @data[:declaration_only] = true }
127
+ options.on(
128
+ '-t',
129
+ '--declaration-in-to-file',
130
+ 'Use the path derived from the TO class name as the existing file path when renaming class declaration'
131
+ ) { @data[:declaration_in_to_file] = true }
132
+ options.on(
133
+ '-f',
134
+ '--force',
135
+ 'Forces file move operations'
136
+ ) { @data[:force] = true }
137
+ options.on(
138
+ '-v',
139
+ '--verbose',
140
+ 'Sets verbose mode on file operations'
141
+ ) { @data[:verbose] = true }
142
+ end
143
+
144
+ def execute(from, to)
145
+ AbideDevUtils::Ppt.rename_puppet_class(from, to, **@data)
146
+ end
147
+ end
148
+
149
+ class PuppetFixClassNamesCommand < AbideCommand
150
+ CMD_NAME = 'fix-class-names'
151
+ CMD_SHORT = 'Fixes Puppet class names that are mismatched'
152
+ CMD_LONG = 'Fixes Puppet class names that are mismatched'
153
+ CMD_MODE_ARG = '"file" or "class". If "file", the file names will be changed to match their class declarations. If "class", the class declarations will be changed to match the file names.'
154
+ CMD_DIR_ARG = 'The directory containing the Puppet class files'
155
+ def initialize
156
+ super(CMD_NAME, CMD_SHORT, CMD_LONG, takes_commands: false)
157
+ argument_desc(MODE: CMD_MODE_ARG, DIR: CMD_DIR_ARG)
158
+ options.on(
159
+ '-f',
160
+ '--force',
161
+ 'Forces file move operations'
162
+ ) { @data[:force] = true }
163
+ options.on(
164
+ '-v',
165
+ '--verbose',
166
+ 'Sets verbose mode on file operations'
167
+ ) { @data[:verbose] = true }
168
+ end
169
+
170
+ def execute(mode, dir)
171
+ case mode
172
+ when /^f.*/
173
+ AbideDevUtils::Ppt.fix_class_names_file_rename(dir, **@data)
174
+ when /^c.*/
175
+ AbideDevUtils::Ppt.fix_class_names_class_rename(dir, **@data)
176
+ else
177
+ raise ::ArgumentError, "Invalid mode. Mode:#{mode}"
178
+ end
179
+ end
180
+ end
181
+
182
+ class PuppetAuditClassNamesCommand < AbideCommand
183
+ CMD_NAME = 'audit-class-names'
184
+ CMD_SHORT = 'Finds Puppet classes in a directory that have names that do not match their path'
185
+ CMD_LONG = 'Finds Puppet classes in a directory that have names that do not match their path. This is helpful because class names that do not match their path structure break Puppet autoloading.'
186
+ CMD_DIR_ARG = 'The directory containing the Puppet class files'
187
+ def initialize
188
+ super(CMD_NAME, CMD_SHORT, CMD_LONG, takes_commands: false)
189
+ argument_desc(DIR: CMD_DIR_ARG)
190
+ options.on('-o [FILE]', '--out-file [FILE]', 'Save results to a file') { |f| @data[:file] = f }
191
+ options.on('-q', '--quiet', 'Do not print results to console') { @data[:quiet] = true }
192
+ end
193
+
194
+ def execute(dir)
195
+ if @data.fetch(:quiet, false) && !@data.key?(:file)
196
+ AbideDevUtils::Output.simple('ERROR: Specifying --quiet without --out-file is useless.', stream: $stderr)
197
+ exit 1
198
+ end
199
+
200
+ AbideDevUtils::Ppt.audit_class_names(dir, **@data)
201
+ end
202
+ end
203
+
204
+ class PuppetAddCISCommentCommand < AbideCommand
205
+ CMD_NAME = 'add-cis-comment'
206
+ CMD_SHORT = 'Adds the CIS recommendation name to the top of a .pp file'
207
+ CMD_LONG = 'Adds the CIS recommendation name to the top of a .pp file. Finds CIS recommendation by pattern-matching the class name against XCCDF recommendations.'
208
+ CMD_PATH_ARG = 'Path to a .pp file or to a directory containing .pp files'
209
+ CMD_XCCDF_ARG = 'Path to XCCDF file to source recommendation names from'
210
+ def initialize
211
+ super(CMD_NAME, CMD_SHORT, CMD_LONG, takes_commands: false)
212
+ argument_desc(PATH: CMD_PATH_ARG, XCCDF: CMD_XCCDF_ARG)
213
+ options.on('-N', '--number-format', 'Matches based on number-formatted control class names') { @data[:number_format] = true }
214
+ end
215
+
216
+ def execute(path, xccdf)
217
+ AbideDevUtils::Ppt.add_cis_comment(path, xccdf, number_format: @data.fetch(:number_format, false))
218
+ end
219
+ end
220
+
221
+ class PuppetScoreModuleCommand < AbideCommand
222
+ CMD_NAME = 'score'
223
+ CMD_SHORT = 'Scores a Puppet module just like Puppet Forge'
224
+ CMD_LONG = 'Scores a Puppet module just like Puppet Forge. This is a useful quality-check before publishing a module.'
225
+ def initialize
226
+ super(CMD_NAME, CMD_SHORT, CMD_LONG, takes_commands: false)
227
+ options.on('-o [PATH]', '--outfile [PATH]', 'Save results to a file') { |x| @data[:outfile] = x }
228
+ options.on('-q', '--quiet', FalseClass, 'Do not print results to console') { |x| @data[:quiet] = x }
229
+ options.on('-c', '--checks', Array, 'Comma-separated list of individual checks to run. Defaults to running all checks.') { |x| @data[:check] = x }
230
+ options.on('-m [PATH]', '--module [PATH]', 'Path to a Puppet module to score. Defaults to using the current directory.') { |x| @data[:module] = x }
231
+ end
232
+
233
+ def execute
234
+ module_path = @data.fetch(:module, nil)
235
+ AbideDevUtils::Ppt.score_module(module_path, **@data)
111
236
  end
112
237
  end
113
238
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'abide_dev_utils/cli/abstract'
3
4
  require 'abide_dev_utils/xccdf'
4
5
 
5
6
  module Abide
@@ -14,11 +15,12 @@ module Abide
14
15
  long_desc(CMD_LONG)
15
16
  add_command(CmdParse::HelpCommand.new, default: true)
16
17
  add_command(XccdfToHieraCommand.new)
18
+ add_command(XccdfDiffCommand.new)
17
19
  end
18
20
  end
19
21
 
20
22
  class XccdfToHieraCommand < CmdParse::Command
21
- CMD_NAME = 'to_hiera'
23
+ CMD_NAME = 'to-hiera'
22
24
  CMD_SHORT = 'Generates control coverage report'
23
25
  CMD_LONG = 'Generates report of valid Puppet classes that match with Hiera controls'
24
26
  def initialize
@@ -37,15 +39,32 @@ module Abide
37
39
 
38
40
  def execute(xccdf_file)
39
41
  @data[:type] = 'cis' if @data[:type].nil?
40
-
41
- to_hiera(xccdf_file)
42
+ hfile = AbideDevUtils::XCCDF.to_hiera(xccdf_file, @data)
43
+ AbideDevUtils::Output.yaml(hfile, console: @data[:file].nil?, file: @data[:file])
42
44
  end
45
+ end
43
46
 
44
- private
47
+ class XccdfDiffCommand < AbideCommand
48
+ CMD_NAME = 'diff'
49
+ CMD_SHORT = 'Generates a diff report between two XCCDF files'
50
+ CMD_LONG = 'Generates a diff report between two XCCDF files'
51
+ CMD_FILE1_ARG = 'path to first XCCDF file'
52
+ CMD_FILE2_ARG = 'path to second XCCDF file'
53
+ def initialize
54
+ super(CMD_NAME, CMD_SHORT, CMD_LONG, takes_commands: false)
55
+ argument_desc(FILE1: CMD_FILE1_ARG, FILE2: CMD_FILE2_ARG)
56
+ options.on('-o [PATH]', '--out-file', 'Save the report as a yaml file') { |x| @data[:outfile] = x }
57
+ options.on('-p [PROFILE]', '--profile', 'Only diff and specific profile in the benchmarks') do |x|
58
+ @data[:profile] = x
59
+ end
60
+ options.on('-q', '--quiet', 'Show no output in the terminal') { @data[:quiet] = false }
61
+ options.on('--no-diff-profiles', 'Do not diff the profiles in the XCCDF files') { @data[:diff_profiles] = false }
62
+ options.on('--no-diff-controls', 'Do not diff the controls in the XCCDF files') { @data[:diff_controls] = false }
63
+ end
45
64
 
46
- def to_hiera(xccdf_file)
47
- xfile = AbideDevUtils::XCCDF.to_hiera(xccdf_file, @data)
48
- Abide::CLI::OUTPUT.yaml(xfile, console: @data[:file].nil?, file: @data[:file])
65
+ def execute(file1, file2)
66
+ diffreport = AbideDevUtils::XCCDF.diff(file1, file2, @data)
67
+ AbideDevUtils::Output.yaml(diffreport, console: @data.fetch(:quiet, true), file: @data.fetch(:outfile, nil))
49
68
  end
50
69
  end
51
70
  end