forest_admin_agent 1.30.7 → 1.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a71c83ef53d0ca0d546b9a574356d887be48eff91a4e2873dc8ee0003185522c
4
- data.tar.gz: ee7a21aecbd4941c88f492a16deb383f447fd1429081c911e033e23c27d5ca9f
3
+ metadata.gz: e0734ce0f980bab395e86e186c571c177f69542ed1784004a848df4f6bddf345
4
+ data.tar.gz: 87f57b3ea71d7c1c67597be08d15835e0387a81ac65fff8988bab4821cd74208
5
5
  SHA512:
6
- metadata.gz: e67bfe201f6aa1d90fb57b9f41a08ca59795775c93ca33b517fb8f5fd7eb134c9153f8e55f44c0c601702277f5a7f930cb7b46f5e3ddca7a50d4427c747217ef
7
- data.tar.gz: 5e3c73296c1ba8e49b2380198450c51cfc4f7ce9b4e24e244d16b8bec2b4b300024948e0479591a20e86876f0c57861ad0a69aa4c0f6f907ef8b9a2d8e1ca7bc
6
+ metadata.gz: ef22c59d7a92eb560bee230de72590c5f3ddd3ddb278ae45215fd09f9e503e7f4aecffd2174abab274bea1c75fd74bc5eb33d1120ee28e53355f6b6b38c114f2
7
+ data.tar.gz: 3133bc41d2eb45f5c948007a161312b1ab2f760c6dfaadc1852453a15877e3eca54489bbc9400cbf51d450822a09dceca624bf261d65f7bb222b72f4fe077cc0
@@ -16,7 +16,7 @@ module ForestAdminAgent
16
16
  end
17
17
 
18
18
  def expiration_in_seconds
19
- Time.now.to_i + 1.hour
19
+ (Time.now + 1.hour).to_i
20
20
  end
21
21
 
22
22
  def make_jwt
@@ -63,7 +63,8 @@ module ForestAdminAgent
63
63
  { name: 'associate_related', handler: -> { Resources::Related::AssociateRelated.new.routes } },
64
64
  { name: 'dissociate_related', handler: -> { Resources::Related::DissociateRelated.new.routes } },
65
65
  { name: 'update_related', handler: -> { Resources::Related::UpdateRelated.new.routes } },
66
- { name: 'update_field', handler: -> { Resources::UpdateField.new.routes } }
66
+ { name: 'update_field', handler: -> { Resources::UpdateField.new.routes } },
67
+ { name: 'workflow_executor_proxy', handler: -> { Workflow::WorkflowExecutorProxy.new.routes } }
67
68
  ]
68
69
 
69
70
  all_routes = {}
@@ -0,0 +1,144 @@
1
+ require 'faraday'
2
+
3
+ module ForestAdminAgent
4
+ module Routes
5
+ module Workflow
6
+ # Forwards workflow-execution traffic from the agent to the workflow executor.
7
+ # Mounted only when the integrator sets `workflow_executor_url`
8
+ class WorkflowExecutorProxy < AbstractAuthenticatedRoute
9
+ AGENT_PREFIX = '/_internal/workflow-executions'.freeze
10
+ EXECUTOR_PREFIX = '/runs'.freeze
11
+ FORWARDED_HEADERS = %w[Authorization Cookie].freeze
12
+ ROUTING_KEYS = %w[run_id route_alias controller action format].freeze
13
+ OPEN_TIMEOUT = 2
14
+ GET_TIMEOUT = 10
15
+ TRIGGER_TIMEOUT = 120
16
+
17
+ def setup_routes
18
+ return self unless executor_configured?
19
+
20
+ add_route(
21
+ 'forest_workflow_run_show',
22
+ 'get',
23
+ "#{AGENT_PREFIX}/:run_id",
24
+ ->(args) { handle_request(:get, args) }
25
+ )
26
+ add_route(
27
+ 'forest_workflow_run_trigger',
28
+ 'post',
29
+ "#{AGENT_PREFIX}/:run_id/trigger",
30
+ ->(args) { handle_request(:post, args) }
31
+ )
32
+
33
+ self
34
+ end
35
+
36
+ def handle_request(method, args = {})
37
+ build(args)
38
+
39
+ base_url = configured_executor_url
40
+ run_id = args.dig(:params, 'run_id') || args.dig(:params, :run_id)
41
+ path = build_path(run_id, method)
42
+ response = forward(method, base_url, path, args)
43
+
44
+ {
45
+ content: response.body,
46
+ status: response.status,
47
+ headers: forwarded_response_headers(response)
48
+ }
49
+ end
50
+
51
+ private
52
+
53
+ def executor_configured?
54
+ url = ForestAdminAgent::Facades::Container.config_from_cache[:workflow_executor_url]
55
+ !(url.nil? || url.to_s.strip.empty?)
56
+ rescue StandardError
57
+ # Container not yet populated (e.g. boot-order edge case): treat as disabled.
58
+ false
59
+ end
60
+
61
+ def configured_executor_url
62
+ url = ForestAdminAgent::Facades::Container.config_from_cache[:workflow_executor_url]
63
+ if url.nil? || url.to_s.strip.empty?
64
+ raise Http::Exceptions::NotFoundError, 'Workflow executor proxy is not configured'
65
+ end
66
+
67
+ url.to_s.sub(%r{/+\z}, '')
68
+ end
69
+
70
+ def build_path(run_id, method)
71
+ suffix = method == :post ? '/trigger' : ''
72
+ "#{EXECUTOR_PREFIX}/#{run_id}#{suffix}"
73
+ end
74
+
75
+ def forward(method, base_url, path, args)
76
+ query = forwarded_query_params(args[:params])
77
+ headers = forwarded_request_headers(args[:headers])
78
+ body = forwarded_body(method, args[:params])
79
+ target_url = "#{base_url}#{path}"
80
+
81
+ client = build_client(timeout_for(method))
82
+ client.run_request(method, target_url, body, headers) do |req|
83
+ req.params.update(query) unless query.empty?
84
+ end
85
+ rescue Faraday::TimeoutError => e
86
+ raise Http::Exceptions::ServiceUnavailableError.new('Workflow executor timed out', cause: e)
87
+ rescue Faraday::ConnectionFailed => e
88
+ raise Http::Exceptions::ServiceUnavailableError.new('Workflow executor unreachable', cause: e)
89
+ end
90
+
91
+ def timeout_for(method)
92
+ method == :get ? GET_TIMEOUT : TRIGGER_TIMEOUT
93
+ end
94
+
95
+ def build_client(request_timeout)
96
+ Faraday.new(request: { open_timeout: OPEN_TIMEOUT, timeout: request_timeout }) do |f|
97
+ f.request :json
98
+ f.response :json, content_type: /\bjson$/
99
+ f.adapter Faraday.default_adapter
100
+ end
101
+ end
102
+
103
+ # Strip Rails-injected routing keys; keep only true client query params.
104
+ def forwarded_query_params(params)
105
+ return {} unless params.is_a?(Hash)
106
+
107
+ params.each_with_object({}) do |(key, value), acc|
108
+ next if ROUTING_KEYS.include?(key.to_s)
109
+ next if value.is_a?(Hash) || value.is_a?(Array) # 'data' body, etc.
110
+
111
+ acc[key.to_s] = value
112
+ end
113
+ end
114
+
115
+ def forwarded_request_headers(headers)
116
+ return {} unless headers.is_a?(Hash)
117
+
118
+ FORWARDED_HEADERS.each_with_object({}) do |name, acc|
119
+ value = headers[name] || headers[name.downcase] || headers["HTTP_#{name.upcase}"]
120
+ acc[name] = value if value && !value.to_s.empty?
121
+ end
122
+ end
123
+
124
+ def forwarded_body(method, params)
125
+ return nil if method == :get
126
+ return nil unless params.is_a?(Hash)
127
+
128
+ # JSON request bodies arrive parsed under :data when sent as JSON:API,
129
+ # or as the raw top-level params hash otherwise. Prefer :data when
130
+ # present; fall back to a sanitized copy of params.
131
+ body = params['data'] || params[:data]
132
+ return body if body
133
+
134
+ params.reject { |key, _| ROUTING_KEYS.include?(key.to_s) }
135
+ end
136
+
137
+ def forwarded_response_headers(response)
138
+ content_type = response.headers['content-type'] || response.headers['Content-Type']
139
+ content_type ? { 'Content-Type' => content_type } : {}
140
+ end
141
+ end
142
+ end
143
+ end
144
+ end
@@ -6,7 +6,7 @@ module ForestAdminAgent
6
6
  module Schema
7
7
  class SchemaEmitter
8
8
  LIANA_NAME = "agent-ruby"
9
- LIANA_VERSION = "1.30.7"
9
+ LIANA_VERSION = "1.31.0"
10
10
 
11
11
  def self.generate(datasource)
12
12
  datasource.collections
@@ -1,3 +1,3 @@
1
1
  module ForestAdminAgent
2
- VERSION = "1.30.7"
2
+ VERSION = "1.31.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: forest_admin_agent
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.30.7
4
+ version: 1.31.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matthieu
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2026-05-26 00:00:00.000000000 Z
12
+ date: 2026-06-08 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
@@ -375,6 +375,7 @@ files:
375
375
  - lib/forest_admin_agent/routes/security/authentication.rb
376
376
  - lib/forest_admin_agent/routes/security/scope_invalidation.rb
377
377
  - lib/forest_admin_agent/routes/system/health_check.rb
378
+ - lib/forest_admin_agent/routes/workflow/workflow_executor_proxy.rb
378
379
  - lib/forest_admin_agent/serializer/forest_chart_serializer.rb
379
380
  - lib/forest_admin_agent/serializer/forest_serializer.rb
380
381
  - lib/forest_admin_agent/serializer/forest_serializer_override.rb
@@ -413,7 +414,6 @@ files:
413
414
  - sig/forest_admin_agent/builder/agent_factory.rbs
414
415
  - sig/forest_admin_agent/facades/container.rbs
415
416
  - sig/forest_admin_agent/http/router.rbs
416
- - sig/forest_admin_agent/routes/abstract_route.rbs
417
417
  - sig/forest_admin_agent/routes/security/authentication.rbs
418
418
  - sig/forest_admin_agent/routes/system/health_check.rbs
419
419
  homepage: https://www.forestadmin.com
@@ -1,12 +0,0 @@
1
- module ForestAdminAgent
2
- module Routes
3
- class AbstractRoute
4
- def initialize : -> void
5
- def routes: -> {}
6
- def add_route: (String, String,String, String) -> void
7
- def setup: -> AbstractRoute
8
-
9
- def setup_routes: -> void
10
- end
11
- end
12
- end