eryph-compute 0.1.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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +47 -0
- data/LICENSE +21 -0
- data/README.md +144 -0
- data/eryph-clientruntime.gemspec +42 -0
- data/eryph-compute.gemspec +44 -0
- data/lib/eryph/clientruntime/client_credentials_lookup.rb +295 -0
- data/lib/eryph/clientruntime/config_store.rb +206 -0
- data/lib/eryph/clientruntime/config_stores_reader.rb +211 -0
- data/lib/eryph/clientruntime/endpoint_lookup.rb +226 -0
- data/lib/eryph/clientruntime/environment.rb +440 -0
- data/lib/eryph/clientruntime/local_identity_provider_info.rb +153 -0
- data/lib/eryph/clientruntime/token_provider.rb +275 -0
- data/lib/eryph/clientruntime/version.rb +6 -0
- data/lib/eryph/clientruntime.rb +79 -0
- data/lib/eryph/compute/client.rb +442 -0
- data/lib/eryph/compute/generated/Gemfile +9 -0
- data/lib/eryph/compute/generated/README.md +206 -0
- data/lib/eryph/compute/generated/Rakefile +10 -0
- data/lib/eryph/compute/generated/compute_client.gemspec +41 -0
- data/lib/eryph/compute/generated/docs/ApiVersion.md +20 -0
- data/lib/eryph/compute/generated/docs/ApiVersionResponse.md +18 -0
- data/lib/eryph/compute/generated/docs/Catlet.md +32 -0
- data/lib/eryph/compute/generated/docs/CatletConfigOperationResult.md +18 -0
- data/lib/eryph/compute/generated/docs/CatletConfigValidationResult.md +20 -0
- data/lib/eryph/compute/generated/docs/CatletConfiguration.md +18 -0
- data/lib/eryph/compute/generated/docs/CatletDrive.md +20 -0
- data/lib/eryph/compute/generated/docs/CatletDriveType.md +15 -0
- data/lib/eryph/compute/generated/docs/CatletList.md +18 -0
- data/lib/eryph/compute/generated/docs/CatletNetwork.md +30 -0
- data/lib/eryph/compute/generated/docs/CatletNetworkAdapter.md +20 -0
- data/lib/eryph/compute/generated/docs/CatletStatus.md +15 -0
- data/lib/eryph/compute/generated/docs/CatletStopMode.md +15 -0
- data/lib/eryph/compute/generated/docs/CatletsApi.md +863 -0
- data/lib/eryph/compute/generated/docs/DiskStatus.md +15 -0
- data/lib/eryph/compute/generated/docs/ExpandCatletConfigRequestBody.md +22 -0
- data/lib/eryph/compute/generated/docs/ExpandNewCatletConfigRequest.md +22 -0
- data/lib/eryph/compute/generated/docs/FloatingNetworkPort.md +26 -0
- data/lib/eryph/compute/generated/docs/Gene.md +30 -0
- data/lib/eryph/compute/generated/docs/GeneList.md +18 -0
- data/lib/eryph/compute/generated/docs/GeneType.md +15 -0
- data/lib/eryph/compute/generated/docs/GeneWithUsage.md +34 -0
- data/lib/eryph/compute/generated/docs/GenesApi.md +281 -0
- data/lib/eryph/compute/generated/docs/NewCatletRequest.md +20 -0
- data/lib/eryph/compute/generated/docs/NewProjectMemberBody.md +22 -0
- data/lib/eryph/compute/generated/docs/NewProjectRequest.md +20 -0
- data/lib/eryph/compute/generated/docs/NewVirtualDiskRequest.md +30 -0
- data/lib/eryph/compute/generated/docs/Operation.md +32 -0
- data/lib/eryph/compute/generated/docs/OperationList.md +18 -0
- data/lib/eryph/compute/generated/docs/OperationLogEntry.md +24 -0
- data/lib/eryph/compute/generated/docs/OperationResource.md +22 -0
- data/lib/eryph/compute/generated/docs/OperationResult.md +18 -0
- data/lib/eryph/compute/generated/docs/OperationStatus.md +15 -0
- data/lib/eryph/compute/generated/docs/OperationTask.md +30 -0
- data/lib/eryph/compute/generated/docs/OperationTaskReference.md +22 -0
- data/lib/eryph/compute/generated/docs/OperationTaskStatus.md +15 -0
- data/lib/eryph/compute/generated/docs/OperationsApi.md +157 -0
- data/lib/eryph/compute/generated/docs/PopulateCatletConfigVariablesRequest.md +20 -0
- data/lib/eryph/compute/generated/docs/ProblemDetails.md +26 -0
- data/lib/eryph/compute/generated/docs/Project.md +22 -0
- data/lib/eryph/compute/generated/docs/ProjectList.md +18 -0
- data/lib/eryph/compute/generated/docs/ProjectMemberRole.md +26 -0
- data/lib/eryph/compute/generated/docs/ProjectMemberRoleList.md +18 -0
- data/lib/eryph/compute/generated/docs/ProjectMembersApi.md +293 -0
- data/lib/eryph/compute/generated/docs/ProjectsApi.md +286 -0
- data/lib/eryph/compute/generated/docs/ResourceType.md +15 -0
- data/lib/eryph/compute/generated/docs/StopCatletRequestBody.md +18 -0
- data/lib/eryph/compute/generated/docs/TaskReferenceType.md +15 -0
- data/lib/eryph/compute/generated/docs/UpdateCatletRequestBody.md +20 -0
- data/lib/eryph/compute/generated/docs/UpdateProjectNetworksRequestBody.md +20 -0
- data/lib/eryph/compute/generated/docs/ValidateConfigRequest.md +18 -0
- data/lib/eryph/compute/generated/docs/ValidationIssue.md +20 -0
- data/lib/eryph/compute/generated/docs/VersionApi.md +69 -0
- data/lib/eryph/compute/generated/docs/VirtualDisk.md +42 -0
- data/lib/eryph/compute/generated/docs/VirtualDiskAttachedCatlet.md +20 -0
- data/lib/eryph/compute/generated/docs/VirtualDiskGeneInfo.md +22 -0
- data/lib/eryph/compute/generated/docs/VirtualDiskList.md +18 -0
- data/lib/eryph/compute/generated/docs/VirtualDisksApi.md +291 -0
- data/lib/eryph/compute/generated/docs/VirtualNetwork.md +28 -0
- data/lib/eryph/compute/generated/docs/VirtualNetworkConfiguration.md +18 -0
- data/lib/eryph/compute/generated/docs/VirtualNetworkList.md +18 -0
- data/lib/eryph/compute/generated/docs/VirtualNetworksApi.md +291 -0
- data/lib/eryph/compute/generated/git_push.sh +57 -0
- data/lib/eryph/compute/generated/lib/compute_client/api/catlets_api.rb +812 -0
- data/lib/eryph/compute/generated/lib/compute_client/api/genes_api.rb +262 -0
- data/lib/eryph/compute/generated/lib/compute_client/api/operations_api.rb +154 -0
- data/lib/eryph/compute/generated/lib/compute_client/api/project_members_api.rb +297 -0
- data/lib/eryph/compute/generated/lib/compute_client/api/projects_api.rb +269 -0
- data/lib/eryph/compute/generated/lib/compute_client/api/version_api.rb +79 -0
- data/lib/eryph/compute/generated/lib/compute_client/api/virtual_disks_api.rb +272 -0
- data/lib/eryph/compute/generated/lib/compute_client/api/virtual_networks_api.rb +282 -0
- data/lib/eryph/compute/generated/lib/compute_client/api_client.rb +437 -0
- data/lib/eryph/compute/generated/lib/compute_client/api_error.rb +58 -0
- data/lib/eryph/compute/generated/lib/compute_client/configuration.rb +392 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/api_version.rb +263 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/api_version_response.rb +237 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/catlet.rb +400 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/catlet_config_operation_result.rb +234 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/catlet_config_validation_result.rb +251 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/catlet_configuration.rb +223 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/catlet_drive.rb +270 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/catlet_drive_type.rb +43 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/catlet_list.rb +239 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/catlet_network.rb +318 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/catlet_network_adapter.rb +247 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/catlet_status.rb +43 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/catlet_stop_mode.rb +41 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/disk_status.rb +40 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/expand_catlet_config_request_body.rb +243 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/expand_new_catlet_config_request.rb +243 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/floating_network_port.rb +313 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/gene.rb +415 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/gene_list.rb +239 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/gene_type.rb +41 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/gene_with_usage.rb +439 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/new_catlet_request.rb +233 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/new_project_member_body.rb +273 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/new_project_request.rb +247 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/new_virtual_disk_request.rb +345 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/operation.rb +352 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/operation_list.rb +239 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/operation_log_entry.rb +299 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/operation_resource.rb +311 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/operation_result.rb +242 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/operation_status.rb +42 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/operation_task.rb +366 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/operation_task_reference.rb +311 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/operation_task_status.rb +42 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/populate_catlet_config_variables_request.rb +233 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/problem_details.rb +261 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/project.rb +289 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/project_list.rb +239 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/project_member_role.rb +341 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/project_member_role_list.rb +239 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/resource_type.rb +42 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/stop_catlet_request_body.rb +259 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/task_reference_type.rb +40 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/update_catlet_request_body.rb +233 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/update_project_networks_request_body.rb +233 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/validate_config_request.rb +223 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/validation_issue.rb +249 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/virtual_disk.rb +479 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/virtual_disk_attached_catlet.rb +285 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/virtual_disk_gene_info.rb +289 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/virtual_disk_list.rb +239 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/virtual_network.rb +351 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/virtual_network_configuration.rb +223 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/virtual_network_list.rb +239 -0
- data/lib/eryph/compute/generated/lib/compute_client/version.rb +15 -0
- data/lib/eryph/compute/generated/lib/compute_client.rb +101 -0
- data/lib/eryph/compute/generated/spec/api/catlets_api_spec.rb +182 -0
- data/lib/eryph/compute/generated/spec/api/genes_api_spec.rb +81 -0
- data/lib/eryph/compute/generated/spec/api/operations_api_spec.rb +62 -0
- data/lib/eryph/compute/generated/spec/api/project_members_api_spec.rb +86 -0
- data/lib/eryph/compute/generated/spec/api/projects_api_spec.rb +82 -0
- data/lib/eryph/compute/generated/spec/api/version_api_spec.rb +46 -0
- data/lib/eryph/compute/generated/spec/api/virtual_disks_api_spec.rb +83 -0
- data/lib/eryph/compute/generated/spec/api/virtual_networks_api_spec.rb +84 -0
- data/lib/eryph/compute/generated/spec/models/api_version_response_spec.rb +36 -0
- data/lib/eryph/compute/generated/spec/models/api_version_spec.rb +42 -0
- data/lib/eryph/compute/generated/spec/models/catlet_config_operation_result_spec.rb +36 -0
- data/lib/eryph/compute/generated/spec/models/catlet_config_validation_result_spec.rb +42 -0
- data/lib/eryph/compute/generated/spec/models/catlet_configuration_spec.rb +36 -0
- data/lib/eryph/compute/generated/spec/models/catlet_drive_spec.rb +42 -0
- data/lib/eryph/compute/generated/spec/models/catlet_drive_type_spec.rb +30 -0
- data/lib/eryph/compute/generated/spec/models/catlet_list_spec.rb +36 -0
- data/lib/eryph/compute/generated/spec/models/catlet_network_adapter_spec.rb +42 -0
- data/lib/eryph/compute/generated/spec/models/catlet_network_spec.rb +72 -0
- data/lib/eryph/compute/generated/spec/models/catlet_spec.rb +78 -0
- data/lib/eryph/compute/generated/spec/models/catlet_status_spec.rb +30 -0
- data/lib/eryph/compute/generated/spec/models/catlet_stop_mode_spec.rb +30 -0
- data/lib/eryph/compute/generated/spec/models/disk_status_spec.rb +30 -0
- data/lib/eryph/compute/generated/spec/models/expand_catlet_config_request_body_spec.rb +48 -0
- data/lib/eryph/compute/generated/spec/models/expand_new_catlet_config_request_spec.rb +48 -0
- data/lib/eryph/compute/generated/spec/models/floating_network_port_spec.rb +60 -0
- data/lib/eryph/compute/generated/spec/models/gene_list_spec.rb +36 -0
- data/lib/eryph/compute/generated/spec/models/gene_spec.rb +72 -0
- data/lib/eryph/compute/generated/spec/models/gene_type_spec.rb +30 -0
- data/lib/eryph/compute/generated/spec/models/gene_with_usage_spec.rb +84 -0
- data/lib/eryph/compute/generated/spec/models/new_catlet_request_spec.rb +42 -0
- data/lib/eryph/compute/generated/spec/models/new_project_member_body_spec.rb +48 -0
- data/lib/eryph/compute/generated/spec/models/new_project_request_spec.rb +42 -0
- data/lib/eryph/compute/generated/spec/models/new_virtual_disk_request_spec.rb +72 -0
- data/lib/eryph/compute/generated/spec/models/operation_list_spec.rb +36 -0
- data/lib/eryph/compute/generated/spec/models/operation_log_entry_spec.rb +54 -0
- data/lib/eryph/compute/generated/spec/models/operation_resource_spec.rb +48 -0
- data/lib/eryph/compute/generated/spec/models/operation_result_spec.rb +36 -0
- data/lib/eryph/compute/generated/spec/models/operation_spec.rb +78 -0
- data/lib/eryph/compute/generated/spec/models/operation_status_spec.rb +30 -0
- data/lib/eryph/compute/generated/spec/models/operation_task_reference_spec.rb +48 -0
- data/lib/eryph/compute/generated/spec/models/operation_task_spec.rb +72 -0
- data/lib/eryph/compute/generated/spec/models/operation_task_status_spec.rb +30 -0
- data/lib/eryph/compute/generated/spec/models/populate_catlet_config_variables_request_spec.rb +42 -0
- data/lib/eryph/compute/generated/spec/models/problem_details_spec.rb +60 -0
- data/lib/eryph/compute/generated/spec/models/project_list_spec.rb +36 -0
- data/lib/eryph/compute/generated/spec/models/project_member_role_list_spec.rb +36 -0
- data/lib/eryph/compute/generated/spec/models/project_member_role_spec.rb +60 -0
- data/lib/eryph/compute/generated/spec/models/project_spec.rb +48 -0
- data/lib/eryph/compute/generated/spec/models/resource_type_spec.rb +30 -0
- data/lib/eryph/compute/generated/spec/models/stop_catlet_request_body_spec.rb +36 -0
- data/lib/eryph/compute/generated/spec/models/task_reference_type_spec.rb +30 -0
- data/lib/eryph/compute/generated/spec/models/update_catlet_request_body_spec.rb +42 -0
- data/lib/eryph/compute/generated/spec/models/update_project_networks_request_body_spec.rb +42 -0
- data/lib/eryph/compute/generated/spec/models/validate_config_request_spec.rb +36 -0
- data/lib/eryph/compute/generated/spec/models/validation_issue_spec.rb +42 -0
- data/lib/eryph/compute/generated/spec/models/virtual_disk_attached_catlet_spec.rb +42 -0
- data/lib/eryph/compute/generated/spec/models/virtual_disk_gene_info_spec.rb +48 -0
- data/lib/eryph/compute/generated/spec/models/virtual_disk_list_spec.rb +36 -0
- data/lib/eryph/compute/generated/spec/models/virtual_disk_spec.rb +108 -0
- data/lib/eryph/compute/generated/spec/models/virtual_network_configuration_spec.rb +36 -0
- data/lib/eryph/compute/generated/spec/models/virtual_network_list_spec.rb +36 -0
- data/lib/eryph/compute/generated/spec/models/virtual_network_spec.rb +66 -0
- data/lib/eryph/compute/generated/spec/spec_helper.rb +111 -0
- data/lib/eryph/compute/generated.rb +137 -0
- data/lib/eryph/compute/operation_result.rb +255 -0
- data/lib/eryph/compute/operation_tracker.rb +247 -0
- data/lib/eryph/compute/problem_details_error.rb +139 -0
- data/lib/eryph/compute/version.rb +6 -0
- data/lib/eryph/compute.rb +40 -0
- data/lib/eryph/version.rb +4 -0
- data/lib/eryph.rb +68 -0
- metadata +329 -0
@@ -0,0 +1,206 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
module Eryph
|
5
|
+
module ClientRuntime
|
6
|
+
# Represents a configuration store for eryph client settings
|
7
|
+
# Manages JSON configuration files and private key storage
|
8
|
+
class ConfigStore
|
9
|
+
STORE_DIR = '.eryph'.freeze
|
10
|
+
PRIVATE_DIR = 'private'.freeze
|
11
|
+
CONFIG_EXTENSION = '.config'.freeze
|
12
|
+
|
13
|
+
# @return [String] base path for this configuration store
|
14
|
+
attr_reader :base_path
|
15
|
+
|
16
|
+
# @return [String] configuration name
|
17
|
+
attr_reader :config_name
|
18
|
+
|
19
|
+
# @return [Environment] environment abstraction
|
20
|
+
attr_reader :environment
|
21
|
+
|
22
|
+
# Initialize a configuration store
|
23
|
+
# @param base_path [String] base directory path
|
24
|
+
# @param config_name [String] configuration name
|
25
|
+
# @param environment [Environment] environment abstraction
|
26
|
+
def initialize(base_path, config_name, environment)
|
27
|
+
@base_path = base_path
|
28
|
+
@config_name = config_name
|
29
|
+
@environment = environment
|
30
|
+
end
|
31
|
+
|
32
|
+
# Check if this configuration store exists
|
33
|
+
# @return [Boolean] true if the store exists
|
34
|
+
def exists?
|
35
|
+
@environment.file_exists?(config_file_path)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Get the configuration data
|
39
|
+
# @return [Hash] parsed configuration data
|
40
|
+
# @raise [ConfigurationError] if configuration cannot be read or parsed
|
41
|
+
def configuration
|
42
|
+
return {} unless exists?
|
43
|
+
|
44
|
+
begin
|
45
|
+
content = @environment.read_file(config_file_path)
|
46
|
+
JSON.parse(content)
|
47
|
+
rescue JSON::ParserError => e
|
48
|
+
raise ConfigurationError, "Invalid JSON in configuration file #{config_file_path}: #{e.message}"
|
49
|
+
rescue IOError => e
|
50
|
+
raise ConfigurationError, "Cannot read configuration file #{config_file_path}: #{e.message}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Get endpoints from this configuration store
|
55
|
+
# @return [Hash] endpoint name -> URL mapping
|
56
|
+
def endpoints
|
57
|
+
config = configuration
|
58
|
+
config['endpoints'] || {}
|
59
|
+
end
|
60
|
+
|
61
|
+
# Get clients from this configuration store
|
62
|
+
# @return [Array<Hash>] array of client configurations
|
63
|
+
def clients
|
64
|
+
config = configuration
|
65
|
+
config['clients'] || []
|
66
|
+
end
|
67
|
+
|
68
|
+
# Get the default client ID
|
69
|
+
# @return [String, nil] default client ID
|
70
|
+
def default_client_id
|
71
|
+
config = configuration
|
72
|
+
config['defaultClient']
|
73
|
+
end
|
74
|
+
|
75
|
+
# Get a client configuration by ID
|
76
|
+
# @param client_id [String] client ID to lookup
|
77
|
+
# @return [Hash, nil] client configuration or nil if not found
|
78
|
+
def get_client(client_id)
|
79
|
+
clients.find { |client| client['id'] == client_id }
|
80
|
+
end
|
81
|
+
|
82
|
+
# Get the default client configuration
|
83
|
+
# @return [Hash, nil] default client configuration or nil if not found
|
84
|
+
def default_client
|
85
|
+
config = configuration
|
86
|
+
default_client_id = config['defaultClientId']
|
87
|
+
|
88
|
+
all_clients = clients
|
89
|
+
return nil if all_clients.empty?
|
90
|
+
|
91
|
+
# If explicit defaultClient is set, use that
|
92
|
+
return all_clients.find { |client| client['id'] == default_client_id } if default_client_id
|
93
|
+
|
94
|
+
# Otherwise, return first client
|
95
|
+
all_clients.first
|
96
|
+
end
|
97
|
+
|
98
|
+
# Get the private key for a client configuration
|
99
|
+
# @param client_config [Hash] client configuration (may include '_store' key)
|
100
|
+
# @return [String, nil] private key content or nil if not found
|
101
|
+
def get_client_private_key(client_config)
|
102
|
+
client_id = client_config['id']
|
103
|
+
return nil unless client_id
|
104
|
+
|
105
|
+
get_private_key(client_id)
|
106
|
+
end
|
107
|
+
|
108
|
+
# Get the private key for a client
|
109
|
+
# @param client_id [String] client ID
|
110
|
+
# @return [String, nil] private key content or nil if not found
|
111
|
+
def get_private_key(client_id)
|
112
|
+
key_path = private_key_path(client_id)
|
113
|
+
return nil unless @environment.file_exists?(key_path)
|
114
|
+
|
115
|
+
begin
|
116
|
+
@environment.read_file(key_path)
|
117
|
+
rescue IOError
|
118
|
+
nil
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# Store a client configuration
|
123
|
+
# @param client_config [Hash] client configuration
|
124
|
+
# @raise [ConfigurationError] if configuration cannot be saved
|
125
|
+
def store_client(client_config)
|
126
|
+
config = configuration
|
127
|
+
config['clients'] ||= []
|
128
|
+
|
129
|
+
# Remove existing client with same ID
|
130
|
+
config['clients'].reject! { |c| c['id'] == client_config['id'] }
|
131
|
+
|
132
|
+
# Add new client
|
133
|
+
config['clients'] << client_config
|
134
|
+
|
135
|
+
# Set as default if no default exists
|
136
|
+
config['defaultClient'] ||= client_config['id']
|
137
|
+
|
138
|
+
save_configuration(config)
|
139
|
+
end
|
140
|
+
|
141
|
+
# Store a private key for a client
|
142
|
+
# @param client_id [String] client ID
|
143
|
+
# @param private_key [String] private key content (PEM format)
|
144
|
+
# @raise [ConfigurationError] if private key cannot be saved
|
145
|
+
def store_private_key(client_id, private_key)
|
146
|
+
key_path = private_key_path(client_id)
|
147
|
+
|
148
|
+
begin
|
149
|
+
@environment.ensure_directory(File.dirname(key_path))
|
150
|
+
@environment.write_file(key_path, private_key)
|
151
|
+
|
152
|
+
# Set restrictive permissions on Unix-like systems
|
153
|
+
File.chmod(0o600, key_path) unless @environment.windows?
|
154
|
+
rescue IOError => e
|
155
|
+
raise ConfigurationError, "Cannot store private key: #{e.message}"
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
# Store an endpoint
|
160
|
+
# @param endpoint_name [String] endpoint name
|
161
|
+
# @param endpoint_url [String] endpoint URL
|
162
|
+
# @raise [ConfigurationError] if endpoint cannot be saved
|
163
|
+
def store_endpoint(endpoint_name, endpoint_url)
|
164
|
+
config = configuration
|
165
|
+
config['endpoints'] ||= {}
|
166
|
+
config['endpoints'][endpoint_name] = endpoint_url
|
167
|
+
save_configuration(config)
|
168
|
+
end
|
169
|
+
|
170
|
+
# Get the full path to the store directory
|
171
|
+
# @return [String] store directory path
|
172
|
+
def store_path
|
173
|
+
File.join(@base_path, STORE_DIR)
|
174
|
+
end
|
175
|
+
|
176
|
+
# Get the full path to the configuration file
|
177
|
+
# @return [String] configuration file path
|
178
|
+
def config_file_path
|
179
|
+
File.join(store_path, "#{@config_name}#{CONFIG_EXTENSION}")
|
180
|
+
end
|
181
|
+
|
182
|
+
# Get the full path to the private key directory
|
183
|
+
# @return [String] private key directory path
|
184
|
+
def private_key_directory
|
185
|
+
File.join(store_path, PRIVATE_DIR)
|
186
|
+
end
|
187
|
+
|
188
|
+
# Get the full path to a specific private key file
|
189
|
+
# @param client_id [String] client ID
|
190
|
+
# @return [String] private key file path
|
191
|
+
def private_key_path(client_id)
|
192
|
+
File.join(private_key_directory, "#{client_id}.key")
|
193
|
+
end
|
194
|
+
|
195
|
+
private
|
196
|
+
|
197
|
+
def save_configuration(config)
|
198
|
+
@environment.ensure_directory(store_path)
|
199
|
+
content = JSON.pretty_generate(config)
|
200
|
+
@environment.write_file(config_file_path, content)
|
201
|
+
rescue IOError => e
|
202
|
+
raise ConfigurationError, "Cannot save configuration: #{e.message}"
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
@@ -0,0 +1,211 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module Eryph
|
4
|
+
module ClientRuntime
|
5
|
+
# Reads configuration from multiple configuration stores
|
6
|
+
# Follows the hierarchy: Current Directory -> User -> System
|
7
|
+
class ConfigStoresReader
|
8
|
+
# @return [Environment] environment abstraction
|
9
|
+
attr_reader :environment
|
10
|
+
|
11
|
+
# @return [Logger] logger instance
|
12
|
+
attr_reader :logger
|
13
|
+
|
14
|
+
# Initialize the configuration stores reader
|
15
|
+
# @param environment [Environment] environment abstraction
|
16
|
+
# @param logger [Logger, nil] logger instance
|
17
|
+
def initialize(environment, logger: nil)
|
18
|
+
@environment = environment
|
19
|
+
@logger = logger || Logger.new($stdout).tap { |l| l.level = Logger::WARN }
|
20
|
+
end
|
21
|
+
|
22
|
+
# Get all configuration stores for the specified configuration name
|
23
|
+
# @param config_name [String] configuration name
|
24
|
+
# @return [Array<ConfigStore>] array of configuration stores in priority order
|
25
|
+
def get_stores(config_name)
|
26
|
+
locations = [
|
27
|
+
Environment::ConfigStoreLocation::CURRENT_DIRECTORY,
|
28
|
+
Environment::ConfigStoreLocation::USER,
|
29
|
+
Environment::ConfigStoreLocation::SYSTEM,
|
30
|
+
]
|
31
|
+
|
32
|
+
locations.map do |location|
|
33
|
+
base_path = @environment.get_config_path(location)
|
34
|
+
ConfigStore.new(base_path, config_name, @environment)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Get all existing configuration stores for the specified configuration name
|
39
|
+
# @param config_name [String] configuration name
|
40
|
+
# @return [Array<ConfigStore>] array of existing configuration stores in priority order
|
41
|
+
def get_existing_stores(config_name)
|
42
|
+
stores = get_stores(config_name)
|
43
|
+
existing = stores.select(&:exists?)
|
44
|
+
@logger.debug("get_existing_stores: config=#{config_name}, found #{existing.length} of #{stores.length} stores")
|
45
|
+
existing.each_with_index do |store, idx|
|
46
|
+
@logger.debug("get_existing_stores: [#{idx}] #{store.base_path}")
|
47
|
+
end
|
48
|
+
existing
|
49
|
+
end
|
50
|
+
|
51
|
+
# Get merged configuration from all stores
|
52
|
+
# @param config_name [String] configuration name
|
53
|
+
# @return [Hash] merged configuration data
|
54
|
+
def get_merged_configuration(config_name)
|
55
|
+
stores = get_existing_stores(config_name)
|
56
|
+
merged_config = {}
|
57
|
+
|
58
|
+
# Merge configurations in reverse order (system -> user -> current)
|
59
|
+
# so that higher priority stores override lower priority ones
|
60
|
+
stores.reverse.each do |store|
|
61
|
+
config = store.configuration
|
62
|
+
merged_config = deep_merge(merged_config, config)
|
63
|
+
end
|
64
|
+
|
65
|
+
merged_config
|
66
|
+
end
|
67
|
+
|
68
|
+
# Get all endpoints from all stores
|
69
|
+
# @param config_name [String] configuration name
|
70
|
+
# @return [Hash] endpoint name -> URL mapping from all stores
|
71
|
+
def get_all_endpoints(config_name)
|
72
|
+
merged_config = get_merged_configuration(config_name)
|
73
|
+
merged_config['endpoints'] || {}
|
74
|
+
end
|
75
|
+
|
76
|
+
# Get all clients from all stores
|
77
|
+
# @param config_name [String] configuration name
|
78
|
+
# @return [Array<Hash>] array of all client configurations
|
79
|
+
def get_all_clients(config_name)
|
80
|
+
@logger.debug("get_all_clients: config=#{config_name}")
|
81
|
+
|
82
|
+
stores = get_existing_stores(config_name)
|
83
|
+
all_clients = []
|
84
|
+
|
85
|
+
stores.each do |store|
|
86
|
+
clients = store.clients
|
87
|
+
@logger.debug("get_all_clients: store #{store.base_path} has #{clients ? clients.length : 0} clients")
|
88
|
+
next unless clients
|
89
|
+
|
90
|
+
clients.each do |client|
|
91
|
+
# Add store reference to client for private key lookup
|
92
|
+
client_with_store = client.dup
|
93
|
+
client_with_store['_store'] = store
|
94
|
+
all_clients << client_with_store
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
@logger.debug("get_all_clients: total clients before dedup=#{all_clients.length}")
|
99
|
+
|
100
|
+
# Remove duplicates by ID, keeping the highest priority (first found)
|
101
|
+
seen_ids = Set.new
|
102
|
+
unique_clients = all_clients.select do |client|
|
103
|
+
if seen_ids.include?(client['id'])
|
104
|
+
false
|
105
|
+
else
|
106
|
+
seen_ids.add(client['id'])
|
107
|
+
true
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
@logger.debug("get_all_clients: unique clients=#{unique_clients.length}")
|
112
|
+
unique_clients
|
113
|
+
end
|
114
|
+
|
115
|
+
# Get the default client configuration
|
116
|
+
# @param config_name [String] configuration name
|
117
|
+
# @return [Hash, nil] default client configuration or nil if not found
|
118
|
+
def get_default_client(config_name)
|
119
|
+
@logger.debug("get_default_client: config=#{config_name}")
|
120
|
+
|
121
|
+
merged_config = get_merged_configuration(config_name)
|
122
|
+
default_client_id = merged_config['defaultClientId']
|
123
|
+
@logger.debug("get_default_client: defaultClientId=#{default_client_id || 'nil'}")
|
124
|
+
|
125
|
+
all_clients = get_all_clients(config_name)
|
126
|
+
@logger.debug("get_default_client: total_clients=#{all_clients.length}")
|
127
|
+
|
128
|
+
if all_clients.empty?
|
129
|
+
@logger.debug('get_default_client: no clients found')
|
130
|
+
return nil
|
131
|
+
end
|
132
|
+
|
133
|
+
# If explicit defaultClient is set, use that
|
134
|
+
if default_client_id
|
135
|
+
client = all_clients.find { |client| client['id'] == default_client_id }
|
136
|
+
if client
|
137
|
+
@logger.debug("get_default_client: found by defaultClientId=#{client['id']}")
|
138
|
+
return client
|
139
|
+
else
|
140
|
+
@logger.debug("get_default_client: defaultClientId '#{default_client_id}' not found")
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# Otherwise, look for client with IsDefault=true (PowerShell -AsDefault flag)
|
145
|
+
# Try both case variations since PowerShell uses IsDefault, Ruby might use isDefault
|
146
|
+
default_client = all_clients.find { |client| client['IsDefault'] == true || client['isDefault'] == true }
|
147
|
+
if default_client
|
148
|
+
@logger.debug("get_default_client: found by IsDefault flag=#{default_client['id']}")
|
149
|
+
return default_client
|
150
|
+
end
|
151
|
+
|
152
|
+
@logger.debug('get_default_client: no default client found')
|
153
|
+
nil
|
154
|
+
end
|
155
|
+
|
156
|
+
# Get a specific client configuration by ID
|
157
|
+
# @param config_name [String] configuration name
|
158
|
+
# @param client_id [String] client ID to lookup
|
159
|
+
# @return [Hash, nil] client configuration or nil if not found
|
160
|
+
def get_client(config_name, client_id)
|
161
|
+
all_clients = get_all_clients(config_name)
|
162
|
+
all_clients.find { |client| client['id'] == client_id }
|
163
|
+
end
|
164
|
+
|
165
|
+
# Get the private key for a client
|
166
|
+
# @param client_config [Hash] client configuration (must include '_store' key)
|
167
|
+
# @return [String, nil] private key content or nil if not found
|
168
|
+
def get_client_private_key(client_config)
|
169
|
+
store = client_config['_store']
|
170
|
+
return nil unless store
|
171
|
+
|
172
|
+
store.get_private_key(client_config['id'])
|
173
|
+
end
|
174
|
+
|
175
|
+
# Get the writable store for the current user
|
176
|
+
# @param config_name [String] configuration name
|
177
|
+
# @return [ConfigStore] user configuration store
|
178
|
+
def get_writable_store(config_name)
|
179
|
+
base_path = if @environment.admin_user?
|
180
|
+
# If running as admin, prefer system store
|
181
|
+
@environment.get_config_path(Environment::ConfigStoreLocation::SYSTEM)
|
182
|
+
else
|
183
|
+
# Otherwise use user store
|
184
|
+
@environment.get_config_path(Environment::ConfigStoreLocation::USER)
|
185
|
+
end
|
186
|
+
|
187
|
+
ConfigStore.new(base_path, config_name, @environment)
|
188
|
+
end
|
189
|
+
|
190
|
+
private
|
191
|
+
|
192
|
+
# Deep merge two hashes
|
193
|
+
# @param hash1 [Hash] first hash
|
194
|
+
# @param hash2 [Hash] second hash
|
195
|
+
# @return [Hash] merged hash
|
196
|
+
def deep_merge(hash1, hash2)
|
197
|
+
result = hash1.dup
|
198
|
+
|
199
|
+
hash2.each do |key, value|
|
200
|
+
result[key] = if result[key].is_a?(Hash) && value.is_a?(Hash)
|
201
|
+
deep_merge(result[key], value)
|
202
|
+
else
|
203
|
+
value
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
result
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
@@ -0,0 +1,226 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module Eryph
|
4
|
+
module ClientRuntime
|
5
|
+
# Looks up endpoint URLs from configuration stores
|
6
|
+
# Handles special configurations like 'zero' with local endpoint discovery
|
7
|
+
class EndpointLookup
|
8
|
+
# @return [ConfigStoresReader] configuration stores reader
|
9
|
+
attr_reader :reader
|
10
|
+
|
11
|
+
# @return [String] configuration name
|
12
|
+
attr_reader :config_name
|
13
|
+
|
14
|
+
# @return [Logger] logger instance
|
15
|
+
attr_reader :logger
|
16
|
+
|
17
|
+
# Initialize endpoint lookup
|
18
|
+
# @param reader [ConfigStoresReader] configuration stores reader
|
19
|
+
# @param config_name [String] configuration name
|
20
|
+
# @param logger [Logger, nil] logger instance
|
21
|
+
def initialize(reader, config_name, logger: nil)
|
22
|
+
@reader = reader
|
23
|
+
@config_name = config_name
|
24
|
+
@logger = logger || Logger.new($stdout).tap { |l| l.level = Logger::WARN }
|
25
|
+
end
|
26
|
+
|
27
|
+
# Get an endpoint URL by name
|
28
|
+
# @param endpoint_name [String] endpoint name to lookup
|
29
|
+
# @return [String, nil] endpoint URL or nil if not found
|
30
|
+
def endpoint(endpoint_name)
|
31
|
+
@logger.debug("get_endpoint: looking for '#{endpoint_name}' in config '#{@config_name}'")
|
32
|
+
|
33
|
+
# First try configuration store endpoints
|
34
|
+
store_endpoints = @reader.get_all_endpoints(@config_name)
|
35
|
+
@logger.debug("get_endpoint: store_endpoints count=#{store_endpoints.length}")
|
36
|
+
|
37
|
+
endpoint_url = store_endpoints[endpoint_name]
|
38
|
+
if endpoint_url
|
39
|
+
@logger.debug("get_endpoint: found in config store=#{endpoint_url}")
|
40
|
+
return endpoint_url
|
41
|
+
end
|
42
|
+
|
43
|
+
# Then try local endpoints for special configurations
|
44
|
+
local_eps = local_endpoints
|
45
|
+
@logger.debug("get_endpoint: local_endpoints count=#{local_eps.length}")
|
46
|
+
|
47
|
+
endpoint_url = local_eps[endpoint_name]
|
48
|
+
if endpoint_url
|
49
|
+
@logger.debug("get_endpoint: found in local endpoints=#{endpoint_url}")
|
50
|
+
else
|
51
|
+
@logger.debug('get_endpoint: not found anywhere')
|
52
|
+
end
|
53
|
+
|
54
|
+
endpoint_url
|
55
|
+
end
|
56
|
+
|
57
|
+
# Get all available endpoints
|
58
|
+
# @return [Hash] endpoint name -> URL mapping
|
59
|
+
def all_endpoints
|
60
|
+
store_endpoints = @reader.get_all_endpoints(@config_name)
|
61
|
+
local_eps = local_endpoints
|
62
|
+
|
63
|
+
# Local endpoints have lower priority than store endpoints
|
64
|
+
local_eps.merge(store_endpoints)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Check if an endpoint exists
|
68
|
+
# @param endpoint_name [String] endpoint name to check
|
69
|
+
# @return [Boolean] true if endpoint exists
|
70
|
+
def endpoint_exists?(endpoint_name)
|
71
|
+
!endpoint(endpoint_name).nil?
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
# Get local endpoints for special configurations
|
77
|
+
# @return [Hash] endpoint name -> URL mapping
|
78
|
+
def local_endpoints
|
79
|
+
@logger.debug("get_local_endpoints: config=#{@config_name}")
|
80
|
+
|
81
|
+
case @config_name&.downcase
|
82
|
+
when 'zero'
|
83
|
+
@logger.debug('get_local_endpoints: checking zero config')
|
84
|
+
zero_endpoints
|
85
|
+
when 'local'
|
86
|
+
@logger.debug('get_local_endpoints: checking local config')
|
87
|
+
get_local_endpoints_for_config('local')
|
88
|
+
else
|
89
|
+
@logger.debug('get_local_endpoints: no special config, returning empty')
|
90
|
+
{}
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Get endpoints for eryph-zero configuration
|
95
|
+
# This discovers running eryph-zero instances from runtime lock files
|
96
|
+
# @return [Hash] endpoint name -> URL mapping
|
97
|
+
def zero_endpoints
|
98
|
+
require_relative 'local_identity_provider_info'
|
99
|
+
|
100
|
+
# Try to discover running identity provider
|
101
|
+
provider_info = LocalIdentityProviderInfo.new(@reader.environment, 'zero', logger: @logger)
|
102
|
+
|
103
|
+
if provider_info.running?
|
104
|
+
endpoints_hash = provider_info.endpoints
|
105
|
+
|
106
|
+
# Convert URI objects to strings and map to expected names
|
107
|
+
result = {}
|
108
|
+
endpoints_hash.each do |name, uri|
|
109
|
+
case name.downcase
|
110
|
+
when 'identity'
|
111
|
+
result['identity'] = uri.to_s
|
112
|
+
when 'compute'
|
113
|
+
result['compute'] = uri.to_s
|
114
|
+
else
|
115
|
+
# Include other endpoints as-is
|
116
|
+
result[name] = uri.to_s
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# If we have identity but no compute, derive compute endpoint
|
121
|
+
if result['identity'] && !result['compute']
|
122
|
+
identity_uri = URI.parse(result['identity'])
|
123
|
+
result['compute'] = "#{identity_uri.scheme}://#{identity_uri.host}:#{identity_uri.port}/compute"
|
124
|
+
end
|
125
|
+
|
126
|
+
return result
|
127
|
+
end
|
128
|
+
|
129
|
+
# Fallback to common local endpoints if no runtime info found
|
130
|
+
fallback_zero_endpoints
|
131
|
+
end
|
132
|
+
|
133
|
+
# Fallback endpoint discovery for eryph-zero when runtime file is not available
|
134
|
+
# @return [Hash] endpoint name -> URL mapping
|
135
|
+
def fallback_zero_endpoints
|
136
|
+
endpoints = {}
|
137
|
+
|
138
|
+
# Try common local endpoints for eryph-zero
|
139
|
+
zero_candidates = [
|
140
|
+
'https://localhost:8080',
|
141
|
+
'https://127.0.0.1:8080',
|
142
|
+
'http://localhost:8080',
|
143
|
+
'http://127.0.0.1:8080',
|
144
|
+
]
|
145
|
+
|
146
|
+
zero_candidates.each do |candidate_url|
|
147
|
+
next unless test_zero_endpoint(candidate_url)
|
148
|
+
|
149
|
+
endpoints['identity'] = candidate_url
|
150
|
+
endpoints['compute'] = "#{candidate_url}/compute"
|
151
|
+
break
|
152
|
+
end
|
153
|
+
|
154
|
+
endpoints
|
155
|
+
end
|
156
|
+
|
157
|
+
# Get endpoints for eryph-local configuration
|
158
|
+
# This discovers running eryph-local instances from runtime lock files
|
159
|
+
# @return [Hash] endpoint name -> URL mapping
|
160
|
+
def get_local_endpoints_for_config(config_name)
|
161
|
+
require_relative 'local_identity_provider_info'
|
162
|
+
|
163
|
+
# Try to discover running identity provider
|
164
|
+
provider_info = LocalIdentityProviderInfo.new(@reader.environment, config_name, logger: @logger)
|
165
|
+
|
166
|
+
if provider_info.running?
|
167
|
+
endpoints_hash = provider_info.endpoints
|
168
|
+
|
169
|
+
# Convert URI objects to strings and map to expected names
|
170
|
+
result = {}
|
171
|
+
endpoints_hash.each do |name, uri|
|
172
|
+
case name.downcase
|
173
|
+
when 'identity'
|
174
|
+
result['identity'] = uri.to_s
|
175
|
+
when 'compute'
|
176
|
+
result['compute'] = uri.to_s
|
177
|
+
else
|
178
|
+
# Include other endpoints as-is
|
179
|
+
result[name] = uri.to_s
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
# If we have identity but no compute, derive compute endpoint
|
184
|
+
if result['identity'] && !result['compute']
|
185
|
+
identity_uri = URI.parse(result['identity'])
|
186
|
+
result['compute'] = "#{identity_uri.scheme}://#{identity_uri.host}:#{identity_uri.port}/compute"
|
187
|
+
end
|
188
|
+
|
189
|
+
return result
|
190
|
+
end
|
191
|
+
|
192
|
+
# No fallback for local config - if not running, return empty
|
193
|
+
{}
|
194
|
+
end
|
195
|
+
|
196
|
+
# Test if a URL hosts an eryph-zero instance
|
197
|
+
# @param base_url [String] base URL to test
|
198
|
+
# @return [Boolean] true if eryph-zero is detected
|
199
|
+
def test_zero_endpoint(base_url)
|
200
|
+
# This is a simplified test - in a full implementation you might:
|
201
|
+
# 1. Make an HTTP request to check for eryph-zero health endpoint
|
202
|
+
# 2. Check for specific response headers or content
|
203
|
+
# 3. Verify SSL certificates if applicable
|
204
|
+
|
205
|
+
# In test environments, don't activate fallback endpoints
|
206
|
+
# This prevents tests from accidentally finding "running" services
|
207
|
+
# when they're testing the "not running" scenario
|
208
|
+
return false if @reader.environment.instance_of?(::TestEnvironment)
|
209
|
+
|
210
|
+
# For now, we'll just check if the URL format is valid
|
211
|
+
# In production, this might make actual HTTP requests
|
212
|
+
begin
|
213
|
+
uri = URI.parse(base_url)
|
214
|
+
# Check if scheme, host, and explicit port are present
|
215
|
+
has_scheme = !uri.scheme.nil?
|
216
|
+
has_host = !uri.host.nil? && !uri.host.empty?
|
217
|
+
has_explicit_port = base_url.include?(':') && base_url.match(/:(\d+)/)
|
218
|
+
|
219
|
+
!(has_scheme && has_host && has_explicit_port).nil?
|
220
|
+
rescue URI::InvalidURIError
|
221
|
+
false
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|