Class: IbmAppconfigurationRubySdk::ConfigurationHandler
- Inherits:
-
Object
- Object
- IbmAppconfigurationRubySdk::ConfigurationHandler
- Includes:
- Utils, Singleton
- Defined in:
- lib/ibm_appconfiguration_ruby_sdk/configuration_handler.rb
Class Method Summary collapse
-
.current_instance ⇒ Object
Returns the existing singleton without creating a new one.
Instance Method Summary collapse
-
#cleanup ⇒ Object
Cleanup resources.
-
#connected? ⇒ Boolean
Check if connected.
-
#evaluate_rules(rules_map, entity_attributes, feature, property, entity_id = nil) ⇒ Hash
Evaluate rules.
-
#evaluate_segment(segment_key, entity_attributes) ⇒ Boolean
Evaluate segment.
-
#feature_evaluation(feature, entity_id, entity_attributes) ⇒ EvaluationResult
Feature evaluation.
- #format_config(configurations, environment_id, collection_id) ⇒ Object
-
#get_feature(feature_id) ⇒ Feature?
Get feature by ID.
-
#get_features ⇒ Hash
Get features.
-
#get_properties ⇒ Hash
Get properties.
-
#get_property(property_id) ⇒ Property?
Get property by ID.
-
#get_rollout_percentage(feature, segment_rule, entity_id) ⇒ Array(Integer, String)
Get rollout percentage for a feature or segment rule evaluation.
-
#get_secret(property_id, secrets_manager_service) ⇒ SecretProperty?
Get secret property.
-
#get_secrets_map ⇒ Hash
Get the secrets map.
-
#get_segment(segment_id) ⇒ Segment?
Get segment by ID.
-
#init(region:, guid:, apikey:, use_private_endpoint: false) ⇒ Object
Initialize the configuration handler.
-
#initialize ⇒ ConfigurationHandler
constructor
A new instance of ConfigurationHandler.
-
#load_configurations_to_cache(data) ⇒ Object
Load configurations to cache.
-
#log_error(error) ⇒ Object
Log error (non-raising — records the error in the log without interrupting flow).
-
#notify_configuration_update_listener ⇒ Object
Notify the registered configuration update listener This method is called internally when configurations are updated.
-
#parse_rules(segment_rules) ⇒ Hash
Parse rules.
-
#property_evaluation(property, entity_id, entity_attributes) ⇒ EvaluationResult
Property evaluation.
-
#record_evaluation(feature_id, property_id, entity_id, segment_id) ⇒ Object
Record evaluation.
-
#register_configuration_update_listener(&block) ⇒ Object
Register configuration update listener Registers a callback block that will be invoked when configurations are updated.
-
#set_bootstrap_file(bootstrap_file) ⇒ Object
Set bootstrap file.
-
#set_context(collection_id, environment_id, **options) ⇒ Object
Set context for configuration.
-
#set_live_update(live_update) ⇒ Object
Set live update status.
-
#set_persistent_cache_directory(directory) ⇒ Object
Set persistent cache directory.
- #track(_event_key, _entity_id) ⇒ Object
-
#write_to_persistent_storage(file_data) ⇒ Object
Write to persistent storage.
Methods included from Utils
append_segment_id, compute_hash, extract_configurations, extract_environment_data, extract_resources, get_current_rollout_percentage, get_normalized_value, parse_rollout_configuration_phases, symbolize_keys, validate_resource
Constructor Details
#initialize ⇒ ConfigurationHandler
Returns a new instance of ConfigurationHandler.
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
# File 'lib/ibm_appconfiguration_ruby_sdk/configuration_handler.rb', line 51 def initialize @collection_id = nil @environment_id = nil @guid = nil @connected = true @live_update = true @bootstrap_file = nil @persistent_cache_directory = nil @feature_map = {} @property_map = {} @segment_map = {} @secret_map = {} @rollout_config_map = {} @all_feature_flags = [] @cache_mutex = Mutex.new @logger = Logger.instance @file_manager = FileManager.instance @websocket_client = nil # Configuration update listener (single listener, matches Java SDK) @configuration_update_listener = nil end |
Class Method Details
.current_instance ⇒ Object
Returns the existing singleton without creating a new one. Raises ConfigurationError if called before the handler has been initialised — matching Node SDK's configurationHandler.currentInstance() behaviour.
44 45 46 47 48 49 |
# File 'lib/ibm_appconfiguration_ruby_sdk/configuration_handler.rb', line 44 def self.current_instance raise IbmAppconfigurationRubySdk::ConfigurationError, Constants::SINGLETON_EXCEPTION \ unless @singleton__instance__ instance end |
Instance Method Details
#cleanup ⇒ Object
Cleanup resources
105 106 107 108 |
# File 'lib/ibm_appconfiguration_ruby_sdk/configuration_handler.rb', line 105 def cleanup Metering.instance.cleanup @websocket_client&.disconnect end |
#connected? ⇒ Boolean
Check if connected
762 763 764 |
# File 'lib/ibm_appconfiguration_ruby_sdk/configuration_handler.rb', line 762 def connected? @connected end |
#evaluate_rules(rules_map, entity_attributes, feature, property, entity_id = nil) ⇒ Hash
Evaluate rules
519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 |
# File 'lib/ibm_appconfiguration_ruby_sdk/configuration_handler.rb', line 519 def evaluate_rules(rules_map, entity_attributes, feature, property, entity_id = nil) result_dict = { evaluated_segment_id: Constants::DEFAULT_SEGMENT_ID, value: nil, enabled: false, details: {} } begin # For each rule in the targeting (1..rules_map.keys.length).each do |index| segment_rule = rules_map[index] next unless segment_rule.get_rules.length.positive? segment_rule.get_rules.each do |rule| segments = rule[:segments] next unless segments&.length&.positive? # For each segment in a rule segments.each do |segment_key| # Check whether the entityAttributes satisfies all the rules of that segment next unless evaluate_segment(segment_key, entity_attributes) segment_obj = get_segment(segment_key) segment_name = segment_obj&.name result_dict[:evaluated_segment_id] = segment_key result_dict[:details][:segment_name] = segment_name if feature # evaluateRules was called for feature flag segment_level_rollout_percentage, effective_entity_id = get_rollout_percentage(feature, segment_rule, entity_id) # Check whether the entityId is eligible for segment rollout if segment_level_rollout_percentage == 100 || (entity_id && get_normalized_value("#{effective_entity_id}:#{feature.feature_id}") < segment_level_rollout_percentage) # Since the entityId is eligible for segment rollout, return inherited or overridden value result_dict[:value] = if segment_rule.get_value == Constants::DEFAULT_FEATURE_VALUE feature.enabled_value # Return the inherited value else segment_rule.get_value # Return the overridden value end result_dict[:details][:value_type] = "SEGMENT_VALUE" result_dict[:enabled] = true result_dict[:details][:rollout_percentage_applied] = true else result_dict[:value] = feature.disabled_value result_dict[:enabled] = false result_dict[:details][:value_type] = "DISABLED_VALUE" result_dict[:details][:rollout_percentage_applied] = false end else # evaluateRules was called for property result_dict[:value] = if segment_rule.get_value == Constants::DEFAULT_PROPERTY_VALUE property.value else segment_rule.get_value end result_dict[:details][:value_type] = "SEGMENT_VALUE" end return result_dict end end end rescue StandardError => e @logger.error("RuleEvaluation #{e}") result_dict[:value] = nil result_dict[:enabled] = false result_dict[:details][:value_type] = "ERROR" result_dict[:details][:error_type] = e. return result_dict end # Since entityAttributes did not satisfy any of the targeting rules if feature # evaluateRules was called for feature flag # Check whether the entityId is eligible for default rollout rollout_percentage, effective_entity_id = get_rollout_percentage(feature, nil, entity_id) if rollout_percentage == 100 || (entity_id && get_normalized_value("#{effective_entity_id}:#{feature.feature_id}") < rollout_percentage) result_dict[:value] = feature.enabled_value result_dict[:enabled] = true result_dict[:details][:value_type] = "ENABLED_VALUE" result_dict[:details][:rollout_percentage_applied] = true else result_dict[:value] = feature.disabled_value result_dict[:enabled] = false result_dict[:details][:value_type] = "DISABLED_VALUE" result_dict[:details][:rollout_percentage_applied] = false end else # evaluateRules was called for property result_dict[:value] = property.value result_dict[:details][:value_type] = "DEFAULT_VALUE" end result_dict end |
#evaluate_segment(segment_key, entity_attributes) ⇒ Boolean
Evaluate segment
437 438 439 440 441 442 |
# File 'lib/ibm_appconfiguration_ruby_sdk/configuration_handler.rb', line 437 def evaluate_segment(segment_key, entity_attributes) segment_obj = @cache_mutex.synchronize { @segment_map[segment_key] } return segment_obj.evaluate_rule(entity_attributes) if segment_obj nil end |
#feature_evaluation(feature, entity_id, entity_attributes) ⇒ EvaluationResult
Feature evaluation
644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 |
# File 'lib/ibm_appconfiguration_ruby_sdk/configuration_handler.rb', line 644 def feature_evaluation(feature, entity_id, entity_attributes) result_dict = { evaluated_segment_id: Constants::DEFAULT_SEGMENT_ID, value: nil, enabled: false, details: {} } begin # Step 1: Check if feature flag is enabled unless feature.enabled result_dict[:details][:value_type] = "DISABLED_VALUE" return EvaluationResult.new( value: feature.disabled_value, enabled: false, details: EvaluationDetails.new(**result_dict[:details]) ) end # Step 2: Check if feature has segment rules (targeting) and valid entity attributes if feature.segment_rules&.length&.positive? && entity_attributes.is_a?(Hash) && entity_attributes.keys.length.positive? # Evaluate targeting rules rules_map = parse_rules(feature.segment_rules) result_dict = evaluate_rules(rules_map, entity_attributes, feature, nil, entity_id) return EvaluationResult.new( value: result_dict[:value], enabled: result_dict[:enabled], details: EvaluationDetails.new(**result_dict[:details]) ) end # Step 3: No targeting rules - apply default rollout percentage # Check if entity_id qualifies for rollout rollout_percentage, effective_entity_id = get_rollout_percentage(feature, nil, entity_id) normalized_value = get_normalized_value("#{effective_entity_id}:#{feature.feature_id}") if rollout_percentage == 100 || normalized_value < rollout_percentage result_dict[:details][:value_type] = "ENABLED_VALUE" result_dict[:details][:rollout_percentage_applied] = true return EvaluationResult.new( value: feature.enabled_value, enabled: true, details: EvaluationDetails.new(**result_dict[:details]) ) end # Step 4: Entity doesn't qualify for rollout result_dict[:details][:value_type] = "DISABLED_VALUE" result_dict[:details][:rollout_percentage_applied] = false EvaluationResult.new( value: feature.disabled_value, enabled: false, details: EvaluationDetails.new(**result_dict[:details]) ) ensure # Always record evaluation for metering record_evaluation(feature.feature_id, nil, entity_id, result_dict[:evaluated_segment_id]) end end |
#format_config(configurations, environment_id, collection_id) ⇒ Object
187 188 189 190 191 192 193 194 195 196 197 |
# File 'lib/ibm_appconfiguration_ruby_sdk/configuration_handler.rb', line 187 def format_config(configurations, environment_id, collection_id) { environments: [{ environment_id: environment_id, features: configurations[:features], properties: configurations[:properties] }], collections: [{ collection_id: collection_id }], segments: configurations[:segments] } end |
#get_feature(feature_id) ⇒ Feature?
Get feature by ID
382 383 384 385 386 387 388 |
# File 'lib/ibm_appconfiguration_ruby_sdk/configuration_handler.rb', line 382 def get_feature(feature_id) @cache_mutex.synchronize do return @feature_map[feature_id] if @feature_map.key?(feature_id) end @logger.error("Invalid feature id - #{feature_id}") nil end |
#get_features ⇒ Hash
Get features
748 749 750 |
# File 'lib/ibm_appconfiguration_ruby_sdk/configuration_handler.rb', line 748 def get_features @cache_mutex.synchronize { @feature_map.dup } end |
#get_properties ⇒ Hash
Get properties
755 756 757 |
# File 'lib/ibm_appconfiguration_ruby_sdk/configuration_handler.rb', line 755 def get_properties @cache_mutex.synchronize { @property_map.dup } end |
#get_property(property_id) ⇒ Property?
Get property by ID
394 395 396 397 398 399 400 |
# File 'lib/ibm_appconfiguration_ruby_sdk/configuration_handler.rb', line 394 def get_property(property_id) @cache_mutex.synchronize do return @property_map[property_id] if @property_map.key?(property_id) end @logger.error("Invalid property id - #{property_id}") nil end |
#get_rollout_percentage(feature, segment_rule, entity_id) ⇒ Array(Integer, String)
Get rollout percentage for a feature or segment rule evaluation.
For progressive rollouts, the rollout configuration's start_at value is
appended to the entity id so that the normalized hash used to decide
eligibility is deterministic per rollout configuration (matches the Go SDK).
467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 |
# File 'lib/ibm_appconfiguration_ruby_sdk/configuration_handler.rb', line 467 def get_rollout_percentage(feature, segment_rule, entity_id) entity_id = entity_id.to_s if segment_rule # Segment-level rollout if segment_rule.rollout_configuration || segment_rule.rollout_type == Constants::PROGRESSIVE # Progressive rollout: inherit the feature-level configuration when the # segment rule uses the "$default" percentage, otherwise use the # segment-rule specific configuration. rollout_hash = @cache_mutex.synchronize do if segment_rule.rollout_percentage == Constants::DEFAULT_ROLLOUT_PERCENTAGE @rollout_config_map[feature.feature_id] else @rollout_config_map["#{feature.feature_id}#{Constants::DELIMITER}#{segment_rule.rule_id}"] end end return [0, entity_id] unless rollout_hash start_at = segment_rule.rollout_configuration && segment_rule.rollout_configuration[:start_at] entity_id += start_at.to_s if start_at [get_current_rollout_percentage(rollout_hash), entity_id] else # Manual rollout percentage = if segment_rule.rollout_percentage == Constants::DEFAULT_ROLLOUT_PERCENTAGE feature.rollout_percentage else segment_rule.rollout_percentage end [percentage, entity_id] end else # Feature-level rollout return [feature.rollout_percentage || 100, entity_id] unless feature.rollout_configuration rollout_hash = @cache_mutex.synchronize { @rollout_config_map[feature.feature_id] } return [0, entity_id] unless rollout_hash start_at = feature.rollout_configuration[:start_at] entity_id += start_at.to_s if start_at [get_current_rollout_percentage(rollout_hash), entity_id] end end |
#get_secret(property_id, secrets_manager_service) ⇒ SecretProperty?
Get secret property
407 408 409 410 411 412 413 414 415 416 417 418 |
# File 'lib/ibm_appconfiguration_ruby_sdk/configuration_handler.rb', line 407 def get_secret(property_id, secrets_manager_service) property_obj = get_property(property_id) if property_obj if property_obj.type == Constants::SECRETREF @secret_map[property_id] = secrets_manager_service return SecretProperty.new(property_id) end @logger.error("Invalid operation: getSecret() cannot be called on a #{property_obj.type} property.") return nil end nil end |
#get_secrets_map ⇒ Hash
Get the secrets map
821 822 823 |
# File 'lib/ibm_appconfiguration_ruby_sdk/configuration_handler.rb', line 821 def get_secrets_map @secret_map end |
#get_segment(segment_id) ⇒ Segment?
Get segment by ID
424 425 426 427 428 429 430 |
# File 'lib/ibm_appconfiguration_ruby_sdk/configuration_handler.rb', line 424 def get_segment(segment_id) @cache_mutex.synchronize do return @segment_map[segment_id] if @segment_map.key?(segment_id) end @logger.error("Invalid segment id - #{segment_id}") nil end |
#init(region:, guid:, apikey:, use_private_endpoint: false) ⇒ Object
Initialize the configuration handler
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 |
# File 'lib/ibm_appconfiguration_ruby_sdk/configuration_handler.rb', line 82 def init(region:, guid:, apikey:, use_private_endpoint: false) @guid = guid @region = region @apikey = apikey @use_private_endpoint = use_private_endpoint # Initialize UrlBuilder url_builder = UrlBuilder.instance url_builder.region = region url_builder.guid = guid url_builder.apikey = apikey url_builder.use_private_endpoint = use_private_endpoint # Initialize ApiManager ApiManager.set_authenticator # Initialize Metering metering_url = "#{url_builder.base_service_url}/apprapp/events/v1/instances/#{guid}/usage" Metering.instance.set_metering_url(metering_url, apikey) end |
#load_configurations_to_cache(data) ⇒ Object
Load configurations to cache
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 |
# File 'lib/ibm_appconfiguration_ruby_sdk/configuration_handler.rb', line 113 def load_configurations_to_cache(data) return unless data @cache_mutex.synchronize do if data[:features] features = data[:features] @all_feature_flags = features @feature_map = {} @rollout_config_map = {} features.each do |feature| feature_obj = Feature.new(feature) @feature_map[feature[:feature_id]] = feature_obj # Parse feature-level progressive rollout if feature_obj.rollout_configuration @rollout_config_map[feature[:feature_id]] = parse_rollout_configuration_phases(feature_obj.rollout_configuration) end # Parse segment-level progressive rollout next unless feature[:segment_rules].is_a?(Array) feature[:segment_rules].each do |segment_rule| segment_rule_obj = SegmentRules.new(segment_rule) if segment_rule_obj.rollout_configuration key = "#{feature[:feature_id]}#{Constants::DELIMITER}#{segment_rule[:rule_id]}" @rollout_config_map[key] = parse_rollout_configuration_phases(segment_rule_obj.rollout_configuration) end end end end if data[:properties] properties = data[:properties] @property_map = {} properties.each do |property| @property_map[property[:property_id]] = Property.new(property) end end if data[:segments] segments = data[:segments] @segment_map = {} segments.each do |segment| @segment_map[segment[:segment_id]] = Segment.new(segment) end end end # Notify listener after configurations are loaded (outside mutex to prevent # deadlock if the listener calls get_feature / get_property etc.) notify_configuration_update_listener end |
#log_error(error) ⇒ Object
Log error (non-raising — records the error in the log without interrupting flow)
182 183 184 185 |
# File 'lib/ibm_appconfiguration_ruby_sdk/configuration_handler.rb', line 182 def log_error(error) error_msg = error.is_a?(Exception) ? error. : error.to_s @logger.error(error_msg) end |
#notify_configuration_update_listener ⇒ Object
Notify the registered configuration update listener This method is called internally when configurations are updated. The listener is invoked safely - exceptions are caught to prevent breaking the update flow.
807 808 809 810 811 812 813 814 815 816 |
# File 'lib/ibm_appconfiguration_ruby_sdk/configuration_handler.rb', line 807 def notify_configuration_update_listener return unless @configuration_update_listener begin @logger.log(Constants::CONFIG_UPDATE_NOTIFYING_LISTENER) @configuration_update_listener.call rescue StandardError => e @logger.error("Error in configuration update listener: #{e.class.name} - #{e.}") end end |
#parse_rules(segment_rules) ⇒ Hash
Parse rules
448 449 450 451 452 453 454 |
# File 'lib/ibm_appconfiguration_ruby_sdk/configuration_handler.rb', line 448 def parse_rules(segment_rules) rules_map = {} segment_rules.each do |rules| rules_map[rules[:order]] = SegmentRules.new(rules) end rules_map end |
#property_evaluation(property, entity_id, entity_attributes) ⇒ EvaluationResult
Property evaluation
712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 |
# File 'lib/ibm_appconfiguration_ruby_sdk/configuration_handler.rb', line 712 def property_evaluation(property, entity_id, entity_attributes) result_dict = { evaluated_segment_id: Constants::DEFAULT_SEGMENT_ID, value: nil, details: {} } begin # Check whether the property is configured with any targeting definition # and then check whether the user has passed valid entityAttributes JSON before we evaluate if property.segment_rules&.length&.positive? && entity_attributes.is_a?(Hash) && entity_attributes.keys.length.positive? rules_map = parse_rules(property.segment_rules) result_dict = evaluate_rules(rules_map, entity_attributes, nil, property, entity_id) return EvaluationResult.new( value: result_dict[:value], enabled: nil, details: EvaluationDetails.new(**result_dict[:details]) ) end result_dict[:details][:value_type] = "DEFAULT_VALUE" EvaluationResult.new( value: property.value, enabled: nil, details: EvaluationDetails.new(**result_dict[:details]) ) ensure # Record evaluation for metering record_evaluation(nil, property.property_id, entity_id, result_dict[:evaluated_segment_id]) end end |
#record_evaluation(feature_id, property_id, entity_id, segment_id) ⇒ Object
Record evaluation
626 627 628 629 630 631 632 633 634 635 636 |
# File 'lib/ibm_appconfiguration_ruby_sdk/configuration_handler.rb', line 626 def record_evaluation(feature_id, property_id, entity_id, segment_id) Metering.instance.add_metering( @guid, @environment_id, @collection_id, entity_id || Constants::DEFAULT_ENTITY_ID, segment_id || Constants::DEFAULT_SEGMENT_ID, feature_id, property_id ) end |
#register_configuration_update_listener(&block) ⇒ Object
Register configuration update listener Registers a callback block that will be invoked when configurations are updated. Only one listener can be registered at a time (matches Java SDK behavior). Calling this method multiple times will replace the previous listener.
793 794 795 796 797 798 799 800 |
# File 'lib/ibm_appconfiguration_ruby_sdk/configuration_handler.rb', line 793 def register_configuration_update_listener(&block) if block_given? @configuration_update_listener = block @logger.log(Constants::CONFIG_UPDATE_LISTENER_REGISTERED) else @logger.warning(Constants::CONFIG_UPDATE_LISTENER_NO_BLOCK) end end |
#set_bootstrap_file(bootstrap_file) ⇒ Object
Set bootstrap file
776 777 778 |
# File 'lib/ibm_appconfiguration_ruby_sdk/configuration_handler.rb', line 776 def set_bootstrap_file(bootstrap_file) @bootstrap_file = bootstrap_file end |
#set_context(collection_id, environment_id, **options) ⇒ Object
Set context for configuration
204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 |
# File 'lib/ibm_appconfiguration_ruby_sdk/configuration_handler.rb', line 204 def set_context(collection_id, environment_id, **) @collection_id = collection_id @environment_id = environment_id @persistent_cache_directory = [:persistent_cache_directory] @bootstrap_file = [:bootstrap_file] @live_update = [:live_config_update_enabled] # EvaluationEvents and MetricEvents are not yet ported from Node SDK. persistent_cache_read = false error_reading_bootstrap_config = false # Handle persistent cache directory if @persistent_cache_directory @logger.info("persistent cache directory path is: #{@persistent_cache_directory}") file_path = File.join(@persistent_cache_directory, "appconfiguration.json") persistent_cache = @file_manager.read_persistent_cache_configurations(file_path) unless persistent_cache.empty? configurations = extract_configurations(JSON.parse(persistent_cache), @environment_id, @collection_id) load_configurations_to_cache(configurations) persistent_cache_read = true end # Check write permissions begin # Test if directory is writable File.write(File.join(@persistent_cache_directory, ".write_test"), "") File.delete(File.join(@persistent_cache_directory, ".write_test")) rescue StandardError => e log_error("ERROR: No write permission for persistent cache directory. #{e}") end end # Handle bootstrap file if @bootstrap_file if @persistent_cache_directory # If persistent cache directory exists if persistent_cache_read # Listener already fired inside load_configurations_to_cache above. else # Only read bootstrap if persistent cache wasn't read begin @logger.info("reading configurations from bootstrap file: #{@bootstrap_file}") bootstrap_config = @file_manager.read_bootstrap_configurations_from_file(@bootstrap_file) configurations = extract_configurations(JSON.parse(bootstrap_config), @environment_id, @collection_id) load_configurations_to_cache(configurations) write_to_persistent_storage(format_config(configurations, @environment_id, @collection_id)) # Listener already fired inside load_configurations_to_cache above. rescue StandardError => e log_error(e) end end else # No persistent cache directory, just read bootstrap @logger.info("reading configurations from bootstrap file: #{@bootstrap_file}") begin bootstrap_config = @file_manager.read_bootstrap_configurations_from_file(@bootstrap_file) configurations = extract_configurations(JSON.parse(bootstrap_config, symbolize_names: true), @environment_id, @collection_id) load_configurations_to_cache(configurations) # Listener already fired inside load_configurations_to_cache above. rescue StandardError => e log_error(e) unless @live_update @logger.error(e..to_s) error_reading_bootstrap_config = true end end end # Implement live update logic return unless @live_update @logger.info(Constants::CONFIG_LIVE_UPDATE_ENABLED) # Track whether to start background retry start_background_retry = false # Create config fetcher instance config_fetcher = ConfigFetcher.new( collection_id: @collection_id, environment_id: @environment_id, handler: self, logger: @logger ) # Fetch configurations from API fetch_result = config_fetcher.fetch if fetch_result[:ok] @logger.log(Constants::SUCCESSFULLY_FETCHED_THE_CONFIGURATIONS) # Process and load configurations begin # Parse JSON string if the IBM SDK returned the body unparsed, then symbolize keys raw = fetch_result[:data] raw = JSON.parse(raw) if raw.is_a?(String) symbolized_data = symbolize_keys(raw) # Extract configurations using utils.rb method extracted_config = extract_configurations( symbolized_data, @environment_id, @collection_id ) # Load to cache using existing method load_configurations_to_cache(extracted_config) # Write to persistent storage if configured if @persistent_cache_directory formatted_config = format_config(extracted_config, @environment_id, @collection_id) write_to_persistent_storage(formatted_config) end @logger.log(Constants::CONFIG_LOADED_SUCCESSFULLY) rescue StandardError => e @logger.error("Failed to process configurations: #{e.class.name} - #{e.}") end else # Failed to fetch from API status_code = fetch_result[:status] err_msg = "Status code: #{status_code}. Message: Failed to fetch the configurations from remote server." # Check for client-side errors (4xx except 429) log_error(err_msg) if status_code >= 400 && status_code < 500 && status_code != 429 # Check if we have fallback configurations (persistent cache or bootstrap) if persistent_cache_read = "Loaded the configurations from the persistent cache into the application." @logger.info("#{err_msg} #{}") start_background_retry = true notify_configuration_update_listener elsif @bootstrap_file && !error_reading_bootstrap_config = "Loaded the configurations from the bootstrap file: #{@bootstrap_file} into the application." @logger.info("#{err_msg} #{}") start_background_retry = true notify_configuration_update_listener else # No fallback available @logger.error(Constants::CONFIG_NO_CONFIGURATIONS_AVAILABLE) log_error(err_msg) end end # Start WebSocket client for live updates @logger.log(Constants::CONFIG_STARTING_WEBSOCKET) begin # Get required parameters from UrlBuilder url_builder = UrlBuilder.instance # Set @guid from url_builder if not already set @guid ||= url_builder.guid @websocket_client = WebSocketClient.new( region: url_builder.region, guid: @guid, apikey: url_builder.apikey, collection_id: @collection_id, environment_id: @environment_id, start_background_retry: start_background_retry ) @websocket_client.connect @logger.log(Constants::CONFIG_WEBSOCKET_STARTED) rescue StandardError => e @logger.error("Failed to start WebSocket client: #{e.class.name} - #{e.}") end end |
#set_live_update(live_update) ⇒ Object
Set live update status
769 770 771 |
# File 'lib/ibm_appconfiguration_ruby_sdk/configuration_handler.rb', line 769 def set_live_update(live_update) @live_update = live_update end |
#set_persistent_cache_directory(directory) ⇒ Object
Set persistent cache directory
783 784 785 |
# File 'lib/ibm_appconfiguration_ruby_sdk/configuration_handler.rb', line 783 def set_persistent_cache_directory(directory) @persistent_cache_directory = directory end |
#track(_event_key, _entity_id) ⇒ Object
374 375 376 |
# File 'lib/ibm_appconfiguration_ruby_sdk/configuration_handler.rb', line 374 def track(_event_key, _entity_id) raise NotImplementedError, "track is not yet implemented — requires MetricEvents port from Node SDK" end |
#write_to_persistent_storage(file_data) ⇒ Object
Write to persistent storage
171 172 173 174 175 176 177 |
# File 'lib/ibm_appconfiguration_ruby_sdk/configuration_handler.rb', line 171 def write_to_persistent_storage(file_data) return unless @persistent_cache_directory json = JSON.generate(file_data) file_path = File.join(@persistent_cache_directory, "appconfiguration.json") @file_manager.store_files(json, file_path) end |