From 7440720f6408ebdcdc3bd4b30f2301e14b0f41bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Crist=C3=B3bal=20Badilla=20C?= Date: Wed, 8 Oct 2025 15:14:55 -0300 Subject: [PATCH 1/6] Update fpm gem version to 1.15.1 in Gemfile and Gemfile.lock --- Gemfile | 2 +- Gemfile.lock | 38 +++++++++++++------------------------- 2 files changed, 14 insertions(+), 26 deletions(-) diff --git a/Gemfile b/Gemfile index e56f498..226254e 100644 --- a/Gemfile +++ b/Gemfile @@ -1,6 +1,6 @@ source 'http://rubygems.org' gem 'aws-sdk-v1', '1.64' -gem 'fpm', '1.12.0' +gem 'fpm', '1.15.1' gem 'sinatra', '1.4.6' gem 'nokogiri', '1.10.0' group :test do diff --git a/Gemfile.lock b/Gemfile.lock index f0cc5b3..1b905a5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,53 +1,42 @@ GEM remote: http://rubygems.org/ specs: - arr-pm (0.0.10) - cabin (> 0) + arr-pm (0.0.12) aws-sdk-v1 (1.64.0) json (~> 1.4) nokogiri (>= 1.4.4) - backports (3.21.0) - cabin (0.9.0) - childprocess (0.9.0) - ffi (~> 1.0, >= 1.0.11) + backports (3.25.2) + cabin (0.9.1) clamp (1.0.1) diff-lcs (1.4.4) - dotenv (2.7.6) - ffi (1.12.2) - fpm (1.12.0) - arr-pm (~> 0.0.10) + dotenv (2.8.1) + fpm (1.15.1) + arr-pm (~> 0.0.11) backports (>= 2.6.2) cabin (>= 0.6.0) - childprocess (< 1.0.0) clamp (~> 1.0.0) - ffi (~> 1.12.0) - git (>= 1.3.0, < 2.0) - json (>= 1.7.7, < 3.0) pleaserun (~> 0.0.29) - ruby-xz (~> 0.2.3) + rexml stud - git (1.8.1) - rchardet (~> 1.8) given_core (3.8.2) sorcerer (>= 0.3.7) insist (1.0.0) - io-like (0.3.1) json (1.8.6) mini_portile2 (2.4.0) mustache (0.99.8) nokogiri (1.10.0) mini_portile2 (~> 2.4.0) - pleaserun (0.0.32) + pleaserun (0.0.33) cabin (> 0) clamp - dotenv + dotenv (~> 2) insist mustache (= 0.99.8) stud rack (1.6.13) rack-protection (1.5.5) rack - rchardet (1.8.0) + rexml (3.4.4) rspec (3.10.0) rspec-core (~> 3.10.0) rspec-expectations (~> 3.10.0) @@ -64,9 +53,6 @@ GEM diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.10.0) rspec-support (3.10.2) - ruby-xz (0.2.3) - ffi (~> 1.9) - io-like (~> 0.3) sinatra (1.4.6) rack (~> 1.4) rack-protection (~> 1.4) @@ -76,11 +62,13 @@ GEM tilt (2.0.10) PLATFORMS + -darwin-23 + -darwin-24 x86_64-linux DEPENDENCIES aws-sdk-v1 (= 1.64) - fpm (= 1.12.0) + fpm (= 1.15.1) nokogiri (= 1.10.0) rspec rspec-given From 65448b3b0ac1d31cb55d3e9f9abdf4074f8ac48f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Crist=C3=B3bal=20Badilla=20C?= Date: Wed, 8 Oct 2025 15:24:05 -0300 Subject: [PATCH 2/6] Add tests for Ploy::Command::Bless, Ploy::Command::Build, and Ploy::MetaOracle; enhance Ploy::LocalPackage::DebBuilder and Ploy::S3Storage specs --- spec/command_bless_spec.rb | 76 +++++++++++++++++++++ spec/command_build_spec.rb | 54 +++++++++++++++ spec/debbuilder_spec.rb | 84 ++++++++++++++++++++++++ spec/metaoracle_spec.rb | 45 ++++++++++++- spec/package_spec.rb | 19 ++++++ spec/resources/conf/some-project.service | 11 ++++ spec/s3storage_spec.rb | 73 ++++++++++++++++++++ 7 files changed, 360 insertions(+), 2 deletions(-) create mode 100644 spec/command_bless_spec.rb create mode 100644 spec/command_build_spec.rb create mode 100644 spec/resources/conf/some-project.service diff --git a/spec/command_bless_spec.rb b/spec/command_bless_spec.rb new file mode 100644 index 0000000..6e2dee0 --- /dev/null +++ b/spec/command_bless_spec.rb @@ -0,0 +1,76 @@ +require 'rspec/given' +require 'ploy/command/bless' + +describe Ploy::Command::Bless do + Given(:cmd) { Ploy::Command::Bless.new } + Given(:mock_package) { double('package') } + Given(:mock_blessed) { double('blessed_package') } + + context "blessing by deployment/branch/version" do + Given do + allow(Ploy::Package).to receive(:new).with('test-bucket', 'test-deploy', 'master', 'abc123').and_return(mock_package) + allow(mock_package).to receive(:bless).with('blessed').and_return(mock_blessed) + allow(mock_package).to receive(:deploy_name).and_return('test-deploy') + allow(mock_package).to receive(:branch).and_return('master') + allow(mock_package).to receive(:version).and_return('abc123') + allow(mock_blessed).to receive(:make_current) + end + + When(:result) { cmd.run(['-b', 'test-bucket', '-d', 'test-deploy', '-B', 'master', '-v', 'abc123']) } + + Then { result.nil? || result.is_a?(Array) } + And { expect(mock_package).to have_received(:bless).with('blessed') } + And { expect(mock_blessed).to have_received(:make_current) } + end + + context "blessing with custom variant" do + Given do + allow(Ploy::Package).to receive(:new).with('test-bucket', 'test-deploy', 'master', 'abc123').and_return(mock_package) + allow(mock_package).to receive(:bless).with('staging').and_return(mock_blessed) + allow(mock_package).to receive(:deploy_name).and_return('test-deploy') + allow(mock_package).to receive(:branch).and_return('master') + allow(mock_package).to receive(:version).and_return('abc123') + allow(mock_blessed).to receive(:make_current) + end + + When(:result) { cmd.run(['-b', 'test-bucket', '-d', 'test-deploy', '-B', 'master', '-v', 'abc123', '--variant', 'staging']) } + + Then { result.nil? || result.is_a?(Array) } + And { expect(mock_package).to have_received(:bless).with('staging') } + end + + context "blessing from JSON file" do + Given(:json_file) { 'spec/resources/bless_data.json' } + Given(:json_data) do + { + 'package1' => {'name' => 'pkg1', 'branch' => 'master', 'sha' => 'sha1', 'variant' => nil}, + 'package2' => {'name' => 'pkg2', 'branch' => 'feature', 'sha' => 'sha2', 'variant' => nil} + } + end + Given(:mock_package2) { double('package2') } + Given(:mock_blessed2) { double('blessed_package2') } + + Given do + allow(File).to receive(:read).with(json_file).and_return(json_data.to_json) + allow(Ploy::Package).to receive(:from_metadata).with('test-bucket', json_data).and_return([mock_package, mock_package2]) + allow(mock_package).to receive(:bless).with('blessed').and_return(mock_blessed) + allow(mock_package).to receive(:deploy_name).and_return('pkg1') + allow(mock_package).to receive(:branch).and_return('master') + allow(mock_package).to receive(:version).and_return('sha1') + allow(mock_blessed).to receive(:make_current) + allow(mock_package2).to receive(:bless).with('blessed').and_return(mock_blessed2) + allow(mock_package2).to receive(:deploy_name).and_return('pkg2') + allow(mock_package2).to receive(:branch).and_return('feature') + allow(mock_package2).to receive(:version).and_return('sha2') + allow(mock_blessed2).to receive(:make_current) + end + + When(:result) { cmd.run(['-b', 'test-bucket', '-f', json_file]) } + + Then { result.nil? || result.is_a?(Array) } + And { expect(mock_package).to have_received(:bless).with('blessed') } + And { expect(mock_package2).to have_received(:bless).with('blessed') } + And { expect(mock_blessed).to have_received(:make_current) } + And { expect(mock_blessed2).to have_received(:make_current) } + end +end diff --git a/spec/command_build_spec.rb b/spec/command_build_spec.rb new file mode 100644 index 0000000..67ecc40 --- /dev/null +++ b/spec/command_build_spec.rb @@ -0,0 +1,54 @@ +require 'rspec/given' +require 'ploy/command/build' + +describe Ploy::Command::Build do + Given(:cmd) { Ploy::Command::Build.new } + Given(:mock_config) { double('config') } + Given(:mock_builder) { double('builder') } + + context "building with default config file" do + Given do + allow(Ploy::LocalPackage::Config).to receive(:load).with('.ploy-publisher.yml').and_return([mock_config]) + allow(mock_config).to receive(:builder).and_return(mock_builder) + allow(mock_builder).to receive(:build_deb).and_return('/tmp/package.deb') + end + + When(:result) { cmd.run([]) } + + Then { result == true } + And { expect(Ploy::LocalPackage::Config).to have_received(:load).with('.ploy-publisher.yml') } + And { expect(mock_builder).to have_received(:build_deb) } + end + + context "building with custom config file" do + Given do + allow(Ploy::LocalPackage::Config).to receive(:load).with('custom.yml').and_return([mock_config]) + allow(mock_config).to receive(:builder).and_return(mock_builder) + allow(mock_builder).to receive(:build_deb).and_return('/tmp/package.deb') + end + + When(:result) { cmd.run(['custom.yml']) } + + Then { result == true } + And { expect(Ploy::LocalPackage::Config).to have_received(:load).with('custom.yml') } + end + + context "building multiple packages" do + Given(:mock_config2) { double('config2') } + Given(:mock_builder2) { double('builder2') } + + Given do + allow(Ploy::LocalPackage::Config).to receive(:load).with('.ploy-publisher.yml').and_return([mock_config, mock_config2]) + allow(mock_config).to receive(:builder).and_return(mock_builder) + allow(mock_builder).to receive(:build_deb).and_return('/tmp/package1.deb') + allow(mock_config2).to receive(:builder).and_return(mock_builder2) + allow(mock_builder2).to receive(:build_deb).and_return('/tmp/package2.deb') + end + + When(:result) { cmd.run([]) } + + Then { result == true } + And { expect(mock_builder).to have_received(:build_deb) } + And { expect(mock_builder2).to have_received(:build_deb) } + end +end diff --git a/spec/debbuilder_spec.rb b/spec/debbuilder_spec.rb index 221b26f..2fc2136 100644 --- a/spec/debbuilder_spec.rb +++ b/spec/debbuilder_spec.rb @@ -86,4 +86,88 @@ it_behaves_like "basic deb" end end + + context "with systemd files" do + Given(:db) do + Ploy::LocalPackage::DebBuilder.new( + :name => 'some-project', + :sha => 'fakesha', + :branch => 'fakebranch', + :timestamp => '123456', + :systemd_files => ['spec/resources/conf/some-project.service'], + :mnt_log_path => '/mnt/some-project-logs', + :dist_dir => 'spec/resources/dist', + :prefix => '/usr/local/someproject', + :postinst => 'systemdpostinst' + ) + end + + context "building a deb file with systemd" do + When(:filename) { cur_f = db.build_deb; cur_f } + Then { File.exists? filename } + And { `dpkg-deb -f #{filename} Version`.chomp == '123456.fakebranch' } + And { `dpkg-deb -c #{filename}` =~ / \.\/usr\/local\/someproject\/file.txt\n/ } + And { `dpkg-deb -c #{filename}` =~ / \.\/lib\/systemd\/system\/some-project.service\n/ } + And { `dpkg-deb -c #{filename}` =~ / \.\/etc\/ploy\/metadata.d\/some-project.yml\n/ } + And { `dpkg-deb -f #{filename} gitrev`.chomp == 'fakesha' } + And { `dpkg-deb -I #{filename} postinst` =~ /^#!\/bin\/bash/ } + And { `dpkg-deb -I #{filename} postinst` =~ /systemdpostinst/ } + And { `dpkg-deb -I #{filename} postinst` =~ /mkdir -p \/mnt\/some-project-logs/ } + And { `dpkg-deb -I #{filename} postinst` !~ /check_upstart_service/ } + after(:all) do + File.delete(cur_f) + end + end + end + + context "with systemd files and default mnt_log_path" do + Given(:db) do + Ploy::LocalPackage::DebBuilder.new( + :name => 'some-project', + :sha => 'fakesha', + :branch => 'fakebranch', + :timestamp => '123456', + :systemd_files => ['spec/resources/conf/some-project.service'], + :dist_dir => 'spec/resources/dist', + :prefix => '/usr/local/someproject', + :postinst => 'systemdpostinst' + ) + end + + context "building a deb file with systemd and default log path" do + When(:filename) { cur_f = db.build_deb; cur_f } + Then { File.exists? filename } + And { `dpkg-deb -I #{filename} postinst` =~ /mkdir -p \/mnt\/some-project.service/ } + after(:all) do + File.delete(cur_f) + end + end + end + + context "with both upstart and systemd files" do + Given(:db) do + Ploy::LocalPackage::DebBuilder.new( + :name => 'some-project', + :sha => 'fakesha', + :branch => 'fakebranch', + :timestamp => '123456', + :upstart_files => ['spec/resources/conf/some-project-initfile'], + :systemd_files => ['spec/resources/conf/some-project.service'], + :dist_dir => 'spec/resources/dist', + :prefix => '/usr/local/someproject', + :postinst => 'bothpostinst' + ) + end + + context "building a deb file with both init systems prefers systemd" do + When(:filename) { cur_f = db.build_deb; cur_f } + Then { File.exists? filename } + And { `dpkg-deb -c #{filename}` =~ / \.\/lib\/systemd\/system\/some-project.service\n/ } + And { `dpkg-deb -c #{filename}` !~ / \.\/etc\/init\/some-project-initfile.conf\n/ } + And { `dpkg-deb -I #{filename} postinst` !~ /check_upstart_service/ } + after(:all) do + File.delete(cur_f) + end + end + end end diff --git a/spec/metaoracle_spec.rb b/spec/metaoracle_spec.rb index ff31b00..097b032 100644 --- a/spec/metaoracle_spec.rb +++ b/spec/metaoracle_spec.rb @@ -1,4 +1,5 @@ require 'ploy/metaoracle' +require 'json' describe Ploy::MetaOracle do it 'can be initialized' do @@ -6,11 +7,43 @@ end describe '#query' do - # no interesting tests yet + it 'queries all instances in stack' do + instance1 = double('instance1') + instance2 = double('instance2') + allow(instance1).to receive(:private_ip_address).and_return('1.1.1.1') + allow(instance2).to receive(:private_ip_address).and_return('2.2.2.2') + + ec2 = double('ec2') + instances = double('instances') + allow(AWS::EC2).to receive(:new).and_return(ec2) + allow(ec2).to receive(:instances).and_return(instances) + allow(instances).to receive(:tagged_values).with('test-stack').and_return([instance1, instance2]) + + oracle = Ploy::MetaOracle.new('test-stack') + allow(oracle).to receive(:meta).with(instance1).and_return({'pkg1' => {'version' => 'v1'}}) + allow(oracle).to receive(:meta).with(instance2).and_return({'pkg2' => {'version' => 'v2'}}) + + result = oracle.query + expect(result).to eq({ + '1.1.1.1' => {'pkg1' => {'version' => 'v1'}}, + '2.2.2.2' => {'pkg2' => {'version' => 'v2'}} + }) + end end describe '#meta' do - # no interesting tests yet + it 'fetches and parses JSON from instance' do + instance = double('instance') + allow(instance).to receive(:private_ip_address).and_return('1.1.1.1') + + json_response = {'package1' => {'version' => 'abc123'}}.to_json + allow(Net::HTTP).to receive(:get).and_return(json_response) + + oracle = Ploy::MetaOracle.new('test-stack') + result = oracle.meta(instance) + + expect(result).to eq({'package1' => {'version' => 'abc123'}}) + end end describe '#oracle_uri' do @@ -22,6 +55,14 @@ expect(r).to be_a(URI) expect(r.to_s).to eq('http://1.1.1.1:9876/') end + + it "uses port 9876" do + instance = double('instance') + allow(instance).to receive(:private_ip_address).and_return('10.0.0.5') + + uri = Ploy::MetaOracle.new("stack").oracle_uri(instance) + expect(uri.port).to eq(9876) + end end end diff --git a/spec/package_spec.rb b/spec/package_spec.rb index c014e97..6756700 100644 --- a/spec/package_spec.rb +++ b/spec/package_spec.rb @@ -43,6 +43,25 @@ ) @inst.bless end + + it "returns a blessed package with correct attributes" do + Ploy::S3Storage.any_instance.stub(:copy) + blessed = @inst.bless + expect(blessed).to be_a(Ploy::Package) + expect(blessed.deploy_name).to eq("some-project") + expect(blessed.branch).to eq("master") + expect(blessed.version).to eq("current") + expect(blessed.variant).to eq("blessed") + end + + it "supports custom variants" do + Ploy::S3Storage.any_instance.should_receive(:copy).with( + Ploy::Util.remote_name("some-project", "master", "current"), + Ploy::Util.remote_name("some-project", "master", "current", "staging") + ) + blessed = @inst.bless("staging") + expect(blessed.variant).to eq("staging") + end end describe "#upload" do diff --git a/spec/resources/conf/some-project.service b/spec/resources/conf/some-project.service new file mode 100644 index 0000000..773a099 --- /dev/null +++ b/spec/resources/conf/some-project.service @@ -0,0 +1,11 @@ +[Unit] +Description=Some Project Service +After=network.target + +[Service] +Type=simple +ExecStart=/usr/local/someproject/start.sh +Restart=on-failure + +[Install] +WantedBy=multi-user.target diff --git a/spec/s3storage_spec.rb b/spec/s3storage_spec.rb index 2645b74..d023127 100644 --- a/spec/s3storage_spec.rb +++ b/spec/s3storage_spec.rb @@ -60,6 +60,79 @@ end describe "#read" do + it "reads content from S3 object" do + from = "a/b/c.txt" + content = "file content" + + object = double("object") + object.should_receive(:read).and_return(content) + + objects = double("objects") + objects.should_receive(:[]).with(from).and_return(object) + + bucket = double("bucket") + bucket.stub(:objects).and_return(objects) + + buckets = double("buckets") + buckets.should_receive(:[]).with("testbucket").and_return(bucket) + + s3 = double("s3") + s3.should_receive(:buckets).and_return(buckets) + AWS::S3.stub(:new).and_return(s3) + + result = @storage.read(from) + expect(result).to eq(content) + end + end + + describe "#metadata" do + it "returns metadata when object exists" do + location = "some/path.deb" + meta = {'git_revision' => 'abc123', 'custom_field' => 'value'} + + object = double("object") + object.should_receive(:exists?).and_return(true) + object.should_receive(:metadata).and_return(meta) + + objects = double("objects") + objects.should_receive(:[]).with(location).and_return(object) + + bucket = double("bucket") + bucket.stub(:objects).and_return(objects) + + buckets = double("buckets") + buckets.should_receive(:[]).with("testbucket").and_return(bucket) + + s3 = double("s3") + s3.should_receive(:buckets).and_return(buckets) + AWS::S3.stub(:new).and_return(s3) + + result = @storage.metadata(location) + expect(result).to eq(meta) + end + + it "returns empty hash when object does not exist" do + location = "nonexistent/path.deb" + + object = double("object") + object.should_receive(:exists?).and_return(false) + + objects = double("objects") + objects.should_receive(:[]).with(location).and_return(object) + + bucket = double("bucket") + bucket.stub(:objects).and_return(objects) + + buckets = double("buckets") + buckets.should_receive(:[]).with("testbucket").and_return(bucket) + + s3 = double("s3") + s3.should_receive(:buckets).and_return(buckets) + AWS::S3.stub(:new).and_return(s3) + + result = @storage.metadata(location) + expect(result).to eq({}) + end end describe "#get" do From 9e4a38b35695a95b56384c7517522ead05979603 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Crist=C3=B3bal=20Badilla=20C?= Date: Wed, 8 Oct 2025 17:21:13 -0300 Subject: [PATCH 3/6] Update Ruby version to 2.7.8, adjust Nokogiri dependency, and add gemspec for ploy-2.7.8 --- .ruby-version | 1 + .travis.yml | 2 +- Gemfile | 2 +- Gemfile.lock | 13 +++++++++---- README.md | 6 +++--- ploy.gemspec => ploy-2.7.8.gemspec | 10 ++++++---- 6 files changed, 21 insertions(+), 13 deletions(-) create mode 100644 .ruby-version rename ploy.gemspec => ploy-2.7.8.gemspec (67%) diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 0000000..6a81b4c --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +2.7.8 diff --git a/.travis.yml b/.travis.yml index 2579367..42d6a61 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: ruby rvm: -- 2.4.4 +- 2.7.8 cache: - bundler script: bundle exec rspec spec diff --git a/Gemfile b/Gemfile index 226254e..9576984 100644 --- a/Gemfile +++ b/Gemfile @@ -2,7 +2,7 @@ source 'http://rubygems.org' gem 'aws-sdk-v1', '1.64' gem 'fpm', '1.15.1' gem 'sinatra', '1.4.6' -gem 'nokogiri', '1.10.0' +gem 'nokogiri', '~> 1.13.0' group :test do gem 'rspec' gem 'rspec-given' diff --git a/Gemfile.lock b/Gemfile.lock index 1b905a5..6324942 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -22,10 +22,13 @@ GEM sorcerer (>= 0.3.7) insist (1.0.0) json (1.8.6) - mini_portile2 (2.4.0) + mini_portile2 (2.8.9) mustache (0.99.8) - nokogiri (1.10.0) - mini_portile2 (~> 2.4.0) + nokogiri (1.13.10) + mini_portile2 (~> 2.8.0) + racc (~> 1.4) + nokogiri (1.13.10-x86_64-linux) + racc (~> 1.4) pleaserun (0.0.33) cabin (> 0) clamp @@ -33,6 +36,7 @@ GEM insist mustache (= 0.99.8) stud + racc (1.8.1) rack (1.6.13) rack-protection (1.5.5) rack @@ -64,12 +68,13 @@ GEM PLATFORMS -darwin-23 -darwin-24 + ruby x86_64-linux DEPENDENCIES aws-sdk-v1 (= 1.64) fpm (= 1.15.1) - nokogiri (= 1.10.0) + nokogiri (~> 1.13.0) rspec rspec-given sinatra (= 1.4.6) diff --git a/README.md b/README.md index f7b596d..897f197 100644 --- a/README.md +++ b/README.md @@ -122,12 +122,12 @@ $ gem build ploy.gemspec ## Requirements - - ruby 1.9+ - - fpm + - ruby 2.7.8+ + - fpm 1.15.1+ - rsync - dpkg (for tests) -Known to work on OSX and Ubuntu. +Known to work on macOS and Ubuntu. ### Legal diff --git a/ploy.gemspec b/ploy-2.7.8.gemspec similarity index 67% rename from ploy.gemspec rename to ploy-2.7.8.gemspec index fb1ab95..7fd1a51 100644 --- a/ploy.gemspec +++ b/ploy-2.7.8.gemspec @@ -1,14 +1,16 @@ Gem::Specification.new do |s| - s.name = 'ploy' - s.version = '0.0.42' - s.date = '2021-05-17' + s.name = 'ploy-2.7.8' + s.version = '0.0.43' + s.date = '2025-10-08' s.summary = 'Multi-phase deployment tool' s.description = 'Multi-phase deployment tool for use in a continuous deployment environment.' s.authors = ["Michael Bruce", "Brian J. Schrock", "Dustin Watson"] s.email = 'mbruce@manta.com' s.files += Dir['lib/**/*.rb'] + s.required_ruby_version = '>= 2.7.8' s.add_runtime_dependency 'aws-sdk-v1', '1.64' - s.add_runtime_dependency 'fpm', '1.12.0' + s.add_runtime_dependency 'fpm', '1.15.1' s.add_runtime_dependency 'sinatra', '1.4.6' + s.add_runtime_dependency 'nokogiri', '~> 1.13.0' s.executables << 'ploy' end From 07c9d3fbab4056b6f1e7fc95f77fcb653a70d237 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Crist=C3=B3bal=20Badilla=20C?= Date: Tue, 21 Oct 2025 14:47:01 -0300 Subject: [PATCH 4/6] Update Ruby version to 3.1.6, adjust dependencies, and refactor AWS SDK usage --- .ruby-version | 2 +- .travis.yml | 2 +- Gemfile | 12 ++-- Gemfile.lock | 97 ++++++++++++++++----------- lib/ploy/command/oracle.rb | 4 +- lib/ploy/metaoracle.rb | 5 +- lib/ploy/packageset.rb | 2 + lib/ploy/s3storage.rb | 44 ++++++------ ploy-3.1.6.gemspec | 17 +++++ spec/metaoracle_spec.rb | 9 +-- spec/packageset_spec.rb | 8 +++ spec/s3storage_spec.rb | 134 ++++++++++++++----------------------- 12 files changed, 180 insertions(+), 156 deletions(-) create mode 100644 ploy-3.1.6.gemspec diff --git a/.ruby-version b/.ruby-version index 6a81b4c..9cec716 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -2.7.8 +3.1.6 diff --git a/.travis.yml b/.travis.yml index 42d6a61..4c8696e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: ruby rvm: -- 2.7.8 +- 3.1.6 cache: - bundler script: bundle exec rspec spec diff --git a/Gemfile b/Gemfile index 9576984..6bffc12 100644 --- a/Gemfile +++ b/Gemfile @@ -1,9 +1,11 @@ source 'http://rubygems.org' -gem 'aws-sdk-v1', '1.64' +gem 'aws-sdk-s3', '~> 1.140' +gem 'aws-sdk-ec2', '~> 1.450' gem 'fpm', '1.15.1' -gem 'sinatra', '1.4.6' -gem 'nokogiri', '~> 1.13.0' +gem 'sinatra', '~> 2.2' +gem 'nokogiri', '~> 1.15' +gem 'rexml' # Required for Ruby 3.0+ group :test do - gem 'rspec' - gem 'rspec-given' + gem 'rspec', '~> 3.12' + gem 'rspec-given', '~> 3.8' end diff --git a/Gemfile.lock b/Gemfile.lock index 6324942..f23b2ee 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -2,13 +2,34 @@ GEM remote: http://rubygems.org/ specs: arr-pm (0.0.12) - aws-sdk-v1 (1.64.0) - json (~> 1.4) - nokogiri (>= 1.4.4) + aws-eventstream (1.4.0) + aws-partitions (1.1174.0) + aws-sdk-core (3.233.0) + aws-eventstream (~> 1, >= 1.3.0) + aws-partitions (~> 1, >= 1.992.0) + aws-sigv4 (~> 1.9) + base64 + bigdecimal + jmespath (~> 1, >= 1.6.1) + logger + aws-sdk-ec2 (1.564.0) + aws-sdk-core (~> 3, >= 3.231.0) + aws-sigv4 (~> 1.5) + aws-sdk-kms (1.114.0) + aws-sdk-core (~> 3, >= 3.231.0) + aws-sigv4 (~> 1.5) + aws-sdk-s3 (1.200.0) + aws-sdk-core (~> 3, >= 3.231.0) + aws-sdk-kms (~> 1) + aws-sigv4 (~> 1.5) + aws-sigv4 (1.12.1) + aws-eventstream (~> 1, >= 1.0.2) backports (3.25.2) + base64 (0.3.0) + bigdecimal (3.3.1) cabin (0.9.1) clamp (1.0.1) - diff-lcs (1.4.4) + diff-lcs (1.6.2) dotenv (2.8.1) fpm (1.15.1) arr-pm (~> 0.0.11) @@ -21,13 +42,12 @@ GEM given_core (3.8.2) sorcerer (>= 0.3.7) insist (1.0.0) - json (1.8.6) - mini_portile2 (2.8.9) + jmespath (1.6.2) + logger (1.7.0) mustache (0.99.8) - nokogiri (1.13.10) - mini_portile2 (~> 2.8.0) - racc (~> 1.4) - nokogiri (1.13.10-x86_64-linux) + mustermann (2.0.2) + ruby2_keywords (~> 0.0.1) + nokogiri (1.18.10-arm64-darwin) racc (~> 1.4) pleaserun (0.0.33) cabin (> 0) @@ -37,47 +57,48 @@ GEM mustache (= 0.99.8) stud racc (1.8.1) - rack (1.6.13) - rack-protection (1.5.5) + rack (2.2.20) + rack-protection (2.2.4) rack rexml (3.4.4) - rspec (3.10.0) - rspec-core (~> 3.10.0) - rspec-expectations (~> 3.10.0) - rspec-mocks (~> 3.10.0) - rspec-core (3.10.1) - rspec-support (~> 3.10.0) - rspec-expectations (3.10.1) + rspec (3.13.2) + rspec-core (~> 3.13.0) + rspec-expectations (~> 3.13.0) + rspec-mocks (~> 3.13.0) + rspec-core (3.13.6) + rspec-support (~> 3.13.0) + rspec-expectations (3.13.5) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.10.0) + rspec-support (~> 3.13.0) rspec-given (3.8.2) given_core (= 3.8.2) rspec (>= 2.14.0) - rspec-mocks (3.10.2) + rspec-mocks (3.13.6) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.10.0) - rspec-support (3.10.2) - sinatra (1.4.6) - rack (~> 1.4) - rack-protection (~> 1.4) - tilt (>= 1.3, < 3) + rspec-support (~> 3.13.0) + rspec-support (3.13.6) + ruby2_keywords (0.0.5) + sinatra (2.2.4) + mustermann (~> 2.0) + rack (~> 2.2) + rack-protection (= 2.2.4) + tilt (~> 2.0) sorcerer (2.0.1) stud (0.0.23) - tilt (2.0.10) + tilt (2.6.1) PLATFORMS - -darwin-23 - -darwin-24 - ruby - x86_64-linux + arm64-darwin-24 DEPENDENCIES - aws-sdk-v1 (= 1.64) + aws-sdk-ec2 (~> 1.450) + aws-sdk-s3 (~> 1.140) fpm (= 1.15.1) - nokogiri (~> 1.13.0) - rspec - rspec-given - sinatra (= 1.4.6) + nokogiri (~> 1.15) + rexml + rspec (~> 3.12) + rspec-given (~> 3.8) + sinatra (~> 2.2) BUNDLED WITH - 2.2.17 + 2.3.27 diff --git a/lib/ploy/command/oracle.rb b/lib/ploy/command/oracle.rb index 5d24b8a..7fa099b 100644 --- a/lib/ploy/command/oracle.rb +++ b/lib/ploy/command/oracle.rb @@ -9,8 +9,8 @@ module Ploy module Command class OracleServer < Sinatra::Base - set :port => 9876 - set :bind => '0.0.0.0' + set :port, 9876 + set :bind, '0.0.0.0' def self.oracle_run(metasrc) @@metasrc = metasrc # this could not be sadder diff --git a/lib/ploy/metaoracle.rb b/lib/ploy/metaoracle.rb index e5652bf..b544605 100644 --- a/lib/ploy/metaoracle.rb +++ b/lib/ploy/metaoracle.rb @@ -1,16 +1,19 @@ require 'net/http' require 'json' +require 'aws-sdk-ec2' module Ploy class MetaOracle def initialize(stack) @stack = stack + @ec2 = Aws::EC2::Resource.new end def query r = {} puts "query" - AWS::EC2.new.instances.tagged_values(@stack).each do |i| + # Find instances with the stack tag + @ec2.instances(filters: [{name: 'tag:Name', values: [@stack]}]).each do |i| puts "asking #{i.private_ip_address}" r[i.private_ip_address] = meta(i) end diff --git a/lib/ploy/packageset.rb b/lib/ploy/packageset.rb index ce9063f..cf2710d 100644 --- a/lib/ploy/packageset.rb +++ b/lib/ploy/packageset.rb @@ -1,3 +1,5 @@ +require 'ploy/package' + module Ploy class PackageSet attr_accessor :packages diff --git a/lib/ploy/s3storage.rb b/lib/ploy/s3storage.rb index efa12b9..698a284 100644 --- a/lib/ploy/s3storage.rb +++ b/lib/ploy/s3storage.rb @@ -1,53 +1,57 @@ -require 'aws-sdk-v1' +require 'aws-sdk-s3' module Ploy class S3Storage def initialize(bucket) @bucketname = bucket + @s3 = Aws::S3::Resource.new + @bucket = @s3.bucket(@bucketname) end def put(path, name, meta = {}) - AWS::S3.new.buckets[@bucketname].objects[name].write( - Pathname.new(path), - { :metadata => meta } - ) + obj = @bucket.object(name) + File.open(path.to_s, 'rb') do |file| + obj.put(body: file, metadata: meta) + end end def copy(from, to) - AWS::S3.new.buckets[@bucketname].objects[from].copy_to(to) + @bucket.object(to).copy_from( + copy_source: "#{@bucketname}/#{from}" + ) end def read(from) - AWS::S3.new.buckets[@bucketname].objects[from].read + @bucket.object(from).get.body.read end def get(from, fileio) - AWS::S3.new.buckets[@bucketname].objects[from].read do |chunk| + @bucket.object(from).get do |chunk| fileio.write(chunk) end fileio.flush end def metadata(loc) - o = AWS::S3.new.buckets[@bucketname].objects[loc] - if (o.exists?) then - return o.metadata - else - return {} + obj = @bucket.object(loc) + begin + obj.head.metadata + rescue Aws::S3::Errors::NotFound + {} end end def list - tree = AWS::S3.new.buckets[@bucketname].as_tree - dirs = tree.children.select(&:branch?).collect(&:prefix) package_names = [] - dirs.each do |dir| - dir.chop! - if dir != 'hub' && dir != 'blessed' && dir != 'staging' - package_names.push(dir) + # List objects with delimiter to get "directories" + @bucket.objects(delimiter: '/').each do |obj_summary| + prefix = obj_summary.key + prefix.chop! if prefix.end_with?('/') + unless ['hub', 'blessed', 'staging'].include?(prefix) + package_names.push(prefix) end end - return package_names + package_names end end end diff --git a/ploy-3.1.6.gemspec b/ploy-3.1.6.gemspec new file mode 100644 index 0000000..6916eda --- /dev/null +++ b/ploy-3.1.6.gemspec @@ -0,0 +1,17 @@ +Gem::Specification.new do |s| + s.name = 'ploy-3.1.6' + s.version = '0.0.46' + s.date = '2025-10-21' + s.summary = 'Multi-phase deployment tool' + s.description = 'Multi-phase deployment tool for use in a continuous deployment environment.' + s.authors = ["Michael Bruce", "Brian J. Schrock", "Dustin Watson"] + s.email = 'mbruce@manta.com' + s.files += Dir['lib/**/*.rb'] + s.required_ruby_version = '>= 3.1.0' + s.add_runtime_dependency 'aws-sdk-s3', '~> 1.140' + s.add_runtime_dependency 'aws-sdk-ec2', '~> 1.450' + s.add_runtime_dependency 'fpm', '1.15.1' + s.add_runtime_dependency 'sinatra', '~> 2.2' + s.add_runtime_dependency 'nokogiri', '~> 1.15' + s.executables << 'ploy' +end diff --git a/spec/metaoracle_spec.rb b/spec/metaoracle_spec.rb index 097b032..56358de 100644 --- a/spec/metaoracle_spec.rb +++ b/spec/metaoracle_spec.rb @@ -13,11 +13,12 @@ allow(instance1).to receive(:private_ip_address).and_return('1.1.1.1') allow(instance2).to receive(:private_ip_address).and_return('2.2.2.2') - ec2 = double('ec2') instances = double('instances') - allow(AWS::EC2).to receive(:new).and_return(ec2) - allow(ec2).to receive(:instances).and_return(instances) - allow(instances).to receive(:tagged_values).with('test-stack').and_return([instance1, instance2]) + allow(instances).to receive(:each).and_yield(instance1).and_yield(instance2) + + ec2 = double('ec2') + allow(ec2).to receive(:instances).with(filters: [{name: 'tag:Name', values: ['test-stack']}]).and_return(instances) + allow(Aws::EC2::Resource).to receive(:new).and_return(ec2) oracle = Ploy::MetaOracle.new('test-stack') allow(oracle).to receive(:meta).with(instance1).and_return({'pkg1' => {'version' => 'v1'}}) diff --git a/spec/packageset_spec.rb b/spec/packageset_spec.rb index 669cc65..d71c714 100644 --- a/spec/packageset_spec.rb +++ b/spec/packageset_spec.rb @@ -2,6 +2,14 @@ require 'ploy/packageset' describe Ploy::PackageSet do + before(:each) do + # Mock AWS SDK to prevent real AWS calls + bucket = double("bucket") + s3 = double("s3") + allow(s3).to receive(:bucket).and_return(bucket) + allow(Aws::S3::Resource).to receive(:new).and_return(s3) + end + context "packageset with two packages, unlocked" do Given(:ps) do Ploy::PackageSet.new( diff --git a/spec/s3storage_spec.rb b/spec/s3storage_spec.rb index d023127..8bf2acd 100644 --- a/spec/s3storage_spec.rb +++ b/spec/s3storage_spec.rb @@ -1,36 +1,31 @@ require './lib/ploy/s3storage' describe Ploy::S3Storage do - before(:all) do + before(:each) do + @bucket = double("bucket") + @s3 = double("s3") + allow(@s3).to receive(:bucket).with("testbucket").and_return(@bucket) + allow(Aws::S3::Resource).to receive(:new).and_return(@s3) @storage = Ploy::S3Storage.new('testbucket') end + it "can be initialized" do expect(@storage).to be_a(Ploy::S3Storage) end + describe "#put" do it "uses aws-sdk to upload a file to s3" do fakepath = "nothing.deb" uploadpath = "foo/bar" - object = double("object") - object.should_receive(:write) do | f,opts2 | - expect(f).to be_a(Pathname) - expect(opts2).to be_a(Hash) - end - - objects = double("objects") - objects.should_receive(:[]).with(uploadpath) { object } + # Create a fake file + allow(File).to receive(:open).with(fakepath, 'rb').and_yield(double("file")) - bucket = double("bucket") - bucket.stub(:objects) { objects } + object = double("object") + expect(object).to receive(:put).with(hash_including(metadata: {})) - buckets = double("buckets") - buckets.should_receive(:[]).with("testbucket") { bucket } + expect(@bucket).to receive(:object).with(uploadpath).and_return(object) - s3 = double("s3") - s3.should_receive(:buckets) { buckets } - AWS::S3.stub(:new) { s3 } - @storage.put(fakepath, uploadpath) end end @@ -39,23 +34,12 @@ from = "a/b/c" to = "d/e/f" - from_obj = double("from_obj") - from_obj.should_receive(:copy_to).with(to) - - objects = double("objects") - objects.should_receive(:[]).with(from) { from_obj } + to_obj = double("to_obj") + expect(to_obj).to receive(:copy_from).with(copy_source: "testbucket/a/b/c") - bucket = double("bucket") - bucket.stub(:objects) { objects } + expect(@bucket).to receive(:object).with(to).and_return(to_obj) - buckets = double("buckets") - buckets.should_receive(:[]).with("testbucket") { bucket } - - s3 = double("s3") - s3.should_receive(:buckets) { buckets } - AWS::S3.stub(:new) { s3 } - - @storage.copy(from, to) + @storage.copy(from, to) end end @@ -64,21 +48,16 @@ from = "a/b/c.txt" content = "file content" - object = double("object") - object.should_receive(:read).and_return(content) - - objects = double("objects") - objects.should_receive(:[]).with(from).and_return(object) + body = double("body") + allow(body).to receive(:read).and_return(content) - bucket = double("bucket") - bucket.stub(:objects).and_return(objects) + response = double("response") + allow(response).to receive(:body).and_return(body) - buckets = double("buckets") - buckets.should_receive(:[]).with("testbucket").and_return(bucket) + object = double("object") + expect(object).to receive(:get).and_return(response) - s3 = double("s3") - s3.should_receive(:buckets).and_return(buckets) - AWS::S3.stub(:new).and_return(s3) + expect(@bucket).to receive(:object).with(from).and_return(object) result = @storage.read(from) expect(result).to eq(content) @@ -90,22 +69,13 @@ location = "some/path.deb" meta = {'git_revision' => 'abc123', 'custom_field' => 'value'} - object = double("object") - object.should_receive(:exists?).and_return(true) - object.should_receive(:metadata).and_return(meta) - - objects = double("objects") - objects.should_receive(:[]).with(location).and_return(object) - - bucket = double("bucket") - bucket.stub(:objects).and_return(objects) + head_response = double("head_response") + allow(head_response).to receive(:metadata).and_return(meta) - buckets = double("buckets") - buckets.should_receive(:[]).with("testbucket").and_return(bucket) + object = double("object") + expect(object).to receive(:head).and_return(head_response) - s3 = double("s3") - s3.should_receive(:buckets).and_return(buckets) - AWS::S3.stub(:new).and_return(s3) + expect(@bucket).to receive(:object).with(location).and_return(object) result = @storage.metadata(location) expect(result).to eq(meta) @@ -115,20 +85,9 @@ location = "nonexistent/path.deb" object = double("object") - object.should_receive(:exists?).and_return(false) - - objects = double("objects") - objects.should_receive(:[]).with(location).and_return(object) - - bucket = double("bucket") - bucket.stub(:objects).and_return(objects) + expect(object).to receive(:head).and_raise(Aws::S3::Errors::NotFound.new(nil, 'Not Found')) - buckets = double("buckets") - buckets.should_receive(:[]).with("testbucket").and_return(bucket) - - s3 = double("s3") - s3.should_receive(:buckets).and_return(buckets) - AWS::S3.stub(:new).and_return(s3) + expect(@bucket).to receive(:object).with(location).and_return(object) result = @storage.metadata(location) expect(result).to eq({}) @@ -139,25 +98,32 @@ it "downloads a file using aws-sdk" do from = "a/b/c" fakeio = double("fakeio") - fakeio.should_receive(:flush) + expect(fakeio).to receive(:write).with("test") + expect(fakeio).to receive(:flush) object = double("object") - object.should_receive(:read) { "test" } - - objects = double("objects") - objects.should_receive(:[]).with(from) { object } + expect(object).to receive(:get).and_yield("test") - bucket = double("bucket") - bucket.stub(:objects) { objects } + expect(@bucket).to receive(:object).with(from).and_return(object) - buckets = double("buckets") - buckets.should_receive(:[]).with("testbucket") { bucket } + @storage.get(from, fakeio) + end + end - s3 = double("s3") - s3.should_receive(:buckets) { buckets } - AWS::S3.stub(:new) { s3 } + describe "#list" do + it "lists package names excluding hub, blessed, and staging" do + obj1 = double("obj1", key: "package1/") + obj2 = double("obj2", key: "package2/") + obj3 = double("obj3", key: "hub/") + obj4 = double("obj4", key: "blessed/") - @storage.get(from, fakeio) + objects = double("objects") + allow(objects).to receive(:each).and_yield(obj1).and_yield(obj2).and_yield(obj3).and_yield(obj4) + + expect(@bucket).to receive(:objects).with(delimiter: '/').and_return(objects) + + result = @storage.list + expect(result).to eq(["package1", "package2"]) end end end From bf361fda67a3d3bdf8cdd7aebb622451e5075efb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Crist=C3=B3bal=20Badilla=20C?= Date: Tue, 21 Oct 2025 17:36:23 -0300 Subject: [PATCH 5/6] Add webrick dependency to Gemfile and gemspec for Ruby 3.0+ compatibility --- Gemfile | 1 + ploy-3.1.6.gemspec | 1 + 2 files changed, 2 insertions(+) diff --git a/Gemfile b/Gemfile index 6bffc12..de7db95 100644 --- a/Gemfile +++ b/Gemfile @@ -5,6 +5,7 @@ gem 'fpm', '1.15.1' gem 'sinatra', '~> 2.2' gem 'nokogiri', '~> 1.15' gem 'rexml' # Required for Ruby 3.0+ +gem 'webrick', '~> 1.8' # Required for Ruby 3.0+ (Sinatra web server) group :test do gem 'rspec', '~> 3.12' gem 'rspec-given', '~> 3.8' diff --git a/ploy-3.1.6.gemspec b/ploy-3.1.6.gemspec index 6916eda..a535193 100644 --- a/ploy-3.1.6.gemspec +++ b/ploy-3.1.6.gemspec @@ -13,5 +13,6 @@ Gem::Specification.new do |s| s.add_runtime_dependency 'fpm', '1.15.1' s.add_runtime_dependency 'sinatra', '~> 2.2' s.add_runtime_dependency 'nokogiri', '~> 1.15' + s.add_runtime_dependency 'webrick', '~> 1.8' s.executables << 'ploy' end From 1e525f3f5fb43ebee4004c9bb2f0f3c73ee27f7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Crist=C3=B3bal=20Badilla=20C?= Date: Wed, 22 Oct 2025 16:16:43 -0300 Subject: [PATCH 6/6] Update Ruby version requirement to 3.1.6 and add AWS SDK dependencies in README --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 897f197..1c4fdfc 100644 --- a/README.md +++ b/README.md @@ -122,8 +122,10 @@ $ gem build ploy.gemspec ## Requirements - - ruby 2.7.8+ + - ruby 3.1.6 - fpm 1.15.1+ + - aws-sdk-s3 + - aws-sdk-ec2 - rsync - dpkg (for tests)