Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,14 @@ jobs:
run: ./go ${{ matrix.language }}:release
secrets: inherit

verify:
name: Verify Published Packages
needs: docs
uses: ./.github/workflows/bazel.yml
with:
name: Verify packages
run: ./go all:verify

docs:
name: Update ${{ matrix.language }} Documentation
needs: [stage, publish, github-release]
Expand Down Expand Up @@ -150,7 +158,7 @@ jobs:

unrestrict-trunk:
name: Unrestrict Trunk Branch
needs: [github-release]
needs: verify
uses: ./.github/workflows/restrict-trunk.yml
with:
restrict: false
Expand Down Expand Up @@ -198,7 +206,7 @@ jobs:
on-release-failure:
name: On Release Failure
runs-on: ubuntu-latest
needs: [stage, publish, docs, github-release, update-version, nightly, mirror]
needs: [stage, publish, docs, github-release, update-version, nightly, mirror, verify]
if: failure()
steps:
- uses: actions/checkout@v4
Expand All @@ -217,5 +225,6 @@ jobs:
• Nightly Version Updated: ${{ needs.update-version.result }}
• Nightly Packages: ${{ needs.nightly.result }}
• Mirror Updated: ${{ needs.mirror.result }}
• Packages Verified: ${{ needs.verify.result }}
MSG_MINIMAL: actions url
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
148 changes: 136 additions & 12 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,27 @@ RELEASE_CREDENTIALS = {
dotnet_nightly: {env: [%w[GITHUB_TOKEN]]}
}.freeze

def verify_package_published(url)
puts "Verifying #{url}..."
uri = URI(url)
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https',
open_timeout: 10, read_timeout: 10) { |http| http.request(Net::HTTP::Get.new(uri)) }
raise "Package not published: #{url}" unless res.is_a?(Net::HTTPSuccess)

puts 'Verified!'
end

def sonatype_api_post(url, token)
uri = URI(url)
req = Net::HTTP::Post.new(uri)
req['Authorization'] = "Basic #{token}"

res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
raise "Sonatype API error (#{res.code}): #{res.body}" unless res.is_a?(Net::HTTPSuccess)

res.body.to_s.empty? ? {} : JSON.parse(res.body)
end

def credential_valid?(cred)
has_env = cred[:env]&.all? { |vars| vars.any? { |v| ENV.fetch(v, nil) } }
has_file = cred[:file]&.call
Expand Down Expand Up @@ -603,6 +624,11 @@ namespace :node do
Bazel.execute('run', ['--config=release'], '//javascript/selenium-webdriver:selenium-webdriver.publish')
end

desc 'Verify Node package is published on npm'
task :verify do
verify_package_published("https://registry.npmjs.org/selenium-webdriver/#{node_version}")
end

task deploy: :release

desc 'Generate Node documentation'
Expand Down Expand Up @@ -668,6 +694,11 @@ namespace :py do
Bazel.execute('run', ['--config=release'], command)
end

desc 'Verify Python package is published on PyPI'
task :verify do
verify_package_published("https://pypi.org/pypi/selenium/#{python_version}/json")
end

desc 'generate and copy files required for local development'
task :local_dev do
Bazel.execute('build', [], '//py:selenium')
Expand Down Expand Up @@ -870,6 +901,16 @@ namespace :rb do
end
end

desc 'Verify Ruby packages are published on RubyGems'
task :verify do
patch_release = ruby_version.split('.').fetch(2, '0').to_i.positive?

verify_package_published("https://rubygems.org/api/v2/rubygems/selenium-webdriver/versions/#{ruby_version}.json")
unless patch_release
verify_package_published("https://rubygems.org/api/v2/rubygems/selenium-devtools/versions/#{ruby_version}.json")
end
end

desc 'Generate Ruby documentation'
task :docs do |_task, arguments|
if ruby_version.include?('nightly') && !arguments.to_a.include?('force')
Expand Down Expand Up @@ -1027,6 +1068,12 @@ namespace :dotnet do
Bazel.execute('run', ['--config=release'], '//dotnet:publish')
end

desc 'Verify .NET packages are published on NuGet'
task :verify do
verify_package_published("https://api.nuget.org/v3/registration5-semver1/selenium.webdriver/#{dotnet_version}.json")
verify_package_published("https://api.nuget.org/v3/registration5-semver1/selenium.support/#{dotnet_version}.json")
end

desc 'Generate .NET documentation'
task :docs do |_task, arguments|
if dotnet_version.include?('nightly') && !arguments.to_a.include?('force')
Expand Down Expand Up @@ -1123,34 +1170,95 @@ namespace :java do
end

desc 'Publish to sonatype'
task :publish do |_task|
task :publish do
read_m2_user_pass unless ENV['MAVEN_PASSWORD'] && ENV['MAVEN_USER']
user = ENV.fetch('MAVEN_USER')
pass = ENV.fetch('MAVEN_PASSWORD')
token = Base64.strict_encode64("#{user}:#{pass}")

puts 'Triggering Sonatype validation...'
uri = URI('https://ossrh-staging-api.central.sonatype.com/manual/upload/defaultRepository/org.seleniumhq')
encoded = Base64.strict_encode64("#{user}:#{pass}")

puts 'Triggering validation POST to Central Portal...'
req = Net::HTTP::Post.new(uri)
req['Authorization'] = "Basic #{encoded}"
req['Authorization'] = "Basic #{token}"
req['Accept'] = '*/*'
req['Content-Length'] = '0'

res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true,
open_timeout: 10, read_timeout: 60) do |http|
http.request(req)
begin
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true,
open_timeout: 10, read_timeout: 180) do |http|
http.request(req)
end
rescue Net::ReadTimeout, Net::OpenTimeout => e
warn <<~MSG
Request timed out waiting for deployment ID.
The deployment may still have been created on the server.
Check https://central.sonatype.com/publishing/deployments for pending deployments,
then run: ./go java:publish_deployment <deployment_id>
MSG
raise e
end

if res.is_a?(Net::HTTPSuccess)
puts "Manual upload triggered successfully (HTTP #{res.code})"
deployment_id = res.body.strip
puts "Got deployment ID: #{deployment_id}"
Rake::Task['java:publish_deployment'].invoke(deployment_id)
else
warn "Manual upload failed (HTTP #{res.code}): #{res.code} #{res.message}"
warn res.body if res.body && !res.body.empty?
warn "Failed to get deployment ID (HTTP #{res.code}): #{res.body}"
exit(1)
end
end

desc 'Publish a Sonatype deployment by ID'
task :publish_deployment, [:deployment_id] do |_task, arguments|
deployment_id = arguments[:deployment_id] || ENV.fetch('DEPLOYMENT_ID', nil)
if deployment_id.nil? || deployment_id.empty?
raise 'Deployment ID required: ./go java:publish_deployment[ID] or set DEPLOYMENT_ID'
end

read_m2_user_pass unless ENV['MAVEN_PASSWORD'] && ENV['MAVEN_USER']
token = Base64.strict_encode64("#{ENV.fetch('MAVEN_USER')}:#{ENV.fetch('MAVEN_PASSWORD')}")

encoded_id = URI.encode_www_form_component(deployment_id.strip)
status = {}
max_attempts = 60
delay = 5
max_attempts.times do |attempt|
status = sonatype_api_post("https://central.sonatype.com/api/v1/publisher/status?id=#{encoded_id}", token)
state = status['deploymentState']
puts "Deployment state: #{state}"

case state
when 'VALIDATED', 'PUBLISHED' then break
when 'FAILED' then raise "Deployment failed: #{status['errors']}"
end
sleep(delay)
rescue StandardError => e
warn "API error (attempt #{attempt + 1}/#{max_attempts}): #{e.message}"
sleep(delay) unless attempt == max_attempts - 1
end

return if status['deploymentState'] == 'PUBLISHED'

raise "Timed out after #{(max_attempts * delay) / 60} minutes waiting for validation" unless state == 'VALIDATED'

expected = java_release_targets.size
actual = status['purls']&.size || 0
if actual != expected
raise "Expected #{expected} packages but found #{actual}. " \
'Drop the deployment at https://central.sonatype.com/publishing/deployments and redeploy.'
end

puts 'Publishing deployed packages...'
sonatype_api_post("https://central.sonatype.com/api/v1/publisher/deployment/#{encoded_id}", token)
puts "Published! Deployment ID: #{deployment_id}"
end

desc 'Verify Java packages are published on Maven Central'
task :verify do
verify_package_published("https://repo1.maven.org/maven2/org/seleniumhq/selenium/selenium-java/#{java_version}/selenium-java-#{java_version}.pom")
end

desc 'Install jars to local m2 directory'
task install: :'maven-install'

Expand Down Expand Up @@ -1338,8 +1446,24 @@ namespace :all do
desc 'Validate release credentials for all languages without releasing'
task :check_credentials do |_task, arguments|
nightly = arguments.to_a.include?('nightly')
langs = nightly ? %i[java dotnet_nightly] : %i[java java_gpg python ruby node dotnet]
check_credentials(langs)

if nightly
check_credentials(%i[java dotnet_nightly])
else
check_credentials(%i[java java_gpg dotnet])
setup_pypirc
setup_gem_credentials
setup_npm_auth
end
end

desc 'Verify all packages are published to their registries'
task :verify do
Rake::Task['java:verify'].invoke
Rake::Task['py:verify'].invoke
Rake::Task['rb:verify'].invoke
Rake::Task['dotnet:verify'].invoke
Rake::Task['node:verify'].invoke
end

desc 'Release all artifacts for all language bindings'
Expand Down