Merge pull request #16063 from Homebrew/revert-16054-revert-16026-support-multiple-sockets-in-service-dsl
service: support multiple sockets in DSL
This commit is contained in:
commit
1c9caf96b7
@ -1,6 +1,7 @@
|
||||
# typed: true
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "ipaddr"
|
||||
require "extend/on_system"
|
||||
|
||||
module Homebrew
|
||||
@ -187,17 +188,33 @@ module Homebrew
|
||||
end
|
||||
end
|
||||
|
||||
sig { params(value: T.nilable(String)).returns(T.nilable(T::Hash[Symbol, String])) }
|
||||
SOCKET_STRING_REGEX = %r{^([a-z]+)://(.+):([0-9]+)$}i.freeze
|
||||
|
||||
sig {
|
||||
params(value: T.nilable(T.any(String, T::Hash[Symbol, String])))
|
||||
.returns(T.nilable(T::Hash[Symbol, T::Hash[Symbol, String]]))
|
||||
}
|
||||
def sockets(value = nil)
|
||||
case value
|
||||
when nil
|
||||
@sockets
|
||||
return @sockets if value.nil?
|
||||
|
||||
@sockets = case value
|
||||
when String
|
||||
match = T.must(value).match(%r{([a-z]+)://([a-z0-9.]+):([0-9]+)}i)
|
||||
{ listeners: value }
|
||||
when Hash
|
||||
value
|
||||
end.transform_values do |socket_string|
|
||||
match = socket_string.match(SOCKET_STRING_REGEX)
|
||||
raise TypeError, "Service#sockets a formatted socket definition as <type>://<host>:<port>" if match.blank?
|
||||
|
||||
type, host, port = match.captures
|
||||
@sockets = { host: host, port: port, type: type }
|
||||
|
||||
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
|
||||
|
||||
@ -410,12 +427,13 @@ module Homebrew
|
||||
|
||||
if @sockets.present?
|
||||
base[:Sockets] = {}
|
||||
base[:Sockets][:Listeners] = {
|
||||
SockNodeName: @sockets[:host],
|
||||
SockServiceName: @sockets[:port],
|
||||
SockProtocol: @sockets[:type].upcase,
|
||||
SockFamily: "IPv4v6",
|
||||
}
|
||||
@sockets.each do |name, info|
|
||||
base[:Sockets][name] = {
|
||||
SockNodeName: info[:host],
|
||||
SockServiceName: info[:port],
|
||||
SockProtocol: info[:type].upcase,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
if @cron.present? && @run_type == RUN_TYPE_CRON
|
||||
@ -511,7 +529,20 @@ module Homebrew
|
||||
.join(" ")
|
||||
end
|
||||
|
||||
sockets_string = "#{@sockets[:type]}://#{@sockets[:host]}:#{@sockets[:port]}" if @sockets.present?
|
||||
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
|
||||
|
||||
{
|
||||
name: name_params.presence,
|
||||
@ -531,7 +562,7 @@ module Homebrew
|
||||
restart_delay: @restart_delay,
|
||||
process_type: @process_type,
|
||||
macos_legacy_timers: @macos_legacy_timers,
|
||||
sockets: sockets_string,
|
||||
sockets: sockets_var,
|
||||
}.compact
|
||||
end
|
||||
|
||||
@ -565,8 +596,6 @@ module Homebrew
|
||||
raise ArgumentError, "Unexpected run command: #{api_hash["run"]}"
|
||||
end
|
||||
|
||||
hash[:keep_alive] = api_hash["keep_alive"].transform_keys(&:to_sym) if api_hash.key?("keep_alive")
|
||||
|
||||
if api_hash.key?("environment_variables")
|
||||
hash[:environment_variables] = api_hash["environment_variables"].to_h do |key, value|
|
||||
[key.to_sym, replace_placeholders(value)]
|
||||
@ -585,12 +614,22 @@ module Homebrew
|
||||
hash[key.to_sym] = replace_placeholders(value)
|
||||
end
|
||||
|
||||
%w[interval cron launch_only_once require_root restart_delay macos_legacy_timers sockets].each do |key|
|
||||
%w[interval cron launch_only_once require_root restart_delay macos_legacy_timers].each do |key|
|
||||
next if (value = api_hash[key]).nil?
|
||||
|
||||
hash[key.to_sym] = value
|
||||
end
|
||||
|
||||
%w[sockets keep_alive].each do |key|
|
||||
next unless (value = api_hash[key])
|
||||
|
||||
hash[key.to_sym] = if value.is_a?(Hash)
|
||||
value.transform_keys(&:to_sym)
|
||||
else
|
||||
value
|
||||
end
|
||||
end
|
||||
|
||||
hash
|
||||
end
|
||||
|
||||
|
||||
@ -14,6 +14,15 @@ describe Homebrew::Service do
|
||||
end
|
||||
end
|
||||
|
||||
def stub_formula_with_service_sockets(sockets_var)
|
||||
stub_formula do
|
||||
service do
|
||||
run opt_bin/"beanstalkd"
|
||||
sockets sockets_var
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#std_service_path_env" do
|
||||
it "returns valid std_service_path_env" do
|
||||
f = stub_formula do
|
||||
@ -102,43 +111,44 @@ describe Homebrew::Service do
|
||||
end
|
||||
|
||||
describe "#sockets" do
|
||||
it "throws for missing type" do
|
||||
f = stub_formula do
|
||||
service do
|
||||
run opt_bin/"beanstalkd"
|
||||
sockets "127.0.0.1:80"
|
||||
end
|
||||
end
|
||||
let(:sockets_type_error_message) { "Service#sockets a formatted socket definition as <type>://<host>:<port>" }
|
||||
|
||||
expect do
|
||||
f.service.manual_command
|
||||
end.to raise_error TypeError, "Service#sockets a formatted socket definition as <type>://<host>:<port>"
|
||||
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" }),
|
||||
].each do |f|
|
||||
expect { f.service.manual_command }.to raise_error TypeError, sockets_type_error_message
|
||||
end
|
||||
end
|
||||
|
||||
it "throws for missing host" do
|
||||
f = stub_formula do
|
||||
service do
|
||||
run opt_bin/"beanstalkd"
|
||||
sockets "tcp://:80"
|
||||
end
|
||||
[
|
||||
stub_formula_with_service_sockets("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
|
||||
|
||||
expect do
|
||||
f.service.manual_command
|
||||
end.to raise_error TypeError, "Service#sockets a formatted socket definition as <type>://<host>:<port>"
|
||||
end
|
||||
|
||||
it "throws for missing port" do
|
||||
f = stub_formula do
|
||||
service do
|
||||
run opt_bin/"beanstalkd"
|
||||
sockets "tcp://127.0.0.1"
|
||||
end
|
||||
[
|
||||
stub_formula_with_service_sockets("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
|
||||
|
||||
expect do
|
||||
f.service.manual_command
|
||||
end.to raise_error TypeError, "Service#sockets a formatted socket definition as <type>://<host>:<port>"
|
||||
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
|
||||
|
||||
@ -259,10 +269,57 @@ describe Homebrew::Service do
|
||||
end
|
||||
|
||||
it "returns valid plist with socket" do
|
||||
plist_expect = <<~EOS
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
\t<key>Label</key>
|
||||
\t<string>homebrew.mxcl.formula_name</string>
|
||||
\t<key>LimitLoadToSessionType</key>
|
||||
\t<array>
|
||||
\t\t<string>Aqua</string>
|
||||
\t\t<string>Background</string>
|
||||
\t\t<string>LoginWindow</string>
|
||||
\t\t<string>StandardIO</string>
|
||||
\t\t<string>System</string>
|
||||
\t</array>
|
||||
\t<key>ProgramArguments</key>
|
||||
\t<array>
|
||||
\t\t<string>#{HOMEBREW_PREFIX}/opt/formula_name/bin/beanstalkd</string>
|
||||
\t</array>
|
||||
\t<key>RunAtLoad</key>
|
||||
\t<true/>
|
||||
\t<key>Sockets</key>
|
||||
\t<dict>
|
||||
\t\t<key>listeners</key>
|
||||
\t\t<dict>
|
||||
\t\t\t<key>SockNodeName</key>
|
||||
\t\t\t<string>127.0.0.1</string>
|
||||
\t\t\t<key>SockProtocol</key>
|
||||
\t\t\t<string>TCP</string>
|
||||
\t\t\t<key>SockServiceName</key>
|
||||
\t\t\t<string>80</string>
|
||||
\t\t</dict>
|
||||
\t</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
EOS
|
||||
|
||||
[
|
||||
stub_formula_with_service_sockets("tcp://127.0.0.1:80"),
|
||||
stub_formula_with_service_sockets({ listeners: "tcp://127.0.0.1:80" }),
|
||||
].each do |f|
|
||||
plist = f.service.to_plist
|
||||
expect(plist).to eq(plist_expect)
|
||||
end
|
||||
end
|
||||
|
||||
it "returns valid plist with multiple sockets" do
|
||||
f = stub_formula do
|
||||
service do
|
||||
run [opt_bin/"beanstalkd", "test"]
|
||||
sockets "tcp://127.0.0.1:80"
|
||||
sockets socket: "tcp://0.0.0.0:80", socket_tls: "tcp://0.0.0.0:443"
|
||||
end
|
||||
end
|
||||
|
||||
@ -291,17 +348,24 @@ describe Homebrew::Service do
|
||||
\t<true/>
|
||||
\t<key>Sockets</key>
|
||||
\t<dict>
|
||||
\t\t<key>Listeners</key>
|
||||
\t\t<key>socket</key>
|
||||
\t\t<dict>
|
||||
\t\t\t<key>SockFamily</key>
|
||||
\t\t\t<string>IPv4v6</string>
|
||||
\t\t\t<key>SockNodeName</key>
|
||||
\t\t\t<string>127.0.0.1</string>
|
||||
\t\t\t<string>0.0.0.0</string>
|
||||
\t\t\t<key>SockProtocol</key>
|
||||
\t\t\t<string>TCP</string>
|
||||
\t\t\t<key>SockServiceName</key>
|
||||
\t\t\t<string>80</string>
|
||||
\t\t</dict>
|
||||
\t\t<key>socket_tls</key>
|
||||
\t\t<dict>
|
||||
\t\t\t<key>SockNodeName</key>
|
||||
\t\t\t<string>0.0.0.0</string>
|
||||
\t\t\t<key>SockProtocol</key>
|
||||
\t\t\t<string>TCP</string>
|
||||
\t\t\t<key>SockServiceName</key>
|
||||
\t\t\t<string>443</string>
|
||||
\t\t</dict>
|
||||
\t</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@ -1052,6 +1052,24 @@ The `sockets` method accepts a formatted socket definition as `<type>://<host>:<
|
||||
|
||||
Please note that sockets will be accessible on IPv4 and IPv6 addresses by default.
|
||||
|
||||
If you only need one socket and you don't care about the name (the default is `listeners`):
|
||||
|
||||
```rb
|
||||
service do
|
||||
run [opt_bin/"beanstalkd", "test"]
|
||||
sockets "tcp://127.0.0.1:80"
|
||||
end
|
||||
```
|
||||
|
||||
If you need multiple sockets and/or you want to specify the name:
|
||||
|
||||
```rb
|
||||
service do
|
||||
run [opt_bin/"beanstalkd", "test"]
|
||||
sockets http: "tcp://0.0.0.0:80", https: "tcp://0.0.0.0:443"
|
||||
end
|
||||
```
|
||||
|
||||
### Using environment variables
|
||||
|
||||
Homebrew has multiple levels of environment variable filtering which affects which variables are available to formulae.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user