From 215419daa505d000d76462e3e8a91316b4a4a218 Mon Sep 17 00:00:00 2001 From: apainintheneck Date: Fri, 29 Sep 2023 19:45:55 -0700 Subject: [PATCH] service: provide backwards compatibility for socket strings The previous PR changed how sockets were represented in the JSON API for formulae and that would cause problems when trying to install packages with service sockets. This provides backwards compatibility until all users have upgraded to versions of homebrew that can deserialize sockets hashes (maybe a couple weeks). Essentially, we store the socket string when serializing sockets that were originally defined with only the string parameter otherwise we serialize it to a hash. --- Library/Homebrew/service.rb | 30 ++++++++++++++++++++------- Library/Homebrew/test/service_spec.rb | 25 +++++++++++++--------- 2 files changed, 38 insertions(+), 17 deletions(-) diff --git a/Library/Homebrew/service.rb b/Library/Homebrew/service.rb index 3d1de39211..09e2fb6b59 100644 --- a/Library/Homebrew/service.rb +++ b/Library/Homebrew/service.rb @@ -1,6 +1,7 @@ # typed: true # frozen_string_literal: true +require "ipaddr" require "extend/on_system" module Homebrew @@ -187,7 +188,7 @@ module Homebrew end end - SOCKET_STRING_REGEX = %r{([a-z]+)://([a-z0-9.]+):([0-9]+)}i.freeze + SOCKET_STRING_REGEX = %r{^([a-z]+)://(.+):([0-9]+)$}i.freeze sig { params(value: T.nilable(T.any(String, T::Hash[Symbol, String]))) @@ -206,6 +207,13 @@ module Homebrew raise TypeError, "Service#sockets a formatted socket definition as ://:" if match.blank? type, host, port = match.captures + + begin + IPAddr.new(host) + rescue IPAddr::InvalidAddressError + raise TypeError, "Service#sockets expects a valid ipv4 or ipv6 host address" + end + { host: host, port: port, type: type } end end @@ -424,7 +432,6 @@ module Homebrew SockNodeName: info[:host], SockServiceName: info[:port], SockProtocol: info[:type].upcase, - SockFamily: "IPv4v6", } end end @@ -522,10 +529,19 @@ module Homebrew .join(" ") end - sockets_hash = if @sockets.present? - @sockets.transform_values do |info| - "#{info[:type]}://#{info[:host]}:#{info[:port]}" - end + sockets_var = if @sockets.present? + @sockets.transform_values { |info| "#{info[:type]}://#{info[:host]}:#{info[:port]}" } + .then do |sockets_hash| + # TODO: Remove this code when all users are running on versions of Homebrew + # that can process sockets hashes (this commit or later). + if sockets_hash.size == 1 && sockets_hash.key?(:listeners) + # When original #sockets argument was a string: `sockets "tcp://127.0.0.1:80"` + sockets_hash.fetch(:listeners) + else + # When original #sockets argument was a hash: `sockets http: "tcp://0.0.0.0:80"` + sockets_hash + end + end end { @@ -546,7 +562,7 @@ module Homebrew restart_delay: @restart_delay, process_type: @process_type, macos_legacy_timers: @macos_legacy_timers, - sockets: sockets_hash, + sockets: sockets_var, }.compact end diff --git a/Library/Homebrew/test/service_spec.rb b/Library/Homebrew/test/service_spec.rb index 7f28005fb0..85f1e8be51 100644 --- a/Library/Homebrew/test/service_spec.rb +++ b/Library/Homebrew/test/service_spec.rb @@ -116,7 +116,7 @@ describe Homebrew::Service do it "throws for missing type" do [ stub_formula_with_service_sockets("127.0.0.1:80"), - stub_formula_with_service_sockets({ "Socket" => "127.0.0.1:80" }), + stub_formula_with_service_sockets({ socket: "127.0.0.1:80" }), ].each do |f| expect { f.service.manual_command }.to raise_error TypeError, sockets_type_error_message end @@ -125,7 +125,7 @@ describe Homebrew::Service do it "throws for missing host" do [ stub_formula_with_service_sockets("tcp://:80"), - stub_formula_with_service_sockets({ "Socket" => "tcp://:80" }), + stub_formula_with_service_sockets({ socket: "tcp://:80" }), ].each do |f| expect { f.service.manual_command }.to raise_error TypeError, sockets_type_error_message end @@ -134,11 +134,22 @@ describe Homebrew::Service do it "throws for missing port" do [ stub_formula_with_service_sockets("tcp://127.0.0.1"), - stub_formula_with_service_sockets({ "Socket" => "tcp://127.0.0.1" }), + stub_formula_with_service_sockets({ socket: "tcp://127.0.0.1" }), ].each do |f| expect { f.service.manual_command }.to raise_error TypeError, sockets_type_error_message end end + + it "throws for invalid host" do + [ + stub_formula_with_service_sockets("tcp://300.0.0.1:80"), + stub_formula_with_service_sockets({ socket: "tcp://300.0.0.1:80" }), + ].each do |f| + expect do + f.service.manual_command + end.to raise_error TypeError, "Service#sockets expects a valid ipv4 or ipv6 host address" + end + end end describe "#manual_command" do @@ -283,8 +294,6 @@ describe Homebrew::Service do \t \t\tlisteners \t\t - \t\t\tSockFamily - \t\t\tIPv4v6 \t\t\tSockNodeName \t\t\t127.0.0.1 \t\t\tSockProtocol @@ -341,8 +350,6 @@ describe Homebrew::Service do \t \t\tsocket \t\t - \t\t\tSockFamily - \t\t\tIPv4v6 \t\t\tSockNodeName \t\t\t0.0.0.0 \t\t\tSockProtocol @@ -352,8 +359,6 @@ describe Homebrew::Service do \t\t \t\tsocket_tls \t\t - \t\t\tSockFamily - \t\t\tIPv4v6 \t\t\tSockNodeName \t\t\t0.0.0.0 \t\t\tSockProtocol @@ -1049,7 +1054,7 @@ describe Homebrew::Service do run_type: :immediate, working_dir: "/$HOME", cron: "0 0 * * 0", - sockets: { listeners: "tcp://0.0.0.0:80" }, + sockets: "tcp://0.0.0.0:80", } end