scelint 0.6.1 → 0.6.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a2a232f07d18ffa6a294a5c8664e5c32fed52810ece80035b6ddc385be96695c
4
- data.tar.gz: 1503e5f3c4a5e1a6c8c065b74b7c48c75efc5f081932e11f69a112c1b4bed08d
3
+ metadata.gz: 6b16cbfa48415fa9683d44daf2788ade982a90ab980ed6c5149139f2d17d8c78
4
+ data.tar.gz: 78bb451017798298bac2460b9e2ab20761f0d1757b8d40d39880943baa4cdbc5
5
5
  SHA512:
6
- metadata.gz: bfe6c7bf98cbdd1364cf230c91faeae76bbfd5e71d7d17ba0a74f48a77c46ea5fd64e3474fb6f6841e92202174e73e34ce995899db9a5a5e0a896be8688ecede
7
- data.tar.gz: c95d7b1e13e5b025374cb51e87494b8e8b25dd48b90df841bbfc482bef7687acbc5af2ddf99898962db3eda4e1a0e89ba1d4b3093b30b0a0e18efd5a954c875e
6
+ metadata.gz: 661faabd127dde36f197f2bac4fad65daea62994d53d63a0df412408909d39e3c9b50de55f3a18977268f1f7a69ad1475da7e78711316c3261e3624381f02235
7
+ data.tar.gz: c260920fb4aa2ac12e45e7ed77c7ead6f2d2a4ce51eee488f85ffac52e081ec59f5c4f6d2f25be96bf2665b76b62a9972e4ced9f8715e69eb9a04e1d28f4a848
@@ -1,6 +1,5 @@
1
- # Add new issues to triage project board (https://github.com/orgs/simp/projects/11)
1
+ # Add newly opened issues and pull requests to the Org Triage board
2
2
  # ------------------------------------------------------------------------------
3
- #
4
3
  # NOTICE: **This file is maintained with puppetsync**
5
4
  #
6
5
  # This file is updated automatically as part of a puppet module baseline.
@@ -8,19 +7,35 @@
8
7
  # The next baseline sync will overwrite any local changes to this file!
9
8
  #
10
9
  # ==============================================================================
11
- # This pipeline uses the following GitHub Action Secrets:
12
10
  #
13
- # GitHub Secret variable Notes
14
- # ------------------------------- ---------------------------------------
15
- # AUTO_TRIAGE_TOKEN Token with appropriate permissions
11
+ # Board: https://github.com/orgs/simp/projects/11
16
12
  #
17
- # ------------------------------------------------------------------------------
13
+ # Intake is filtered (simp/puppetsync#97): automated traffic never needs a
14
+ # human to triage it, and letting it in buried the items that do.
15
+ #
16
+ # * Renovate (and Dependabot, pre-emptively) are matched by actor.
17
+ # * puppetsync's own sync PRs are matched by the `[puppetsync]` title prefix.
18
+ # That prefix is a CONVENTION set by the `commit_message` in each sync
19
+ # config, not something enforced -- a config that drops it would silently
20
+ # start adding sync PRs to the board again.
21
+ #
22
+ # PRs in general are deliberately NOT filtered: drive-by PRs from outside
23
+ # contributors are exactly what the board must catch.
24
+ #
25
+ # This workflow does not check out any code, which is what makes its use of
26
+ # `pull_request_target` safe. Keep it that way.
18
27
  #
28
+ # Labelling (`community` for outside contributions, `needs-attention` for
29
+ # stalled items) is NOT done here: the event payload's `author_association`
30
+ # reports private org members as CONTRIBUTOR, so a per-repo step would mislabel
31
+ # most of the team. It lives in the scheduled org job
32
+ # simp/gha-workflows/.github/workflows/org_reconcile_board_status.yml, which
33
+ # resolves membership from the org member list (simp/puppetsync#98, #99).
19
34
  #
20
35
  ---
21
36
  name: Add new issues to triage project
22
37
 
23
- 'on':
38
+ on:
24
39
  issues:
25
40
  types:
26
41
  - opened
@@ -33,6 +48,14 @@ jobs:
33
48
  add-to-project:
34
49
  name: Add issue to project
35
50
  runs-on: ubuntu-latest
51
+ # `github.event.pull_request` is null on `issues` events, so the title test
52
+ # is guarded by event_name rather than relying on startsWith(null, ...)
53
+ # evaluating false.
54
+ if: >-
55
+ github.actor != 'renovate[bot]' &&
56
+ github.actor != 'dependabot[bot]' &&
57
+ !(github.event_name == 'pull_request_target' &&
58
+ startsWith(github.event.pull_request.title, '[puppetsync]'))
36
59
  steps:
37
60
  - uses: actions/add-to-project@v2
38
61
  with:
@@ -17,7 +17,7 @@ jobs:
17
17
  - name: "Install Ruby"
18
18
  uses: ruby/setup-ruby@v1
19
19
  with:
20
- ruby-version: 4.0.6
20
+ ruby-version: 4.0.9
21
21
  bundler-cache: true
22
22
  - run: bundle exec rake rubocop
23
23
 
@@ -41,3 +41,21 @@ jobs:
41
41
  bundler-cache: true
42
42
  - name: Spec tests
43
43
  run: bundle exec rake spec
44
+
45
+ # The one check for branch protection to require: GitHub only blocks a
46
+ # merge on checks listed by name, and this job fails unless every job it
47
+ # needs succeeded. Add any new job in this workflow to `needs:`.
48
+ all-checks:
49
+ name: 'All checks'
50
+ if: always()
51
+ needs:
52
+ - ruby-style
53
+ - spec-tests
54
+ runs-on: ubuntu-latest
55
+ steps:
56
+ - name: 'Fail unless every needed job succeeded'
57
+ env:
58
+ NEEDS: ${{ toJSON(needs) }}
59
+ run: |
60
+ jq -r 'to_entries[] | "\(.key): \(.value.result)"' <<< "$NEEDS"
61
+ jq -e 'all(.[]; .result == "success")' <<< "$NEEDS" > /dev/null
@@ -92,7 +92,7 @@ jobs:
92
92
  echo "release_command=$GEM_RELEASE_COMMAND" | tee -a "$GITHUB_OUTPUT"
93
93
  - uses: ruby/setup-ruby@v1
94
94
  with:
95
- ruby-version: 4.0.6
95
+ ruby-version: 4.0.9
96
96
  bundler-cache: true
97
97
  - name: Test build the package
98
98
  run: "${{ steps.commands.outputs.build_command }}"
@@ -187,7 +187,7 @@ jobs:
187
187
  clean: true
188
188
  - uses: ruby/setup-ruby@v1
189
189
  with:
190
- ruby-version: 4.0.6
190
+ ruby-version: 4.0.9
191
191
  bundler-cache: true
192
192
  - name: Build RubyGem
193
193
  run: |
data/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ ### 0.6.2 / 2026-11-02
2
+ * Report the file and check when data cannot be merged instead of crashing
3
+ * Report a `profiles`, `ce` or `checks` section that is not a hash, and a check's
4
+ `settings`, `ces` or remediation section of the wrong type, instead of crashing
5
+ * Report a CE's `imported_data` that is not a hash instead of crashing
6
+ * Exit with a non-zero status when linting fails with an exception
7
+
1
8
  ### 0.6.1 / 2026-08-20
2
9
  * Support compliance_engine 1.x
3
10
 
data/Gemfile CHANGED
@@ -7,8 +7,8 @@ gem 'rake', '~> 13.4.0'
7
7
 
8
8
  group :tests do
9
9
  gem 'rspec', '~> 3.13.1'
10
- gem 'rubocop', '~> 1.89.0'
11
- gem 'rubocop-performance', '~> 1.26.0'
10
+ gem 'rubocop', '~> 1.92.0'
11
+ gem 'rubocop-performance', '~> 1.27.0'
12
12
  gem 'rubocop-rake', '~> 0.7.1'
13
13
  gem 'rubocop-rspec', '~> 3.10.0'
14
14
  end
@@ -16,5 +16,5 @@ end
16
16
  group :development do
17
17
  gem 'pry', '~> 0.16.0'
18
18
  gem 'pry-byebug', '~> 3.12.0'
19
- gem 'rdoc', '~> 8.0.0'
19
+ gem 'rdoc', '~> 8.1.0'
20
20
  end
data/lib/scelint/cli.rb CHANGED
@@ -81,6 +81,8 @@ class Scelint::CLI < Thor
81
81
  exit exit_code
82
82
  rescue => e
83
83
  logger.fatal e.message
84
+ logger.debug e.backtrace.join("\n")
85
+ exit 1
84
86
  end
85
87
  default_task :lint
86
88
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Scelint
4
- VERSION = '0.6.1'
4
+ VERSION = '0.6.2'
5
5
  end
data/lib/scelint.rb CHANGED
@@ -314,6 +314,11 @@ module Scelint
314
314
  def check_imported_data(file, file_data)
315
315
  ok = ['checktext', 'fixtext']
316
316
 
317
+ unless file_data.is_a?(Hash)
318
+ warnings << "#{file}: bad imported_data '#{file_data}'"
319
+ return
320
+ end
321
+
317
322
  file_data.each do |key, value|
318
323
  warnings << "#{file}: unexpected key '#{key}'" unless ok.include?(key)
319
324
 
@@ -337,7 +342,22 @@ module Scelint
337
342
  'benchmark_version',
338
343
  ]
339
344
 
345
+ unless file_data.is_a?(Hash)
346
+ errors << "#{file}: 'profiles' must be a hash, got #{file_data.class}"
347
+ return
348
+ end
349
+
340
350
  file_data.each do |profile, value|
351
+ if value.nil?
352
+ warnings << "#{file} (profile '#{profile}'): empty value"
353
+ next
354
+ end
355
+
356
+ unless value.is_a?(Hash)
357
+ errors << "#{file} (profile '#{profile}'): contains something other than a hash"
358
+ next
359
+ end
360
+
341
361
  value.each_key do |key|
342
362
  warnings << "#{file} (profile '#{profile}'): unexpected key '#{key}'" unless ok.include?(key)
343
363
  end
@@ -367,7 +387,22 @@ module Scelint
367
387
  'notes',
368
388
  ]
369
389
 
390
+ unless file_data.is_a?(Hash)
391
+ errors << "#{file}: 'ce' must be a hash, got #{file_data.class}"
392
+ return
393
+ end
394
+
370
395
  file_data.each do |ce, value|
396
+ if value.nil?
397
+ warnings << "#{file} (CE '#{ce}'): empty value"
398
+ next
399
+ end
400
+
401
+ unless value.is_a?(Hash)
402
+ errors << "#{file} (CE '#{ce}'): contains something other than a hash"
403
+ next
404
+ end
405
+
371
406
  value.each_key do |key|
372
407
  warnings << "#{file} (CE '#{ce}'): unexpected key '#{key}'" unless ok.include?(key)
373
408
  end
@@ -445,6 +480,11 @@ module Scelint
445
480
 
446
481
  if remediation_section.is_a?(Hash)
447
482
  remediation_section.each do |section, value|
483
+ if ['scan-false-positive', 'disabled', 'risk'].include?(section) && !value.is_a?(Array)
484
+ errors << "#{file} (check '#{check}'): malformed remediation section #{section}, expecting an array."
485
+ next
486
+ end
487
+
448
488
  case section
449
489
  when 'scan-false-positive', 'disabled'
450
490
  value.each do |reason|
@@ -512,6 +552,16 @@ module Scelint
512
552
  return false
513
553
  end
514
554
 
555
+ unless file_data.is_a?(Hash)
556
+ msg = "#{file} (check '#{check}'): settings must be a hash, got #{file_data.class}"
557
+ if file == 'merged data'
558
+ errors << msg
559
+ else
560
+ warnings << msg
561
+ end
562
+ return false
563
+ end
564
+
515
565
  if file_data.key?('parameter')
516
566
  check_parameter(file, check, file_data['parameter'])
517
567
  else
@@ -542,12 +592,16 @@ module Scelint
542
592
  # Check CEs in a check
543
593
  #
544
594
  # @param file [String] The path to the file being checked
595
+ # @param check [String] The name of the check
545
596
  # @param file_data [Array] The data to validate
546
- def check_check_ces(file, file_data)
547
- warnings << "#{file}: bad ces '#{file_data}'" unless file_data.is_a?(Array)
597
+ def check_check_ces(file, check, file_data)
598
+ unless file_data.is_a?(Array)
599
+ warnings << "#{file} (check '#{check}'): bad ces '#{file_data}'"
600
+ return
601
+ end
548
602
 
549
603
  file_data.each do |key|
550
- warnings << "#{file}: bad ce '#{key}'" unless key.is_a?(String)
604
+ warnings << "#{file} (check '#{check}'): bad ce '#{key}'" unless key.is_a?(String)
551
605
  end
552
606
  end
553
607
 
@@ -567,6 +621,11 @@ module Scelint
567
621
  'remediation',
568
622
  ]
569
623
 
624
+ unless file_data.is_a?(Hash)
625
+ errors << "#{file}: 'checks' must be a hash, got #{file_data.class}"
626
+ return
627
+ end
628
+
570
629
  file_data.each do |check, value|
571
630
  if value.nil?
572
631
  warnings << "#{file} (check '#{check}'): empty value"
@@ -579,6 +638,7 @@ module Scelint
579
638
  end
580
639
  else
581
640
  errors << "#{file} (check '#{check}'): contains something other than a hash, this is most likely caused by a missing note or ce element under the check"
641
+ next
582
642
  end
583
643
 
584
644
  check_type(file, check, value['type']) if value['type'] || file == 'merged data'
@@ -589,7 +649,7 @@ module Scelint
589
649
  check_controls(file, value['controls']) unless value['controls'].nil?
590
650
  check_identifiers(file, value['identifiers']) unless value['identifiers'].nil?
591
651
  check_oval_ids(file, value['oval-ids']) unless value['oval-ids'].nil?
592
- check_check_ces(file, value['ces']) unless value['ces'].nil?
652
+ check_check_ces(file, check, value['ces']) unless value['ces'].nil?
593
653
  check_confine(file, value['confine']) unless value['confine'].nil?
594
654
  end
595
655
  end
@@ -672,7 +732,12 @@ module Scelint
672
732
 
673
733
  # Unconfined, verify that hiera data exists
674
734
  data.profiles.each_key do |profile|
675
- hiera = data.hiera([profile])
735
+ begin
736
+ hiera = data.hiera([profile])
737
+ rescue => e
738
+ errors << "Profile '#{profile}': unable to render Hiera data (#{e.message})"
739
+ next
740
+ end
676
741
  if hiera.nil?
677
742
  errors << "Profile '#{profile}': Invalid Hiera data (returned nil)"
678
743
  next
@@ -687,8 +752,20 @@ module Scelint
687
752
  # Again, this time confined
688
753
  confines.each do |confine|
689
754
  data.facts = confine
690
- data.profiles.select { |_, value| value.ces&.count&.positive? || value.controls&.count&.positive? }.each_key do |profile|
691
- hiera = data.hiera([profile])
755
+ eligible = data.profiles.select do |profile, value|
756
+ value.ces&.count&.positive? || value.controls&.count&.positive?
757
+ rescue => e
758
+ errors << "Profile '#{profile}': unable to render Hiera data (#{e.message}) with facts #{confine}"
759
+ false
760
+ end
761
+
762
+ eligible.each_key do |profile|
763
+ begin
764
+ hiera = data.hiera([profile])
765
+ rescue => e
766
+ errors << "Profile '#{profile}': unable to render Hiera data (#{e.message}) with facts #{confine}"
767
+ next
768
+ end
692
769
  if hiera.nil?
693
770
  errors << "Profile '#{profile}': Invalid Hiera data (returned nil) with facts #{confine}"
694
771
  next
@@ -728,18 +805,33 @@ module Scelint
728
805
 
729
806
  # Merge a ComplianceEngine::Collection object into a Hash
730
807
  #
808
+ # Components that cannot be merged (usually because a fragment is
809
+ # malformed) are reported as errors and left out of the result rather
810
+ # than aborting the entire lint run.
811
+ #
731
812
  # @param collection [ComplianceEngine::Collection] A collection object
813
+ # @param type [String] The type of component in the collection, used in error messages
732
814
  # @return [Hash] The merged data
733
- def merge(collection)
734
- collection.to_h.reduce({}) { |result, value| result.merge!(value[0] => value[1].to_h) }
815
+ def merge(collection, type)
816
+ collection.to_h.each_with_object({}) do |(key, value), result|
817
+ result[key] = value.to_h
818
+ rescue => e
819
+ errors << "merged data: unable to merge #{type} '#{key}': #{e.message}"
820
+ end
735
821
  end
736
822
 
737
823
  # Perform lint checks on merged data
824
+ #
825
+ # @note Unlike #lint, this has no file to attribute a failure to, so any
826
+ # unexpected exception is reported against 'merged data' rather than
827
+ # escaping to the caller.
738
828
  def merged_data_lint
739
- check_profiles('merged data', merge(data.profiles))
740
- check_ce('merged data', merge(data.ces))
741
- check_checks('merged data', merge(data.checks))
742
- check_controls('merged data', merge(data.controls))
829
+ check_profiles('merged data', merge(data.profiles, 'profile'))
830
+ check_ce('merged data', merge(data.ces, 'CE'))
831
+ check_checks('merged data', merge(data.checks, 'check'))
832
+ check_controls('merged data', merge(data.controls, 'control'))
833
+ rescue => e
834
+ errors << "merged data: #{e.message}"
743
835
  end
744
836
  end
745
837
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: scelint
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.1
4
+ version: 0.6.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Steven Pritchard
@@ -94,7 +94,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
94
94
  - !ruby/object:Gem::Version
95
95
  version: '0'
96
96
  requirements: []
97
- rubygems_version: 4.0.16
97
+ rubygems_version: 4.0.20
98
98
  specification_version: 4
99
99
  summary: Linter SIMP Compliance Engine data
100
100
  test_files: []