diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c4f2666..9b49e1e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -30,6 +30,13 @@ repos: - id: check-added-large-files - id: mixed-line-ending + - repo: https://github.com/rubocop/rubocop + rev: v1.86.2 + hooks: + - id: rubocop + language_version: 3.4.7 + args: ['--autocorrect', '--force-exclusion'] + - repo: https://github.com/woodruffw/zizmor-pre-commit rev: v1.25.2 hooks: diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000..8631d72 --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,30 @@ +AllCops: + NewCops: enable + SuggestExtensions: false + TargetRubyVersion: 3.4 + +# Dockerfile_spec.rb filenames are intentional — they mirror the Dockerfile being tested +Naming/FileName: + Exclude: + - 'spec/**/*' + +# Not required for a test-only project +Style/FrozenStringLiteralComment: + Enabled: false + +# Spec helpers contain necessarily complex cleanup logic +Metrics/MethodLength: + Exclude: + - 'spec/**/*' + +Metrics/AbcSize: + Exclude: + - 'spec/**/*' + +Metrics/CyclomaticComplexity: + Exclude: + - 'spec/**/*' + +Metrics/PerceivedComplexity: + Exclude: + - 'spec/**/*' diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 0000000..7bcbb38 --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +3.4.9 diff --git a/Gemfile b/Gemfile index 813c4f7..24cb704 100644 --- a/Gemfile +++ b/Gemfile @@ -1,4 +1,4 @@ -source "https://rubygems.org" +source 'https://rubygems.org' gem 'docker-api', '~> 2.4' gem 'rake', '~> 13.4' diff --git a/Rakefile b/Rakefile index 80ba260..1134323 100644 --- a/Rakefile +++ b/Rakefile @@ -4,24 +4,25 @@ require 'rspec/core/rake_task' # Enable Docker BuildKit for faster builds with better caching ENV['DOCKER_BUILDKIT'] = '1' -task :spec => 'spec:all' -task :default => :spec +task spec: 'spec:all' +task default: :spec namespace :spec do targets = [] Dir.glob('./spec/*').each do |dir| next unless File.directory?(dir) + target = File.basename(dir) - target = "_#{target}" if target == "default" + target = "_#{target}" if target == 'default' targets << target end # Use multitask for parallel execution - multitask :all => targets - task :default => :all + multitask all: targets + task default: :all targets.each do |target| - original_target = target == "_default" ? target[1..-1] : target + original_target = target == '_default' ? target[1..] : target desc "Run serverspec tests to #{original_target}" task target.to_sym do # Run RSpec in a separate process with environment variable set diff --git a/spec/22/Dockerfile_spec.rb b/spec/22/Dockerfile_spec.rb index 7658ab3..ff45b45 100644 --- a/spec/22/Dockerfile_spec.rb +++ b/spec/22/Dockerfile_spec.rb @@ -3,21 +3,20 @@ require 'node_tests' require 'npm_tests' -tag = ENV['TARGET_HOST'] +tag = ENV.fetch('TARGET_HOST', nil) -describe "#{tag}" do +describe tag.to_s do include Helpers before(:all) do create_image(tag) end - test_node("22.22.0") + test_node('22.22.0') test_npm after(:all) do delete_image end - end diff --git a/spec/24/Dockerfile_spec.rb b/spec/24/Dockerfile_spec.rb index 73e96b0..1c1e6ce 100644 --- a/spec/24/Dockerfile_spec.rb +++ b/spec/24/Dockerfile_spec.rb @@ -3,21 +3,20 @@ require 'node_tests' require 'npm_tests' -tag = ENV['TARGET_HOST'] +tag = ENV.fetch('TARGET_HOST', nil) -describe "#{tag}" do +describe tag.to_s do include Helpers before(:all) do create_image(tag) end - test_node("24.13.0") + test_node('24.13.0') test_npm after(:all) do delete_image end - end diff --git a/spec/node_tests.rb b/spec/node_tests.rb index ec72cf5..2db35b1 100644 --- a/spec/node_tests.rb +++ b/spec/node_tests.rb @@ -3,6 +3,6 @@ def test_node(version) describe command('node -v') do its(:exit_status) { should eq 0 } - its(:stdout) { should match /#{version}/ } + its(:stdout) { should match(/#{version}/) } end end diff --git a/spec/npm_tests.rb b/spec/npm_tests.rb index 1efc430..4147ac0 100644 --- a/spec/npm_tests.rb +++ b/spec/npm_tests.rb @@ -13,7 +13,7 @@ def test_npm its(:exit_status) { should eq 0 } end - describe command("echo \'{\"foo\":\"bar\"}\' | json foo") do - its(:stdout) { should match /bar/ } + describe command("echo '{\"foo\":\"bar\"}' | json foo") do + its(:stdout) { should match(/bar/) } end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index a94442b..8a646fd 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,6 +1,6 @@ module Helpers def create_image(version) - puts "Building image..." + puts 'Building image...' begin @image = Docker::Image.build_from_dir("#{version}/") @@ -9,44 +9,44 @@ def create_image(version) raise end - set :os, :family => 'debian' + set :os, family: 'debian' set :backend, :docker set :docker_image, @image.id - puts "Running tests..." + puts 'Running tests...' end def delete_image return unless @image - puts "Deleting image..." + puts 'Deleting image...' # Remove specinfra's Docker backend finalizer before we clean up containers. # Without this, the finalizer fires at process exit and tries to stop/delete # containers we've already removed here, producing "Exception in finalizer" warnings. begin ObjectSpace.undefine_finalizer(Specinfra.backend) - rescue => e + rescue StandardError => e puts "Warning: Could not undefine specinfra finalizer: #{e.message}" end # Stop and remove only containers created from this image begin - Docker::Container.all(:all => true).each do |container| + Docker::Container.all(all: true).each do |container| container_image = container.info['Image'] container_image_id = container.info['ImageID'] # Match image IDs - container IDs may have sha256: prefix and be full hashes # while @image.id is the short ID - if container_image&.include?(@image.id) || container_image_id&.include?(@image.id) - begin - container.stop unless container.info['State'] == 'exited' - container.delete(:force => true) - rescue Docker::Error::NotModifiedError - # Container already stopped, ignore - rescue Docker::Error::DockerError => e - puts "Warning: Failed to cleanup container #{container.id[0..11]}: #{e.message}" - end + next unless container_image&.include?(@image.id) || container_image_id&.include?(@image.id) + + begin + container.stop unless container.info['State'] == 'exited' + container.delete(force: true) + rescue Docker::Error::NotModifiedError + # Container already stopped, ignore + rescue Docker::Error::DockerError => e + puts "Warning: Failed to cleanup container #{container.id[0..11]}: #{e.message}" end end rescue Docker::Error::DockerError => e @@ -55,10 +55,10 @@ def delete_image # Always attempt to remove the image, even if container cleanup failed begin - @image.remove(:force => true) - puts "Image removed successfully" + @image.remove(force: true) + puts 'Image removed successfully' rescue Docker::Error::NotFoundError - puts "Image already removed" + puts 'Image already removed' rescue Docker::Error::DockerError => e puts "Warning: Failed to remove image: #{e.message}" end