chronos-ruby 0.7.0.pre.1 → 0.9.0.pre.1

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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +49 -0
  3. data/README.md +55 -9
  4. data/contracts/apm-batch-v1.schema.json +1 -1
  5. data/contracts/dependencies-v1.schema.json +38 -0
  6. data/contracts/deploy-v1.schema.json +26 -0
  7. data/contracts/event-v1.schema.json +15 -1
  8. data/docs/adr/ADR-016-explicit-observability-integrations.md +25 -0
  9. data/docs/adr/ADR-017-deploy-events-and-correlation.md +25 -0
  10. data/docs/architecture.md +12 -1
  11. data/docs/compatibility.md +4 -0
  12. data/docs/configuration.md +24 -3
  13. data/docs/data-collected.md +11 -4
  14. data/docs/examples/plain-ruby.md +13 -1
  15. data/docs/modules/apm-aggregation.md +2 -2
  16. data/docs/modules/cache-observability.md +16 -0
  17. data/docs/modules/configuration.md +4 -0
  18. data/docs/modules/dependencies.md +18 -0
  19. data/docs/modules/deploy-tracking.md +86 -0
  20. data/docs/modules/external-http.md +27 -0
  21. data/docs/modules/rails-legacy.md +1 -1
  22. data/docs/modules/sidekiq-legacy.md +1 -1
  23. data/docs/modules/telemetry-events.md +4 -2
  24. data/docs/performance.md +30 -1
  25. data/docs/privacy-lgpd.md +20 -2
  26. data/docs/troubleshooting.md +25 -1
  27. data/lib/chronos/agent.rb +43 -0
  28. data/lib/chronos/application/apm_aggregator.rb +7 -10
  29. data/lib/chronos/application/apm_error_classifier.rb +27 -0
  30. data/lib/chronos/application/capture_telemetry.rb +18 -1
  31. data/lib/chronos/application/dependency_reporter.rb +140 -0
  32. data/lib/chronos/application/remote_configuration.rb +3 -1
  33. data/lib/chronos/capistrano.rb +4 -0
  34. data/lib/chronos/configuration/apm_validation.rb +28 -1
  35. data/lib/chronos/configuration.rb +25 -3
  36. data/lib/chronos/core/cache_normalizer.rb +99 -0
  37. data/lib/chronos/core/correlation_context.rb +65 -0
  38. data/lib/chronos/core/deploy_normalizer.rb +102 -0
  39. data/lib/chronos/core/payload_serializer.rb +4 -1
  40. data/lib/chronos/core/telemetry_event.rb +31 -4
  41. data/lib/chronos/integrations/capistrano.rb +61 -0
  42. data/lib/chronos/integrations/net_http.rb +165 -0
  43. data/lib/chronos/net_http.rb +4 -0
  44. data/lib/chronos/observability_facade.rb +50 -0
  45. data/lib/chronos/rails/notifications_subscriber.rb +6 -5
  46. data/lib/chronos/version.rb +1 -1
  47. data/lib/chronos.rb +8 -0
  48. metadata +19 -1
@@ -2,7 +2,7 @@ module Chronos
2
2
  module Internal
3
3
  # Validates bounded APM aggregation, histogram, and detector settings.
4
4
  #
5
- # @responsibility Return configuration errors for version 0.7 APM options.
5
+ # @responsibility Return configuration errors for bounded APM and observability options.
6
6
  # @motivation Keep the general configuration validator focused and maintainable.
7
7
  # @limits It validates shape and bounds but does not allocate aggregator state.
8
8
  # @collaborators Chronos::Configuration predicates and attributes.
@@ -55,6 +55,33 @@ module Chronos
55
55
 
56
56
  values.each_cons(2).all? { |left, right| right > left }
57
57
  end
58
+
59
+ def observability_errors
60
+ errors = []
61
+ errors << "external_http_enabled must be true or false" unless boolean?(external_http_enabled)
62
+ errors << "external_http_trace_headers must be true or false" unless boolean?(external_http_trace_headers)
63
+ errors << "cache_key_mode must be :none or :sha256" unless [:none, :sha256].include?(cache_key_mode)
64
+ errors << "dependency_reporting must be true or false" unless boolean?(dependency_reporting)
65
+ unless dependency_max_items.is_a?(Integer) && dependency_max_items >= 1 && dependency_max_items <= 200
66
+ errors << "dependency_max_items must be between 1 and 200"
67
+ end
68
+ correlation_attributes.each do |name, value|
69
+ errors << "#{name} must be a String with at most 128 bytes" unless bounded_optional_string?(value, 128)
70
+ end
71
+ errors
72
+ end
73
+
74
+ def correlation_attributes
75
+ {:revision => revision, :deploy_id => deploy_id, :region => region, :instance_id => instance_id}
76
+ end
77
+
78
+ def bounded_optional_string?(value, limit)
79
+ value.nil? || (value.is_a?(String) && value.bytesize <= limit)
80
+ end
81
+
82
+ def boolean?(value)
83
+ [true, false].include?(value)
84
+ end
58
85
  end
59
86
  end
60
87
  end
@@ -32,7 +32,8 @@ module Chronos
32
32
 
33
33
  ATTRIBUTES = [
34
34
  :project_id, :project_key, :host, :environment, :app_version,
35
- :service_name, :root_directory, :logger, :timeout, :open_timeout,
35
+ :service_name, :revision, :deploy_id, :region, :instance_id,
36
+ :root_directory, :logger, :timeout, :open_timeout,
36
37
  :queue_size, :workers, :enabled, :error_notifications,
37
38
  :ignored_environments, :proxy, :ssl_verify, :user_agent,
38
39
  :max_payload_size, :gzip, :blocklist_keys, :allowlist_keys,
@@ -46,7 +47,8 @@ module Chronos
46
47
  :apm_enabled, :apm_max_groups, :apm_flush_count, :apm_batch_size,
47
48
  :apm_max_queries_per_request, :apm_slow_query_threshold_ms,
48
49
  :apm_long_transaction_threshold_ms, :apm_n_plus_one_threshold,
49
- :apm_histogram_buckets
50
+ :apm_histogram_buckets, :external_http_enabled, :external_http_trace_headers,
51
+ :cache_key_mode, :dependency_reporting, :dependency_max_items
50
52
  ].freeze
51
53
 
52
54
  attr_accessor(*ATTRIBUTES)
@@ -57,6 +59,8 @@ module Chronos
57
59
  initialize_resilience_defaults
58
60
  initialize_rails_defaults
59
61
  initialize_apm_defaults
62
+ initialize_observability_defaults
63
+ initialize_correlation_defaults
60
64
  end
61
65
 
62
66
  def snapshot
@@ -86,6 +90,7 @@ module Chronos
86
90
  errors.concat(privacy_errors)
87
91
  errors.concat(context_errors)
88
92
  errors.concat(apm_errors)
93
+ errors.concat(observability_errors)
89
94
  errors
90
95
  end
91
96
 
@@ -117,6 +122,13 @@ module Chronos
117
122
  @breadcrumb_max_bytes = 2048
118
123
  end
119
124
 
125
+ def initialize_correlation_defaults
126
+ @revision = nil
127
+ @deploy_id = nil
128
+ @region = nil
129
+ @instance_id = nil
130
+ end
131
+
120
132
  def initialize_rails_defaults
121
133
  @rails_enabled = true
122
134
  @rails_capture_in_console = false
@@ -136,6 +148,14 @@ module Chronos
136
148
  @apm_histogram_buckets = [5.0, 10.0, 25.0, 50.0, 100.0, 250.0, 500.0, 1000.0, 2500.0, 5000.0]
137
149
  end
138
150
 
151
+ def initialize_observability_defaults
152
+ @external_http_enabled = false
153
+ @external_http_trace_headers = true
154
+ @cache_key_mode = :none
155
+ @dependency_reporting = true
156
+ @dependency_max_items = 100
157
+ end
158
+
139
159
  def initialize_privacy_defaults
140
160
  @blocklist_keys = DEFAULT_BLOCKLIST_KEYS.dup
141
161
  @allowlist_keys = []
@@ -155,7 +175,9 @@ module Chronos
155
175
  @remote_configuration = true
156
176
  @remote_config_max_bytes = 4096
157
177
  @sampling_rate = 1.0
158
- @enabled_event_types = ["exception", "request", "query", "job", "cache", "metric_batch"]
178
+ @enabled_event_types = [
179
+ "exception", "request", "query", "job", "cache", "external_http", "dependencies", "deploy", "metric_batch"
180
+ ]
159
181
  @max_remote_send_interval = 60.0
160
182
  end
161
183
 
@@ -0,0 +1,99 @@
1
+ require "digest"
2
+
3
+ module Chronos
4
+ module Core
5
+ # Produces bounded cache metadata without exposing raw cache keys or values.
6
+ #
7
+ # @responsibility Normalize operation, backend, namespace, outcome, and optional key hash.
8
+ # @motivation Cache telemetry needs diagnostic identity without leaking application keys.
9
+ # @limits It never reads cache values and hashes keys only after explicit opt-in.
10
+ # @collaborators Rails notification subscriber and Chronos configuration.
11
+ # @thread_safety Instances hold only immutable scalar configuration.
12
+ # @compatibility Ruby 2.2.10 through Ruby 2.6.
13
+ # @example
14
+ # CacheNormalizer.new("project", :none).call(name, payload)
15
+ # @errors Unreadable fields become empty bounded strings.
16
+ # @performance Constant work plus SHA-256 only when key hashing is enabled.
17
+ class CacheNormalizer
18
+ def initialize(project_id, key_mode)
19
+ @project_id = project_id.to_s
20
+ @key_mode = key_mode
21
+ end
22
+
23
+ def call(name, payload = {})
24
+ hit = hit_value(name, payload)
25
+ result = {
26
+ "operation" => bounded(name.to_s.split(".").first, 64),
27
+ "backend" => backend(payload),
28
+ "namespace" => bounded(namespace(payload).to_s, 128),
29
+ "hit" => hit,
30
+ "outcome" => outcome(hit)
31
+ }
32
+ result["key_hash"] = key_hash(value(payload, :key)) if @key_mode == :sha256
33
+ result.delete_if { |key, child| child.to_s.empty? && key != "hit" }
34
+ rescue StandardError
35
+ {"operation" => "cache", "outcome" => "unknown"}
36
+ end
37
+
38
+ private
39
+
40
+ def outcome(hit)
41
+ return "unknown" if hit.nil?
42
+
43
+ hit ? "hit" : "miss"
44
+ end
45
+
46
+ def backend(payload)
47
+ store = value(payload, :store)
48
+ return "" if store.nil?
49
+
50
+ name = store.is_a?(String) || store.is_a?(Symbol) ? store.to_s : store.class.name.to_s
51
+ bounded(name, 128)
52
+ rescue StandardError
53
+ ""
54
+ end
55
+
56
+ def namespace(payload)
57
+ direct = value(payload, :namespace)
58
+ return direct unless direct.nil?
59
+
60
+ value(value(payload, :options), :namespace)
61
+ end
62
+
63
+ def hit_value(name, payload)
64
+ return true if name.to_s.start_with?("cache_fetch_hit")
65
+
66
+ hit = value(payload, :hit)
67
+ [true, false].include?(hit) ? hit : nil
68
+ end
69
+
70
+ def key_hash(key)
71
+ return nil if key.nil?
72
+
73
+ Digest::SHA256.hexdigest("#{@project_id}:cache:#{safe_key(key)}")
74
+ end
75
+
76
+ def safe_key(key)
77
+ text = key.to_s
78
+ text = text.scrub("?") if text.respond_to?(:scrub)
79
+ text.bytesize > 2048 ? text.byteslice(0, 2048) : text
80
+ rescue StandardError
81
+ "[UNREADABLE]"
82
+ end
83
+
84
+ def value(hash, key)
85
+ return nil unless hash.is_a?(Hash)
86
+
87
+ hash.key?(key) ? hash[key] : hash[key.to_s]
88
+ end
89
+
90
+ def bounded(value, limit)
91
+ text = value.to_s
92
+ text = text.scrub("?") if text.respond_to?(:scrub)
93
+ text.bytesize > limit ? text.byteslice(0, limit) : text
94
+ rescue StandardError
95
+ ""
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,65 @@
1
+ module Chronos
2
+ module Core
3
+ # Builds the bounded release/deploy correlation shared by every event envelope.
4
+ #
5
+ # @responsibility Normalize release, revision, deploy, environment, service, region, and instance.
6
+ # @motivation Give the SaaS stable before/after-deploy dimensions without integration-specific fields.
7
+ # @limits Values come only from explicit configuration or caller overrides; no environment is scanned.
8
+ # @collaborators PayloadSerializer, TelemetrySerializer, and Configuration::Snapshot.
9
+ # @thread_safety Instances hold immutable configuration and calls allocate independent frozen hashes.
10
+ # @compatibility Ruby 2.2.10 through Ruby 2.6.
11
+ # @example
12
+ # CorrelationContext.new(config).call("revision" => "abc123")
13
+ # @errors Unreadable optional values become nil and do not escape.
14
+ # @performance Seven strings are copied with a fixed 128-byte maximum each.
15
+ class CorrelationContext
16
+ FIELDS = %w(release revision deploy_id environment service region instance).freeze
17
+
18
+ def initialize(config)
19
+ @config = config
20
+ end
21
+
22
+ def call(overrides = {})
23
+ values = defaults.merge(string_hash(overrides))
24
+ result = FIELDS.each_with_object({}) do |name, correlation|
25
+ correlation[name.freeze] = bounded(values[name], 128)
26
+ end
27
+ result.each_value { |value| value.freeze if value }
28
+ result.freeze
29
+ rescue StandardError
30
+ FIELDS.each_with_object({}) { |name, fallback| fallback[name] = nil }.freeze
31
+ end
32
+
33
+ private
34
+
35
+ def defaults
36
+ {
37
+ "release" => @config.app_version, "revision" => @config.revision,
38
+ "deploy_id" => @config.deploy_id, "environment" => @config.environment,
39
+ "service" => @config.service_name, "region" => @config.region,
40
+ "instance" => @config.instance_id
41
+ }
42
+ end
43
+
44
+ def string_hash(value)
45
+ return {} unless value.is_a?(Hash)
46
+
47
+ value.each_with_object({}) do |(key, child), result|
48
+ result[key.to_s] = child if key.is_a?(String) || key.is_a?(Symbol)
49
+ end
50
+ end
51
+
52
+ def bounded(value, limit)
53
+ return nil if value.nil?
54
+ return nil unless value.is_a?(String) || value.is_a?(Symbol) || value.is_a?(Numeric)
55
+
56
+ text = value.to_s
57
+ text = text.scrub("?") if text.respond_to?(:scrub)
58
+ text = text.byteslice(0, limit) if text.bytesize > limit
59
+ text
60
+ rescue StandardError
61
+ nil
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,102 @@
1
+ require "securerandom"
2
+ require "uri"
3
+
4
+ module Chronos
5
+ module Core
6
+ # Normalizes explicit deployment metadata into the versioned deploy payload.
7
+ #
8
+ # @responsibility Validate required deploy identity and bound every public API field.
9
+ # @motivation Deployment commands need predictable safe input independent of frameworks.
10
+ # @limits It does not inspect Git, environment variables, credentials, or deployment systems.
11
+ # @collaborators Chronos.notify_deploy, CorrelationContext, and Configuration::Snapshot.
12
+ # @thread_safety Stateless apart from immutable configuration and an injected ID generator.
13
+ # @compatibility Ruby 2.2.10 through Ruby 2.6.
14
+ # @example
15
+ # DeployNormalizer.new(config).call(:revision => "abc", :version => "1.2.3")
16
+ # @errors Missing environment or both revision/version raise ArgumentError for the agent to contain.
17
+ # @performance Constant work over nine bounded scalar fields.
18
+ class DeployNormalizer
19
+ def initialize(config, options = {})
20
+ @config = config
21
+ @id_generator = options[:id_generator] || proc { SecureRandom.uuid }
22
+ end
23
+
24
+ def call(attributes = {})
25
+ values = string_hash(attributes)
26
+ payload = {
27
+ "deploy_id" => deploy_id(values["deploy_id"] || @config.deploy_id),
28
+ "environment" => bounded(values["environment"] || @config.environment, 128),
29
+ "revision" => bounded(values["revision"] || @config.revision, 128),
30
+ "version" => bounded(values["version"] || @config.app_version, 128),
31
+ "repository" => repository(values["repository"]),
32
+ "actor" => bounded(values["actor"], 128),
33
+ "service" => bounded(values["service"] || @config.service_name, 128),
34
+ "region" => bounded(values["region"] || @config.region, 128),
35
+ "instance" => bounded(values["instance"] || @config.instance_id, 128)
36
+ }
37
+ validate!(payload)
38
+ payload
39
+ end
40
+
41
+ private
42
+
43
+ def validate!(payload)
44
+ raise ArgumentError, "deploy environment is required" if payload["environment"].to_s.empty?
45
+ return unless payload["revision"].to_s.empty? && payload["version"].to_s.empty?
46
+
47
+ raise ArgumentError, "deploy revision or version is required"
48
+ end
49
+
50
+ def generated_id
51
+ value = bounded(@id_generator.call, 128)
52
+ raise ArgumentError, "deploy ID is required" if value.to_s.empty?
53
+
54
+ value
55
+ end
56
+
57
+ def deploy_id(value)
58
+ normalized = bounded(value, 128)
59
+ normalized.to_s.empty? ? generated_id : normalized
60
+ end
61
+
62
+ def repository(value)
63
+ text = bounded(value, 512)
64
+ return nil if text.nil?
65
+
66
+ scp = text.match(/\A[^@]+@([^:]+):(.+)\z/)
67
+ return bounded("#{scp[1]}/#{scp[2]}", 512) if scp
68
+
69
+ uri = URI.parse(text)
70
+ return bounded("#{uri.host}#{uri.path}", 512) if uri.host
71
+ return bounded(uri.path, 512) unless uri.path.to_s.empty?
72
+
73
+ nil
74
+ rescue StandardError
75
+ nil
76
+ end
77
+
78
+ def string_hash(value)
79
+ raise ArgumentError, "deploy attributes must be a Hash" unless value.is_a?(Hash)
80
+
81
+ value.each_with_object({}) do |(key, child), result|
82
+ result[key.to_s] = child if key.is_a?(String) || key.is_a?(Symbol)
83
+ end
84
+ end
85
+
86
+ def bounded(value, limit)
87
+ return nil if value.nil?
88
+ return nil unless scalar?(value)
89
+
90
+ text = value.to_s
91
+ text = text.scrub("?") if text.respond_to?(:scrub)
92
+ text.bytesize > limit ? text.byteslice(0, limit) : text
93
+ rescue StandardError
94
+ nil
95
+ end
96
+
97
+ def scalar?(value)
98
+ value.is_a?(String) || value.is_a?(Symbol) || value.is_a?(Numeric)
99
+ end
100
+ end
101
+ end
102
+ end
@@ -44,6 +44,7 @@ module Chronos
44
44
  @sanitizer = options[:sanitizer] || Sanitizer.new(config)
45
45
  @safe_serializer = options[:safe_serializer] || SafeSerializer.new
46
46
  @max_payload_size = options[:max_payload_size] || proc { @config.max_payload_size }
47
+ @correlation = options[:correlation] || CorrelationContext.new(config)
47
48
  end
48
49
 
49
50
  def call(notice)
@@ -70,8 +71,10 @@ module Chronos
70
71
  "service" => {
71
72
  "name" => @config.service_name,
72
73
  "version" => @config.app_version,
73
- "instance_id" => notice.host
74
+ "instance_id" => @config.instance_id || notice.host
74
75
  },
76
+ "correlation" => @correlation.call("environment" => notice.environment,
77
+ "instance" => @config.instance_id || notice.host),
75
78
  "runtime" => notice.runtime,
76
79
  "context" => notice.context,
77
80
  "payload" => payload(notice)
@@ -16,7 +16,7 @@ module Chronos
16
16
  # @errors Unsupported types raise ArgumentError during construction.
17
17
  # @performance Construction is linear in supplied context and payload size.
18
18
  class TelemetryEvent
19
- TYPES = %w(request query job cache metric_batch).freeze
19
+ TYPES = %w(request query job cache external_http dependencies deploy metric_batch).freeze
20
20
 
21
21
  attr_reader :event_id, :event_type, :timestamp, :context, :payload
22
22
 
@@ -68,6 +68,7 @@ module Chronos
68
68
  @safe_serializer = options[:safe_serializer] || SafeSerializer.new
69
69
  @max_payload_size = options[:max_payload_size] || proc { @config.max_payload_size }
70
70
  @runtime_info = RuntimeInfo.new
71
+ @correlation = options[:correlation] || CorrelationContext.new(config)
71
72
  end
72
73
 
73
74
  def call(event)
@@ -89,13 +90,39 @@ module Chronos
89
90
  "schema_version" => "1.0", "event_id" => event.event_id,
90
91
  "event_type" => event.event_type, "occurred_at" => event.timestamp,
91
92
  "sent_at" => @clock.call.utc.iso8601(6), "project_key" => @config.project_id,
92
- "environment" => @config.environment,
93
- "service" => {"name" => @config.service_name, "version" => @config.app_version,
94
- "instance_id" => runtime[:host]},
93
+ "environment" => event_environment(event),
94
+ "service" => event_service(event, runtime),
95
+ "correlation" => event_correlation(event, runtime),
95
96
  "runtime" => runtime[:runtime], "context" => event.context, "payload" => event.payload
96
97
  }
97
98
  end
98
99
 
100
+ def event_environment(event)
101
+ event.event_type == "deploy" ? event.payload["environment"] : @config.environment
102
+ end
103
+
104
+ def event_service(event, runtime)
105
+ deploy = event.event_type == "deploy" ? event.payload : {}
106
+ {
107
+ "name" => deploy["service"] || @config.service_name,
108
+ "version" => deploy["version"] || @config.app_version,
109
+ "instance_id" => deploy["instance"] || @config.instance_id || runtime[:host]
110
+ }
111
+ end
112
+
113
+ def event_correlation(event, runtime)
114
+ overrides = {"instance" => @config.instance_id || runtime[:host]}
115
+ if event.event_type == "deploy"
116
+ overrides.merge!(
117
+ "release" => event.payload["version"], "revision" => event.payload["revision"],
118
+ "deploy_id" => event.payload["deploy_id"], "environment" => event.payload["environment"],
119
+ "service" => event.payload["service"], "region" => event.payload["region"],
120
+ "instance" => event.payload["instance"]
121
+ )
122
+ end
123
+ @correlation.call(overrides)
124
+ end
125
+
99
126
  def compact_envelope(envelope)
100
127
  envelope["context"] = {"_truncated" => true}
101
128
  envelope["payload"] = {"_truncated" => true}
@@ -0,0 +1,61 @@
1
+ module Chronos
2
+ module Integrations
3
+ # Optional Capistrano task installer using only the public task DSL.
4
+ #
5
+ # @responsibility Register one post-publish task that delegates explicit values to notify_deploy.
6
+ # @motivation Legacy deployments need release reporting without a Capistrano runtime dependency.
7
+ # @limits Values are read only from configured Capistrano variables; Git and ENV are not inspected.
8
+ # @collaborators Capistrano/Rake DSL and the Chronos public facade.
9
+ # @thread_safety Installation is idempotent per DSL object before task execution.
10
+ # @compatibility Capistrano task DSLs available to supported Ruby 2.2.10 through Ruby 2.6 apps.
11
+ # @example
12
+ # require "chronos/capistrano"
13
+ # @errors Missing DSL methods return false; notification failures are contained by Chronos.
14
+ # @performance Adds one task and one after hook; no background worker is created.
15
+ module Capistrano
16
+ INSTALLED_KEY = :@__chronos_capistrano_installed
17
+
18
+ class << self
19
+ def install(dsl)
20
+ return false if dsl.instance_variable_get(INSTALLED_KEY)
21
+ return false unless compatible?(dsl)
22
+
23
+ dsl.send(:namespace, :chronos) do
24
+ dsl.send(:desc, "Notify Chronos about the published deployment") if dsl.respond_to?(:desc, true)
25
+ dsl.send(:task, :notify_deploy) { notify(dsl) }
26
+ end
27
+ dsl.send(:after, "deploy:published", "chronos:notify_deploy")
28
+ dsl.instance_variable_set(INSTALLED_KEY, true)
29
+ true
30
+ rescue StandardError
31
+ false
32
+ end
33
+
34
+ def notify(dsl)
35
+ Chronos.notify_deploy(
36
+ :environment => value(dsl, :stage).to_s,
37
+ :revision => value(dsl, :current_revision) || value(dsl, :branch),
38
+ :version => value(dsl, :chronos_version) || value(dsl, :release_name),
39
+ :repository => value(dsl, :repo_url), :actor => value(dsl, :chronos_actor),
40
+ :deploy_id => value(dsl, :chronos_deploy_id), :service => value(dsl, :chronos_service),
41
+ :region => value(dsl, :chronos_region), :instance => value(dsl, :chronos_instance)
42
+ )
43
+ rescue StandardError
44
+ false
45
+ end
46
+
47
+ private
48
+
49
+ def compatible?(dsl)
50
+ [:namespace, :task, :after, :fetch].all? { |name| dsl.respond_to?(name, true) }
51
+ end
52
+
53
+ def value(dsl, name)
54
+ dsl.send(:fetch, name, nil)
55
+ rescue StandardError
56
+ nil
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end