diff --git a/support/ci/generate b/support/ci/generate index 17a56d6261..78fff787ea 100755 --- a/support/ci/generate +++ b/support/ci/generate @@ -12,20 +12,11 @@ end class CI RUBY_VERSIONS = [ - MRI_RUBY = [ - RUBY_3_2 = "ruby-3.2", - RUBY_3_1 = "ruby-3.1", - RUBY_3_0 = "ruby-3.0" - ], + MRI_RUBY = [RUBY_3_2 = "ruby-3.2", RUBY_3_1 = "ruby-3.1", RUBY_3_0 = "ruby-3.0"], TRUFFLE_RUBY = "truffleruby" ].flatten - DATA_TYPES = [ - DATA_TEXT = "text", - DATA_BINARY = "binary", - DATA_JSON = "json", - DATA_JSONB = "jsonb" - ] + DATA_TYPES = [DATA_TEXT = "text", DATA_BINARY = "binary", DATA_JSON = "json", DATA_JSONB = "jsonb"] DATA_TYPES_IN_AR = [DATA_BINARY, DATA_TYPES.drop(2)].flatten DATA_TYPES_IN_SEQUEL = [DATA_TEXT, DATA_TYPES.drop(2)].flatten @@ -34,10 +25,8 @@ class CI SQLITE = "sqlite:db.sqlite3", SQLITE3 = "sqlite3:db.sqlite3", POSTGRES = [ - POSTGRES_15 = - "postgres://postgres:secret@localhost:10015/rails_event_store?pool=5", - POSTGRES_11 = - "postgres://postgres:secret@localhost:10011/rails_event_store?pool=5" + POSTGRES_15 = "postgres://postgres:secret@localhost:10015/rails_event_store?pool=5", + POSTGRES_11 = "postgres://postgres:secret@localhost:10011/rails_event_store?pool=5" ], MYSQL = [ MYSQL_8 = "mysql2://root:secret@127.0.0.1:10008/rails_event_store?pool=5", @@ -47,20 +36,11 @@ class CI GEMFILE = "Gemfile" - RAILS_GEMFILES = [ - GEMFILE_RAILS_6_1 = "Gemfile.rails_6_1", - GEMFILE_RAILS_6_0 = "Gemfile.rails_6_0" - ].flatten + RAILS_GEMFILES = [GEMFILE_RAILS_6_1 = "Gemfile.rails_6_1", GEMFILE_RAILS_6_0 = "Gemfile.rails_6_0"].flatten - AS_GEMFILES = [ - GEMFILE_AS_6_1 = "Gemfile.activesupport_6_1", - GEMFILE_AS_6_0 = "Gemfile.activesupport_6_0" - ] + AS_GEMFILES = [GEMFILE_AS_6_1 = "Gemfile.activesupport_6_1", GEMFILE_AS_6_0 = "Gemfile.activesupport_6_0"] - SIDEKIQ_GEMFILES = [ - GEMFILE_SIDEKIQ_6_5 = "Gemfile.sidekiq_6_5", - GEMFILE_SIDEKIQ_5_2 = "Gemfile.sidekiq_5_2" - ] + SIDEKIQ_GEMFILES = [GEMFILE_SIDEKIQ_6_5 = "Gemfile.sidekiq_6_5", GEMFILE_SIDEKIQ_5_2 = "Gemfile.sidekiq_5_2"] def workflows [ @@ -75,29 +55,14 @@ class CI release_coverage("ruby_event_store-rspec"), release_test( "ruby_event_store-browser", - steps: [ - checkout, - verify_lockfile, - setup_ruby, - setup_node, - cache_elm, - make("install-npm test") - ], - matrix: - generate( - ruby_version(RUBY_VERSIONS), - bundle_gemfile(GEMFILE, "Gemfile.rack_2_0") - ) + steps: [checkout, verify_lockfile, setup_ruby, setup_node, cache_elm, make("install-npm test")], + matrix: generate(ruby_version(RUBY_VERSIONS), bundle_gemfile(GEMFILE, "Gemfile.rack_2_0")) ), release_mutate("ruby_event_store-browser"), release_coverage("ruby_event_store-browser"), release_test( "rails_event_store", - matrix: - generate( - ruby_version(RUBY_VERSIONS), - bundle_gemfile(GEMFILE, RAILS_GEMFILES) - ) + matrix: generate(ruby_version(RUBY_VERSIONS), bundle_gemfile(GEMFILE, RAILS_GEMFILES)) ), release_mutate("rails_event_store"), release_coverage("rails_event_store"), @@ -109,10 +74,7 @@ class CI ruby_version(RUBY_VERSIONS), bundle_gemfile(GEMFILE), join( - generate( - database_url(SQLITE3), - data_type(DATA_TYPES_IN_AR.take(1)) - ), + generate(database_url(SQLITE3), data_type(DATA_TYPES_IN_AR.take(1))), generate(database_url(POSTGRES), data_type(DATA_TYPES_IN_AR)), generate(database_url(MYSQL), data_type(DATA_TYPES_IN_AR.take(2))) ) @@ -122,8 +84,7 @@ class CI release_coverage("ruby_event_store-active_record"), contrib_test( "ruby_event_store-flipper", - matrix: - generate(ruby_version(MRI_RUBY), bundle_gemfile(GEMFILE, AS_GEMFILES)) + matrix: generate(ruby_version(MRI_RUBY), bundle_gemfile(GEMFILE, AS_GEMFILES)) ), contrib_mutate("ruby_event_store-flipper"), contrib_coverage("ruby_event_store-flipper"), @@ -141,14 +102,7 @@ class CI database_url(SQLITE3, MYSQL_5, MYSQL_8) ) ), - steps: [ - checkout, - setup_nix, - setup_cachix, - verify_lockfile, - setup_ruby, - make_nix_shell("test") - ] + steps: [checkout, setup_nix, setup_cachix, verify_lockfile, setup_ruby, make_nix_shell("test")] ), contrib_mutate( "ruby_event_store-outbox", @@ -163,14 +117,7 @@ class CI ), contrib_coverage( "ruby_event_store-outbox", - steps: [ - checkout, - setup_nix, - setup_cachix, - verify_lockfile, - setup_ruby, - make_nix_shell("mutate") - ] + steps: [checkout, setup_nix, setup_cachix, verify_lockfile, setup_ruby, make_nix_shell("mutate")] ), contrib_test("ruby_event_store-profiler"), contrib_mutate("ruby_event_store-profiler"), @@ -244,21 +191,8 @@ class CI contrib_coverage("ruby_event_store-sequel"), contrib_test( "ruby_event_store-sidekiq_scheduler", - matrix: - join( - generate( - ruby_version(MRI_RUBY), - bundle_gemfile(GEMFILE, SIDEKIQ_GEMFILES) - ) - ), - steps: [ - checkout, - setup_nix, - setup_cachix, - verify_lockfile, - setup_ruby, - make_nix_shell("test") - ] + matrix: join(generate(ruby_version(MRI_RUBY), bundle_gemfile(GEMFILE, SIDEKIQ_GEMFILES))), + steps: [checkout, setup_nix, setup_cachix, verify_lockfile, setup_ruby, make_nix_shell("test")] ), contrib_mutate( "ruby_event_store-sidekiq_scheduler", @@ -273,14 +207,7 @@ class CI ), contrib_coverage( "ruby_event_store-sidekiq_scheduler", - steps: [ - checkout, - setup_nix, - setup_cachix, - verify_lockfile, - setup_ruby, - make_nix_shell("mutate") - ] + steps: [checkout, setup_nix, setup_cachix, verify_lockfile, setup_ruby, make_nix_shell("mutate")] ), contrib_test("ruby_event_store-transformations"), contrib_mutate("ruby_event_store-transformations"), @@ -288,7 +215,7 @@ class CI contrib_test("minitest-ruby_event_store"), contrib_mutate("minitest-ruby_event_store"), contrib_coverage("minitest-ruby_event_store"), - contrib_test("dres_client", paths: dres_paths("dres_client_test")), + contrib_test("dres_client", triggers: dres_triggers("dres_client_test")), contrib_test( "dres_rails", services: [postgres_11, postgres_15], @@ -301,7 +228,7 @@ class CI data_type(DATA_TYPES_IN_AR) ) ), - paths: dres_paths("dres_rails_test") + triggers: dres_triggers("dres_rails_test") ), assets("ruby_event_store-browser") ] @@ -313,10 +240,7 @@ class CI end def verify_lockfile - { - "run" => "test -e ${{ env.BUNDLE_GEMFILE }}.lock", - "working-directory" => "${{ env.WORKING_DIRECTORY }}" - } + { "run" => "test -e ${{ env.BUNDLE_GEMFILE }}.lock", "working-directory" => "${{ env.WORKING_DIRECTORY }}" } end def setup_ruby @@ -336,8 +260,7 @@ class CI "with" => { "node-version" => 18, "cache" => "npm", - "cache-dependency-path" => - "${{ env.WORKING_DIRECTORY }}/elm/package-lock.json" + "cache-dependency-path" => "${{ env.WORKING_DIRECTORY }}/elm/package-lock.json" } } end @@ -347,19 +270,13 @@ class CI "uses" => "actions/cache@v3", "with" => { "path" => "~/.elm", - "key" => - "elm-${{ hashFiles(format('{0}/elm/elm.json', env.WORKING_DIRECTORY)) }}" + "key" => "elm-${{ hashFiles(format('{0}/elm/elm.json', env.WORKING_DIRECTORY)) }}" } } end def setup_nix - { - "uses" => "cachix/install-nix-action@v20", - "with" => { - "nix_path" => "nixpkgs=channel:nixos-unstable" - } - } + { "uses" => "cachix/install-nix-action@v20", "with" => { "nix_path" => "nixpkgs=channel:nixos-unstable" } } end def setup_cachix @@ -373,26 +290,20 @@ class CI end def make_nix_shell(target, imports: ["redis.nix"]) - { - "run" => <<~SHELL, - nix-shell --run "make #{target}" -E" - with import { }; - mkShell { - inputsFrom = [ - #{imports.map { |i| "(import ../../support/nix/#{i})" }.join("\n")} - ]; - } - " - SHELL - "working-directory" => "${{ env.WORKING_DIRECTORY }}" - } + { "run" => <<~SHELL, "working-directory" => "${{ env.WORKING_DIRECTORY }}" } + nix-shell --run "make #{target}" -E" + with import { }; + mkShell { + inputsFrom = [ + #{imports.map { |i| "(import ../../support/nix/#{i})" }.join("\n")} + ]; + } + " + SHELL end def make(target) - { - "run" => "make #{target}", - "working-directory" => "${{ env.WORKING_DIRECTORY }}" - } + { "run" => "make #{target}", "working-directory" => "${{ env.WORKING_DIRECTORY }}" } end def upload_artifact(name) @@ -417,17 +328,11 @@ class CI end def set_short_sha_env - { - "run" => - "echo \"SHORT_SHA=$(git rev-parse --short=12 HEAD)\" >> $GITHUB_ENV" - } + { "run" => "echo \"SHORT_SHA=$(git rev-parse --short=12 HEAD)\" >> $GITHUB_ENV" } end def aws_s3_sync - { - "run" => - "aws s3 sync ${{ env.WORKING_DIRECTORY }}/public s3://ruby-event-store-assets/${{ env.SHORT_SHA }}" - } + { "run" => "aws s3 sync ${{ env.WORKING_DIRECTORY }}/public s3://ruby-event-store-assets/${{ env.SHORT_SHA }}" } end end include Actions @@ -453,193 +358,27 @@ class CI def scheduled_trigger { "schedule" => [{ "cron" => "0 17 * * *" }] } end - end - include Triggers - - module Workflows - def release_test( - name, - matrix: generate(ruby_version(RUBY_VERSIONS), bundle_gemfile(GEMFILE)), - steps: [checkout, verify_lockfile, setup_ruby, make("test")], - services: [], - paths: release_paths("#{name}_test") - ) - { - name: "#{name}_test", - working_directory: name, - matrix: matrix, - job_name: "test", - services: services, - steps: steps, - paths: paths, - on: [ - manual_trigger, - api_trigger, - push_trigger(paths.dup), - pr_trigger(paths.dup) - ] - } - end - def contrib_test( - name, - matrix: generate(ruby_version(MRI_RUBY), bundle_gemfile(GEMFILE)), - steps: [checkout, verify_lockfile, setup_ruby, make("test")], - services: [], - paths: contrib_paths("#{name}_test", "contrib/#{name}") - ) - { - name: "#{name}_test", - working_directory: "contrib/#{name}", - matrix: matrix, - job_name: "test", - services: services, - steps: steps, - paths: paths, - on: [ - manual_trigger, - api_trigger, - push_trigger(paths.dup), - pr_trigger(paths.dup) - ] - } + def release_triggers(workflow_name) + paths = release_paths(workflow_name) + [manual_trigger, api_trigger, push_trigger(paths.dup), pr_trigger(paths.dup)] end - def release_mutate( - name, - matrix: generate(ruby_version(MRI_RUBY.take(1)), bundle_gemfile(GEMFILE)), - steps: [ - checkout(depth: 0), - verify_lockfile, - setup_ruby, - make("mutate-changes") - ], - services: [], - paths: release_paths("#{name}_mutate") - ) - { - name: "#{name}_mutate", - working_directory: name, - matrix: matrix, - job_name: "mutate", - services: services, - steps: steps, - paths: paths, - on: [ - manual_trigger, - api_trigger, - push_trigger(paths.dup), - pr_trigger(paths.dup) - ] - } - end - - def contrib_mutate( - name, - matrix: generate(ruby_version(MRI_RUBY.take(1)), bundle_gemfile(GEMFILE)), - steps: [ - checkout(depth: 0), - verify_lockfile, - setup_ruby, - make("mutate-changes") - ], - services: [], - paths: contrib_paths("#{name}_mutate", "contrib/#{name}") - ) - { - name: "#{name}_mutate", - working_directory: "contrib/#{name}", - matrix: matrix, - job_name: "mutate", - services: services, - steps: steps, - paths: paths, - on: [ - manual_trigger, - api_trigger, - push_trigger(paths.dup), - pr_trigger(paths.dup) - ] - } - end - - def release_coverage( - name, - matrix: generate(ruby_version(MRI_RUBY.take(1)), bundle_gemfile(GEMFILE)), - steps: [checkout, verify_lockfile, setup_ruby, make("mutate")], - services: [], - paths: coverage_paths("#{name}_coverage", name) - ) - { - name: "#{name}_coverage", - working_directory: name, - matrix: matrix, - job_name: "coverage", - services: services, - steps: steps, - paths: paths, - on: [ - manual_trigger, - api_trigger, - push_trigger(paths.dup), - pr_trigger(paths.dup), - scheduled_trigger - ] - } + def contrib_triggers(workflow_name, working_directory) + paths = contrib_paths(workflow_name, working_directory) + [manual_trigger, api_trigger, push_trigger(paths.dup), pr_trigger(paths.dup)] end - def contrib_coverage( - name, - matrix: generate(ruby_version(MRI_RUBY.take(1)), bundle_gemfile(GEMFILE)), - steps: [checkout, verify_lockfile, setup_ruby, make("mutate")], - services: [], - paths: coverage_paths("#{name}_coverage", "contrib/#{name}") - ) - { - name: "#{name}_coverage", - working_directory: "contrib/#{name}", - matrix: matrix, - job_name: "coverage", - services: services, - steps: steps, - paths: paths, - on: [ - manual_trigger, - api_trigger, - push_trigger(paths.dup), - pr_trigger(paths.dup), - scheduled_trigger - ] - } + def coverage_triggers(workflow_name, working_directory) + paths = coverage_paths(workflow_name, working_directory) + [manual_trigger, api_trigger, push_trigger(paths.dup), pr_trigger(paths.dup), scheduled_trigger] end - def assets(name) - { - name: "#{name}_assets", - working_directory: name, - matrix: [], - job_name: "assets", - services: [], - steps: [ - checkout, - setup_node, - cache_elm, - make("install-npm"), - make("build-npm"), - upload_artifact("ruby_event_store_browser.js"), - upload_artifact("ruby_event_store_browser.css"), - configure_aws_credentials, - set_short_sha_env, - aws_s3_sync - ], - paths: [], - on: [manual_trigger, api_trigger, push_trigger] - } + def dres_triggers(workflow_name) + paths = dres_paths(workflow_name) + [manual_trigger, api_trigger, push_trigger(paths.dup), pr_trigger(paths.dup)] end - end - include Workflows - module Paths def release_paths(workflow_name) [ %w[ @@ -656,19 +395,13 @@ class CI end def contrib_paths(workflow_name, working_directory) - [ - own_paths(working_directory), - workflow_paths(workflow_name), - support_paths - ].reduce(&:concat).uniq + [own_paths(working_directory), workflow_paths(workflow_name), support_paths].reduce(&:concat).uniq end def coverage_paths(workflow_name, working_directory) - [ - [working_directory].map { |wd| "#{wd}/Gemfile.lock" }, - workflow_paths(workflow_name), - support_paths - ].reduce(&:concat).uniq + [[working_directory].map { |wd| "#{wd}/Gemfile.lock" }, workflow_paths(workflow_name), support_paths].reduce( + &:concat + ).uniq end def dres_paths(workflow_name) @@ -691,7 +424,7 @@ class CI %w[support/** !support/bundler/** !support/ci/**] end end - include Paths + include Triggers module Services def postgres_11 @@ -703,8 +436,7 @@ class CI "POSTGRES_PASSWORD" => "secret" }, "ports" => ["10011:5432"], - "options" => - "--health-cmd \"pg_isready\" --health-interval 10s --health-timeout 5s --health-retries 5" + "options" => "--health-cmd \"pg_isready\" --health-interval 10s --health-timeout 5s --health-retries 5" } } end @@ -718,8 +450,7 @@ class CI "POSTGRES_PASSWORD" => "secret" }, "ports" => ["10015:5432"], - "options" => - "--health-cmd \"pg_isready\" --health-interval 10s --health-timeout 5s --health-retries 5" + "options" => "--health-cmd \"pg_isready\" --health-interval 10s --health-timeout 5s --health-retries 5" } } end @@ -733,8 +464,7 @@ class CI "MYSQL_ROOT_PASSWORD" => "secret" }, "ports" => ["10008:3306"], - "options" => - "--health-cmd \"mysqladmin ping\" --health-interval 10s --health-timeout 5s --health-retries 5" + "options" => "--health-cmd \"mysqladmin ping\" --health-interval 10s --health-timeout 5s --health-retries 5" } } end @@ -748,8 +478,7 @@ class CI "MYSQL_ROOT_PASSWORD" => "secret" }, "ports" => ["10005:3306"], - "options" => - "--health-cmd \"mysqladmin ping\" --health-interval 10s --health-timeout 5s --health-retries 5" + "options" => "--health-cmd \"mysqladmin ping\" --health-interval 10s --health-timeout 5s --health-retries 5" } } end @@ -787,64 +516,178 @@ class CI end include Matrix - def as_github_actions - workflows.each do |gem| - name = gem.fetch(:name) - filename = "#{name}.yml" - env = { "WORKING_DIRECTORY" => gem.fetch(:working_directory) }.merge( - matrix_env_vars(gem.fetch(:matrix)) + module Workflows + class Workflow + include Triggers + include Matrix + include Actions + + def initialize( + gem, + job_name: "test", + name: "#{gem}_#{job_name}", + working_directory: gem, + matrix: generate(ruby_version(RUBY_VERSIONS), bundle_gemfile(GEMFILE)), + steps: [checkout, verify_lockfile, setup_ruby, make("test")], + services: [], + triggers: release_triggers(name) ) - job = + @gem = gem + @job_name = job_name + @name = name + @working_directory = working_directory + @matrix = matrix + @steps = steps + @services = services + @triggers = triggers + end + + def to_h + { "name" => name, "on" => triggers.reduce(&:merge), "jobs" => { job_name => job } } + end + + attr_reader :gem, :job_name, :name, :working_directory, :matrix, :steps, :services, :triggers + + private + + def job { "runs-on" => "ubuntu-20.04", "timeout-minutes" => 120, - "env" => env, - "services" => gem.fetch(:services).reduce(&:merge), + "env" => { "WORKING_DIRECTORY" => working_directory }.merge(env(matrix)), + "services" => services.reduce(&:merge), "strategy" => { "fail-fast" => false, "matrix" => { - "include" => gem.fetch(:matrix) + "include" => matrix } }, - "steps" => gem.fetch(:steps) - }.reject { |k, v| k == "services" && gem.fetch(:services).empty? } - .reject { |k, v| k == "strategy" && gem.fetch(:matrix).empty? } - - File.write( - File.join(workflows_root, filename), - as_formatted_yaml( - { - "name" => name, - "on" => gem.fetch(:on).reduce(&:merge), - "jobs" => { - gem.fetch(:job_name) => job - } - } - ) + "steps" => steps + }.reject { |k, _| k == "services" && services.empty? }.reject { |k, _| k == "strategy" && matrix.empty? } + end + + def env(matrix) + matrix + .take(1) + .reduce({}) do |acc, matrix_item| + matrix_item.reduce(acc) { |acc, (key, _)| acc.merge(key.upcase => "${{ matrix.#{key} }}") } + end + end + end + + def release_test(name, **) + Workflow.new(name, **) + end + + def contrib_test( + name, + working_directory: "contrib/#{name}", + matrix: generate(ruby_version(MRI_RUBY), bundle_gemfile(GEMFILE)), + ** + ) + Workflow.new( + name, + working_directory: working_directory, + matrix: matrix, + triggers: contrib_triggers("#{name}_test", working_directory), + ** ) + end - puts "writing #{filename}" + def release_mutate( + name, + matrix: generate(ruby_version(MRI_RUBY.take(1)), bundle_gemfile(GEMFILE)), + steps: [checkout(depth: 0), verify_lockfile, setup_ruby, make("mutate-changes")], + ** + ) + Workflow.new(name, job_name: "mutate", matrix: matrix, steps: steps, **) + end + + def contrib_mutate( + name, + working_directory: "contrib/#{name}", + matrix: generate(ruby_version(MRI_RUBY.take(1)), bundle_gemfile(GEMFILE)), + steps: [checkout(depth: 0), verify_lockfile, setup_ruby, make("mutate-changes")], + ** + ) + Workflow.new( + name, + job_name: "mutate", + working_directory: working_directory, + matrix: matrix, + steps: steps, + triggers: contrib_triggers("#{name}_mutate", working_directory), + ** + ) + end + + def release_coverage( + name, + matrix: generate(ruby_version(MRI_RUBY.take(1)), bundle_gemfile(GEMFILE)), + steps: [checkout, verify_lockfile, setup_ruby, make("mutate")], + ** + ) + Workflow.new( + name, + job_name: "coverage", + matrix: matrix, + steps: steps, + triggers: coverage_triggers("#{name}_coverage", name), + ** + ) + end + + def contrib_coverage( + name, + working_directory: "contrib/#{name}", + matrix: generate(ruby_version(MRI_RUBY.take(1)), bundle_gemfile(GEMFILE)), + steps: [checkout, verify_lockfile, setup_ruby, make("mutate")], + ** + ) + Workflow.new( + name, + job_name: "coverage", + working_directory: working_directory, + matrix: matrix, + steps: steps, + triggers: coverage_triggers("#{name}_coverage", working_directory), + ** + ) + end + + def assets(name) + Workflow.new( + name, + job_name: "assets", + matrix: [], + steps: [ + checkout, + setup_node, + cache_elm, + make("install-npm"), + make("build-npm"), + upload_artifact("ruby_event_store_browser.js"), + upload_artifact("ruby_event_store_browser.css"), + configure_aws_credentials, + set_short_sha_env, + aws_s3_sync + ], + triggers: [manual_trigger, api_trigger, push_trigger] + ) end end + include Workflows - def matrix_env_vars(matrix) - matrix - .take(1) - .reduce({}) do |acc, matrix_item| - matrix_item.reduce(acc) do |acc, (key, _)| - acc.merge(key.upcase => "${{ matrix.#{key} }}") - end - end + def as_github_actions + workflows.each do |workflow| + filename = "#{workflow.name}.yml" + File.write(File.join(workflows_root, filename), as_yaml(workflow.to_h)) + puts "writing #{filename}" + end end - def as_formatted_yaml(content) - Psych - .safe_dump(content, line_width: 120) - .lines - .drop(1) - .join - .strip - .gsub(/'on':\n/, "on:\n") + def as_yaml(content) + Psych.safe_dump(content, line_width: 120).lines.drop(1).join.strip.gsub(/'on':\n/, "on:\n") end def initialize(workflows_root, template_root) @@ -855,7 +698,4 @@ class CI attr_reader :workflows_root, :template_root end -CI.new( - File.join(__dir__, "../../.github/workflows/"), - __dir__ -).as_github_actions +CI.new(File.join(__dir__, "../../.github/workflows/"), __dir__).as_github_actions