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.
Files changed (222) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +47 -0
  3. data/LICENSE +21 -0
  4. data/README.md +144 -0
  5. data/eryph-clientruntime.gemspec +42 -0
  6. data/eryph-compute.gemspec +44 -0
  7. data/lib/eryph/clientruntime/client_credentials_lookup.rb +295 -0
  8. data/lib/eryph/clientruntime/config_store.rb +206 -0
  9. data/lib/eryph/clientruntime/config_stores_reader.rb +211 -0
  10. data/lib/eryph/clientruntime/endpoint_lookup.rb +226 -0
  11. data/lib/eryph/clientruntime/environment.rb +440 -0
  12. data/lib/eryph/clientruntime/local_identity_provider_info.rb +153 -0
  13. data/lib/eryph/clientruntime/token_provider.rb +275 -0
  14. data/lib/eryph/clientruntime/version.rb +6 -0
  15. data/lib/eryph/clientruntime.rb +79 -0
  16. data/lib/eryph/compute/client.rb +442 -0
  17. data/lib/eryph/compute/generated/Gemfile +9 -0
  18. data/lib/eryph/compute/generated/README.md +206 -0
  19. data/lib/eryph/compute/generated/Rakefile +10 -0
  20. data/lib/eryph/compute/generated/compute_client.gemspec +41 -0
  21. data/lib/eryph/compute/generated/docs/ApiVersion.md +20 -0
  22. data/lib/eryph/compute/generated/docs/ApiVersionResponse.md +18 -0
  23. data/lib/eryph/compute/generated/docs/Catlet.md +32 -0
  24. data/lib/eryph/compute/generated/docs/CatletConfigOperationResult.md +18 -0
  25. data/lib/eryph/compute/generated/docs/CatletConfigValidationResult.md +20 -0
  26. data/lib/eryph/compute/generated/docs/CatletConfiguration.md +18 -0
  27. data/lib/eryph/compute/generated/docs/CatletDrive.md +20 -0
  28. data/lib/eryph/compute/generated/docs/CatletDriveType.md +15 -0
  29. data/lib/eryph/compute/generated/docs/CatletList.md +18 -0
  30. data/lib/eryph/compute/generated/docs/CatletNetwork.md +30 -0
  31. data/lib/eryph/compute/generated/docs/CatletNetworkAdapter.md +20 -0
  32. data/lib/eryph/compute/generated/docs/CatletStatus.md +15 -0
  33. data/lib/eryph/compute/generated/docs/CatletStopMode.md +15 -0
  34. data/lib/eryph/compute/generated/docs/CatletsApi.md +863 -0
  35. data/lib/eryph/compute/generated/docs/DiskStatus.md +15 -0
  36. data/lib/eryph/compute/generated/docs/ExpandCatletConfigRequestBody.md +22 -0
  37. data/lib/eryph/compute/generated/docs/ExpandNewCatletConfigRequest.md +22 -0
  38. data/lib/eryph/compute/generated/docs/FloatingNetworkPort.md +26 -0
  39. data/lib/eryph/compute/generated/docs/Gene.md +30 -0
  40. data/lib/eryph/compute/generated/docs/GeneList.md +18 -0
  41. data/lib/eryph/compute/generated/docs/GeneType.md +15 -0
  42. data/lib/eryph/compute/generated/docs/GeneWithUsage.md +34 -0
  43. data/lib/eryph/compute/generated/docs/GenesApi.md +281 -0
  44. data/lib/eryph/compute/generated/docs/NewCatletRequest.md +20 -0
  45. data/lib/eryph/compute/generated/docs/NewProjectMemberBody.md +22 -0
  46. data/lib/eryph/compute/generated/docs/NewProjectRequest.md +20 -0
  47. data/lib/eryph/compute/generated/docs/NewVirtualDiskRequest.md +30 -0
  48. data/lib/eryph/compute/generated/docs/Operation.md +32 -0
  49. data/lib/eryph/compute/generated/docs/OperationList.md +18 -0
  50. data/lib/eryph/compute/generated/docs/OperationLogEntry.md +24 -0
  51. data/lib/eryph/compute/generated/docs/OperationResource.md +22 -0
  52. data/lib/eryph/compute/generated/docs/OperationResult.md +18 -0
  53. data/lib/eryph/compute/generated/docs/OperationStatus.md +15 -0
  54. data/lib/eryph/compute/generated/docs/OperationTask.md +30 -0
  55. data/lib/eryph/compute/generated/docs/OperationTaskReference.md +22 -0
  56. data/lib/eryph/compute/generated/docs/OperationTaskStatus.md +15 -0
  57. data/lib/eryph/compute/generated/docs/OperationsApi.md +157 -0
  58. data/lib/eryph/compute/generated/docs/PopulateCatletConfigVariablesRequest.md +20 -0
  59. data/lib/eryph/compute/generated/docs/ProblemDetails.md +26 -0
  60. data/lib/eryph/compute/generated/docs/Project.md +22 -0
  61. data/lib/eryph/compute/generated/docs/ProjectList.md +18 -0
  62. data/lib/eryph/compute/generated/docs/ProjectMemberRole.md +26 -0
  63. data/lib/eryph/compute/generated/docs/ProjectMemberRoleList.md +18 -0
  64. data/lib/eryph/compute/generated/docs/ProjectMembersApi.md +293 -0
  65. data/lib/eryph/compute/generated/docs/ProjectsApi.md +286 -0
  66. data/lib/eryph/compute/generated/docs/ResourceType.md +15 -0
  67. data/lib/eryph/compute/generated/docs/StopCatletRequestBody.md +18 -0
  68. data/lib/eryph/compute/generated/docs/TaskReferenceType.md +15 -0
  69. data/lib/eryph/compute/generated/docs/UpdateCatletRequestBody.md +20 -0
  70. data/lib/eryph/compute/generated/docs/UpdateProjectNetworksRequestBody.md +20 -0
  71. data/lib/eryph/compute/generated/docs/ValidateConfigRequest.md +18 -0
  72. data/lib/eryph/compute/generated/docs/ValidationIssue.md +20 -0
  73. data/lib/eryph/compute/generated/docs/VersionApi.md +69 -0
  74. data/lib/eryph/compute/generated/docs/VirtualDisk.md +42 -0
  75. data/lib/eryph/compute/generated/docs/VirtualDiskAttachedCatlet.md +20 -0
  76. data/lib/eryph/compute/generated/docs/VirtualDiskGeneInfo.md +22 -0
  77. data/lib/eryph/compute/generated/docs/VirtualDiskList.md +18 -0
  78. data/lib/eryph/compute/generated/docs/VirtualDisksApi.md +291 -0
  79. data/lib/eryph/compute/generated/docs/VirtualNetwork.md +28 -0
  80. data/lib/eryph/compute/generated/docs/VirtualNetworkConfiguration.md +18 -0
  81. data/lib/eryph/compute/generated/docs/VirtualNetworkList.md +18 -0
  82. data/lib/eryph/compute/generated/docs/VirtualNetworksApi.md +291 -0
  83. data/lib/eryph/compute/generated/git_push.sh +57 -0
  84. data/lib/eryph/compute/generated/lib/compute_client/api/catlets_api.rb +812 -0
  85. data/lib/eryph/compute/generated/lib/compute_client/api/genes_api.rb +262 -0
  86. data/lib/eryph/compute/generated/lib/compute_client/api/operations_api.rb +154 -0
  87. data/lib/eryph/compute/generated/lib/compute_client/api/project_members_api.rb +297 -0
  88. data/lib/eryph/compute/generated/lib/compute_client/api/projects_api.rb +269 -0
  89. data/lib/eryph/compute/generated/lib/compute_client/api/version_api.rb +79 -0
  90. data/lib/eryph/compute/generated/lib/compute_client/api/virtual_disks_api.rb +272 -0
  91. data/lib/eryph/compute/generated/lib/compute_client/api/virtual_networks_api.rb +282 -0
  92. data/lib/eryph/compute/generated/lib/compute_client/api_client.rb +437 -0
  93. data/lib/eryph/compute/generated/lib/compute_client/api_error.rb +58 -0
  94. data/lib/eryph/compute/generated/lib/compute_client/configuration.rb +392 -0
  95. data/lib/eryph/compute/generated/lib/compute_client/models/api_version.rb +263 -0
  96. data/lib/eryph/compute/generated/lib/compute_client/models/api_version_response.rb +237 -0
  97. data/lib/eryph/compute/generated/lib/compute_client/models/catlet.rb +400 -0
  98. data/lib/eryph/compute/generated/lib/compute_client/models/catlet_config_operation_result.rb +234 -0
  99. data/lib/eryph/compute/generated/lib/compute_client/models/catlet_config_validation_result.rb +251 -0
  100. data/lib/eryph/compute/generated/lib/compute_client/models/catlet_configuration.rb +223 -0
  101. data/lib/eryph/compute/generated/lib/compute_client/models/catlet_drive.rb +270 -0
  102. data/lib/eryph/compute/generated/lib/compute_client/models/catlet_drive_type.rb +43 -0
  103. data/lib/eryph/compute/generated/lib/compute_client/models/catlet_list.rb +239 -0
  104. data/lib/eryph/compute/generated/lib/compute_client/models/catlet_network.rb +318 -0
  105. data/lib/eryph/compute/generated/lib/compute_client/models/catlet_network_adapter.rb +247 -0
  106. data/lib/eryph/compute/generated/lib/compute_client/models/catlet_status.rb +43 -0
  107. data/lib/eryph/compute/generated/lib/compute_client/models/catlet_stop_mode.rb +41 -0
  108. data/lib/eryph/compute/generated/lib/compute_client/models/disk_status.rb +40 -0
  109. data/lib/eryph/compute/generated/lib/compute_client/models/expand_catlet_config_request_body.rb +243 -0
  110. data/lib/eryph/compute/generated/lib/compute_client/models/expand_new_catlet_config_request.rb +243 -0
  111. data/lib/eryph/compute/generated/lib/compute_client/models/floating_network_port.rb +313 -0
  112. data/lib/eryph/compute/generated/lib/compute_client/models/gene.rb +415 -0
  113. data/lib/eryph/compute/generated/lib/compute_client/models/gene_list.rb +239 -0
  114. data/lib/eryph/compute/generated/lib/compute_client/models/gene_type.rb +41 -0
  115. data/lib/eryph/compute/generated/lib/compute_client/models/gene_with_usage.rb +439 -0
  116. data/lib/eryph/compute/generated/lib/compute_client/models/new_catlet_request.rb +233 -0
  117. data/lib/eryph/compute/generated/lib/compute_client/models/new_project_member_body.rb +273 -0
  118. data/lib/eryph/compute/generated/lib/compute_client/models/new_project_request.rb +247 -0
  119. data/lib/eryph/compute/generated/lib/compute_client/models/new_virtual_disk_request.rb +345 -0
  120. data/lib/eryph/compute/generated/lib/compute_client/models/operation.rb +352 -0
  121. data/lib/eryph/compute/generated/lib/compute_client/models/operation_list.rb +239 -0
  122. data/lib/eryph/compute/generated/lib/compute_client/models/operation_log_entry.rb +299 -0
  123. data/lib/eryph/compute/generated/lib/compute_client/models/operation_resource.rb +311 -0
  124. data/lib/eryph/compute/generated/lib/compute_client/models/operation_result.rb +242 -0
  125. data/lib/eryph/compute/generated/lib/compute_client/models/operation_status.rb +42 -0
  126. data/lib/eryph/compute/generated/lib/compute_client/models/operation_task.rb +366 -0
  127. data/lib/eryph/compute/generated/lib/compute_client/models/operation_task_reference.rb +311 -0
  128. data/lib/eryph/compute/generated/lib/compute_client/models/operation_task_status.rb +42 -0
  129. data/lib/eryph/compute/generated/lib/compute_client/models/populate_catlet_config_variables_request.rb +233 -0
  130. data/lib/eryph/compute/generated/lib/compute_client/models/problem_details.rb +261 -0
  131. data/lib/eryph/compute/generated/lib/compute_client/models/project.rb +289 -0
  132. data/lib/eryph/compute/generated/lib/compute_client/models/project_list.rb +239 -0
  133. data/lib/eryph/compute/generated/lib/compute_client/models/project_member_role.rb +341 -0
  134. data/lib/eryph/compute/generated/lib/compute_client/models/project_member_role_list.rb +239 -0
  135. data/lib/eryph/compute/generated/lib/compute_client/models/resource_type.rb +42 -0
  136. data/lib/eryph/compute/generated/lib/compute_client/models/stop_catlet_request_body.rb +259 -0
  137. data/lib/eryph/compute/generated/lib/compute_client/models/task_reference_type.rb +40 -0
  138. data/lib/eryph/compute/generated/lib/compute_client/models/update_catlet_request_body.rb +233 -0
  139. data/lib/eryph/compute/generated/lib/compute_client/models/update_project_networks_request_body.rb +233 -0
  140. data/lib/eryph/compute/generated/lib/compute_client/models/validate_config_request.rb +223 -0
  141. data/lib/eryph/compute/generated/lib/compute_client/models/validation_issue.rb +249 -0
  142. data/lib/eryph/compute/generated/lib/compute_client/models/virtual_disk.rb +479 -0
  143. data/lib/eryph/compute/generated/lib/compute_client/models/virtual_disk_attached_catlet.rb +285 -0
  144. data/lib/eryph/compute/generated/lib/compute_client/models/virtual_disk_gene_info.rb +289 -0
  145. data/lib/eryph/compute/generated/lib/compute_client/models/virtual_disk_list.rb +239 -0
  146. data/lib/eryph/compute/generated/lib/compute_client/models/virtual_network.rb +351 -0
  147. data/lib/eryph/compute/generated/lib/compute_client/models/virtual_network_configuration.rb +223 -0
  148. data/lib/eryph/compute/generated/lib/compute_client/models/virtual_network_list.rb +239 -0
  149. data/lib/eryph/compute/generated/lib/compute_client/version.rb +15 -0
  150. data/lib/eryph/compute/generated/lib/compute_client.rb +101 -0
  151. data/lib/eryph/compute/generated/spec/api/catlets_api_spec.rb +182 -0
  152. data/lib/eryph/compute/generated/spec/api/genes_api_spec.rb +81 -0
  153. data/lib/eryph/compute/generated/spec/api/operations_api_spec.rb +62 -0
  154. data/lib/eryph/compute/generated/spec/api/project_members_api_spec.rb +86 -0
  155. data/lib/eryph/compute/generated/spec/api/projects_api_spec.rb +82 -0
  156. data/lib/eryph/compute/generated/spec/api/version_api_spec.rb +46 -0
  157. data/lib/eryph/compute/generated/spec/api/virtual_disks_api_spec.rb +83 -0
  158. data/lib/eryph/compute/generated/spec/api/virtual_networks_api_spec.rb +84 -0
  159. data/lib/eryph/compute/generated/spec/models/api_version_response_spec.rb +36 -0
  160. data/lib/eryph/compute/generated/spec/models/api_version_spec.rb +42 -0
  161. data/lib/eryph/compute/generated/spec/models/catlet_config_operation_result_spec.rb +36 -0
  162. data/lib/eryph/compute/generated/spec/models/catlet_config_validation_result_spec.rb +42 -0
  163. data/lib/eryph/compute/generated/spec/models/catlet_configuration_spec.rb +36 -0
  164. data/lib/eryph/compute/generated/spec/models/catlet_drive_spec.rb +42 -0
  165. data/lib/eryph/compute/generated/spec/models/catlet_drive_type_spec.rb +30 -0
  166. data/lib/eryph/compute/generated/spec/models/catlet_list_spec.rb +36 -0
  167. data/lib/eryph/compute/generated/spec/models/catlet_network_adapter_spec.rb +42 -0
  168. data/lib/eryph/compute/generated/spec/models/catlet_network_spec.rb +72 -0
  169. data/lib/eryph/compute/generated/spec/models/catlet_spec.rb +78 -0
  170. data/lib/eryph/compute/generated/spec/models/catlet_status_spec.rb +30 -0
  171. data/lib/eryph/compute/generated/spec/models/catlet_stop_mode_spec.rb +30 -0
  172. data/lib/eryph/compute/generated/spec/models/disk_status_spec.rb +30 -0
  173. data/lib/eryph/compute/generated/spec/models/expand_catlet_config_request_body_spec.rb +48 -0
  174. data/lib/eryph/compute/generated/spec/models/expand_new_catlet_config_request_spec.rb +48 -0
  175. data/lib/eryph/compute/generated/spec/models/floating_network_port_spec.rb +60 -0
  176. data/lib/eryph/compute/generated/spec/models/gene_list_spec.rb +36 -0
  177. data/lib/eryph/compute/generated/spec/models/gene_spec.rb +72 -0
  178. data/lib/eryph/compute/generated/spec/models/gene_type_spec.rb +30 -0
  179. data/lib/eryph/compute/generated/spec/models/gene_with_usage_spec.rb +84 -0
  180. data/lib/eryph/compute/generated/spec/models/new_catlet_request_spec.rb +42 -0
  181. data/lib/eryph/compute/generated/spec/models/new_project_member_body_spec.rb +48 -0
  182. data/lib/eryph/compute/generated/spec/models/new_project_request_spec.rb +42 -0
  183. data/lib/eryph/compute/generated/spec/models/new_virtual_disk_request_spec.rb +72 -0
  184. data/lib/eryph/compute/generated/spec/models/operation_list_spec.rb +36 -0
  185. data/lib/eryph/compute/generated/spec/models/operation_log_entry_spec.rb +54 -0
  186. data/lib/eryph/compute/generated/spec/models/operation_resource_spec.rb +48 -0
  187. data/lib/eryph/compute/generated/spec/models/operation_result_spec.rb +36 -0
  188. data/lib/eryph/compute/generated/spec/models/operation_spec.rb +78 -0
  189. data/lib/eryph/compute/generated/spec/models/operation_status_spec.rb +30 -0
  190. data/lib/eryph/compute/generated/spec/models/operation_task_reference_spec.rb +48 -0
  191. data/lib/eryph/compute/generated/spec/models/operation_task_spec.rb +72 -0
  192. data/lib/eryph/compute/generated/spec/models/operation_task_status_spec.rb +30 -0
  193. data/lib/eryph/compute/generated/spec/models/populate_catlet_config_variables_request_spec.rb +42 -0
  194. data/lib/eryph/compute/generated/spec/models/problem_details_spec.rb +60 -0
  195. data/lib/eryph/compute/generated/spec/models/project_list_spec.rb +36 -0
  196. data/lib/eryph/compute/generated/spec/models/project_member_role_list_spec.rb +36 -0
  197. data/lib/eryph/compute/generated/spec/models/project_member_role_spec.rb +60 -0
  198. data/lib/eryph/compute/generated/spec/models/project_spec.rb +48 -0
  199. data/lib/eryph/compute/generated/spec/models/resource_type_spec.rb +30 -0
  200. data/lib/eryph/compute/generated/spec/models/stop_catlet_request_body_spec.rb +36 -0
  201. data/lib/eryph/compute/generated/spec/models/task_reference_type_spec.rb +30 -0
  202. data/lib/eryph/compute/generated/spec/models/update_catlet_request_body_spec.rb +42 -0
  203. data/lib/eryph/compute/generated/spec/models/update_project_networks_request_body_spec.rb +42 -0
  204. data/lib/eryph/compute/generated/spec/models/validate_config_request_spec.rb +36 -0
  205. data/lib/eryph/compute/generated/spec/models/validation_issue_spec.rb +42 -0
  206. data/lib/eryph/compute/generated/spec/models/virtual_disk_attached_catlet_spec.rb +42 -0
  207. data/lib/eryph/compute/generated/spec/models/virtual_disk_gene_info_spec.rb +48 -0
  208. data/lib/eryph/compute/generated/spec/models/virtual_disk_list_spec.rb +36 -0
  209. data/lib/eryph/compute/generated/spec/models/virtual_disk_spec.rb +108 -0
  210. data/lib/eryph/compute/generated/spec/models/virtual_network_configuration_spec.rb +36 -0
  211. data/lib/eryph/compute/generated/spec/models/virtual_network_list_spec.rb +36 -0
  212. data/lib/eryph/compute/generated/spec/models/virtual_network_spec.rb +66 -0
  213. data/lib/eryph/compute/generated/spec/spec_helper.rb +111 -0
  214. data/lib/eryph/compute/generated.rb +137 -0
  215. data/lib/eryph/compute/operation_result.rb +255 -0
  216. data/lib/eryph/compute/operation_tracker.rb +247 -0
  217. data/lib/eryph/compute/problem_details_error.rb +139 -0
  218. data/lib/eryph/compute/version.rb +6 -0
  219. data/lib/eryph/compute.rb +40 -0
  220. data/lib/eryph/version.rb +4 -0
  221. data/lib/eryph.rb +68 -0
  222. metadata +329 -0
@@ -0,0 +1,440 @@
1
+ require 'rbconfig'
2
+ require 'json'
3
+ require 'uri'
4
+
5
+ module Eryph
6
+ module ClientRuntime
7
+ # Cross-platform environment abstraction
8
+ # Provides platform-specific file system access and configuration paths
9
+ class Environment
10
+ # Configuration store locations
11
+ module ConfigStoreLocation
12
+ CURRENT_DIRECTORY = :current_directory
13
+ USER = :user
14
+ SYSTEM = :system
15
+ end
16
+
17
+ # Get the configuration path for the specified location
18
+ # @param location [Symbol] the configuration store location
19
+ # @return [String] the configuration path
20
+ def get_config_path(location)
21
+ case location
22
+ when ConfigStoreLocation::CURRENT_DIRECTORY
23
+ Dir.pwd
24
+ when ConfigStoreLocation::USER
25
+ user_config_path
26
+ when ConfigStoreLocation::SYSTEM
27
+ system_config_path
28
+ else
29
+ raise ArgumentError, "Unknown configuration store location: #{location}"
30
+ end
31
+ end
32
+
33
+ # Check if running on Windows platform
34
+ # @return [Boolean] true if running on Windows
35
+ def windows?
36
+ !!(RbConfig::CONFIG['target_os'] =~ /mswin|mingw|cygwin/)
37
+ end
38
+
39
+ # Check if running on macOS platform
40
+ # @return [Boolean] true if running on macOS
41
+ def macos?
42
+ RbConfig::CONFIG['target_os'] =~ /darwin/
43
+ end
44
+
45
+ # Check if running on Linux platform
46
+ # @return [Boolean] true if running on Linux
47
+ def linux?
48
+ RbConfig::CONFIG['target_os'] =~ /linux/
49
+ end
50
+
51
+ # Get the current directory
52
+ # @return [String] current working directory
53
+ def current_directory
54
+ Dir.pwd
55
+ end
56
+
57
+ # Check if a file exists
58
+ # @param path [String] file path
59
+ # @return [Boolean] true if file exists
60
+ def file_exists?(path)
61
+ File.exist?(path) && File.file?(path)
62
+ end
63
+
64
+ # Check if a directory exists
65
+ # @param path [String] directory path
66
+ # @return [Boolean] true if directory exists
67
+ def directory_exists?(path)
68
+ File.exist?(path) && File.directory?(path)
69
+ end
70
+
71
+ # Read file content
72
+ # @param path [String] file path
73
+ # @return [String] file content
74
+ # @raise [IOError] if file cannot be read
75
+ def read_file(path)
76
+ File.read(path)
77
+ rescue Errno::ENOENT, Errno::EACCES => e
78
+ raise IOError, "Cannot read file #{path}: #{e.message}"
79
+ end
80
+
81
+ # Write file content
82
+ # @param path [String] file path
83
+ # @param content [String] content to write
84
+ # @raise [IOError] if file cannot be written
85
+ def write_file(path, content)
86
+ # Ensure directory exists
87
+ dir = File.dirname(path)
88
+ FileUtils.mkdir_p(dir) unless directory_exists?(dir)
89
+
90
+ File.write(path, content)
91
+ rescue Errno::EACCES, Errno::ENOENT => e
92
+ raise IOError, "Cannot write file #{path}: #{e.message}"
93
+ end
94
+
95
+ # Create directory if it doesn't exist
96
+ # @param path [String] directory path
97
+ # @raise [IOError] if directory cannot be created
98
+ def ensure_directory(path)
99
+ return if directory_exists?(path)
100
+
101
+ FileUtils.mkdir_p(path)
102
+ rescue Errno::EACCES => e
103
+ raise IOError, "Cannot create directory #{path}: #{e.message}"
104
+ end
105
+
106
+ # Check if current user has admin/root privileges
107
+ # @return [Boolean] true if user has elevated privileges
108
+ def admin_user?
109
+ if windows?
110
+ # Check if running as administrator on Windows using PowerShell
111
+ check_windows_admin_privileges
112
+ else
113
+ # Check if running as root on Unix-like systems
114
+ Process.uid.zero?
115
+ end
116
+ end
117
+
118
+ # Get environment variable with optional default
119
+ # @param name [String] environment variable name
120
+ # @param default [String, nil] default value if not found
121
+ # @return [String, nil] environment variable value
122
+ def get_environment_variable(name, default = nil)
123
+ ENV[name] || default
124
+ end
125
+
126
+ # Create a temporary file
127
+ # @param prefix [String] filename prefix
128
+ # @param suffix [String] filename suffix
129
+ # @return [Tempfile] temporary file object
130
+ def create_temp_file(prefix, suffix = '')
131
+ require 'tempfile'
132
+ Tempfile.new([prefix, suffix])
133
+ end
134
+
135
+ # Execute PowerShell script from file
136
+ # @param script_path [String] path to PowerShell script file
137
+ # @return [Boolean] true if script executed successfully
138
+ def execute_powershell_script_file(script_path)
139
+ return false unless windows?
140
+
141
+ # Try regular PowerShell first
142
+ success = execute_command('powershell.exe', '-ExecutionPolicy', 'Bypass', '-NoProfile', '-File', script_path)
143
+
144
+ # If that fails, try the full path
145
+ success ||= execute_command(
146
+ 'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe',
147
+ '-ExecutionPolicy', 'Bypass', '-NoProfile', '-File', script_path
148
+ )
149
+
150
+ success
151
+ end
152
+
153
+ # Get application data path (system-wide)
154
+ # @param app_name [String] application name (default: 'eryph')
155
+ # @return [String] application data path
156
+ def get_application_data_path(app_name = 'eryph')
157
+ if windows?
158
+ # Windows: Use PROGRAMDATA
159
+ program_data = get_environment_variable('PROGRAMDATA', 'C:/ProgramData')
160
+ File.join(program_data, app_name)
161
+ else
162
+ # Unix-like: Use /var/lib for system data
163
+ File.join('/var/lib', app_name)
164
+ end
165
+ end
166
+
167
+ # Check if a process is running
168
+ # @param process_id [Integer] process ID
169
+ # @param process_name [String, nil] optional process name for verification
170
+ # @return [Boolean] true if process is running
171
+ def process_running?(process_id, process_name = nil)
172
+ return false if process_id.nil? || process_id <= 0
173
+
174
+ if windows?
175
+ # On Windows, use PowerShell to check if process exists
176
+ execute_command('powershell', '-Command',
177
+ "Get-Process -Id #{process_id} -ErrorAction SilentlyContinue | Out-Null")
178
+ elsif file_exists?("/proc/#{process_id}")
179
+ # On Unix-like systems, check /proc or use ps
180
+ return true unless process_name
181
+
182
+ # Verify process name if provided
183
+ begin
184
+ cmdline = read_file("/proc/#{process_id}/cmdline")
185
+ cmdline.include?(process_name)
186
+ rescue IOError
187
+ false
188
+ end
189
+ else
190
+ # Fallback to ps command
191
+ execute_command('ps', '-p', process_id.to_s)
192
+ end
193
+ rescue StandardError
194
+ false
195
+ end
196
+
197
+ # Get encrypted system client private key for identity provider
198
+ # @param identity_provider_name [String] identity provider name ('zero' or 'local')
199
+ # @param identity_endpoint [String] identity endpoint for DPAPI entropy
200
+ # @return [String, nil] private key PEM content or nil if not found
201
+ def get_encrypted_system_client(identity_provider_name, identity_endpoint = nil)
202
+ private_key_path = File.join(
203
+ get_application_data_path,
204
+ identity_provider_name,
205
+ 'private',
206
+ 'clients',
207
+ 'system-client.key'
208
+ )
209
+
210
+ return nil unless file_exists?(private_key_path)
211
+
212
+ encrypted_data = read_binary_file(private_key_path)
213
+
214
+ if windows?
215
+ # On Windows, decrypt DPAPI-protected data
216
+ decrypted_data = decrypt_dpapi_data(encrypted_data, identity_endpoint)
217
+ raise IOError, 'Failed to decrypt system client private key using DPAPI' if decrypted_data.nil?
218
+
219
+ decrypted_data
220
+ else
221
+ # On non-Windows systems, assume it's already in PEM format
222
+ encrypted_data
223
+ end
224
+ end
225
+
226
+ # Decrypt DPAPI-protected data (Windows only)
227
+ # @param encrypted_data [String] binary encrypted data
228
+ # @param entropy [String, nil] entropy string (typically the identity endpoint URI)
229
+ # @return [String, nil] decrypted PEM private key or nil if decryption fails
230
+ def decrypt_dpapi_data(encrypted_data, entropy = nil)
231
+ return nil unless windows?
232
+
233
+ # Create cache key from encrypted data and entropy
234
+ require 'digest'
235
+ cache_key = Digest::SHA256.hexdigest("#{encrypted_data}#{entropy}")
236
+
237
+ # Check cache first
238
+ return @dpapi_cache[cache_key] if @dpapi_cache && @dpapi_cache[cache_key]
239
+
240
+ temp_file = nil
241
+ script_file = nil
242
+ output_file = nil
243
+
244
+ begin
245
+ # Create temporary files for DPAPI decryption process
246
+ temp_file = create_temp_file('encrypted_key', '.bin')
247
+ temp_file.binmode
248
+ temp_file.write(encrypted_data)
249
+ temp_file.close
250
+
251
+ script_file = create_temp_file('decrypt_dpapi', '.ps1')
252
+ output_file = create_temp_file('decrypted_output', '.txt')
253
+
254
+ # Close output file immediately after creation so PowerShell can write to it
255
+ output_file.close
256
+
257
+ # Create PowerShell script for DPAPI decryption
258
+ entropy_value = entropy&.gsub("'", "''")
259
+ script_content = create_dpapi_decrypt_script(
260
+ temp_file.path.gsub('/', '\\'),
261
+ output_file.path.gsub('/', '\\'),
262
+ entropy_value
263
+ )
264
+
265
+ # Write script content and close file properly
266
+ script_file.write(script_content)
267
+ script_file.close
268
+
269
+ # Execute PowerShell script
270
+ script_path = script_file.path.gsub('/', '\\')
271
+
272
+ success = execute_powershell_script_file(script_path)
273
+
274
+ result = nil
275
+ if success && file_exists?(output_file.path) && File.size(output_file.path).positive?
276
+ output_content = read_file(output_file.path).strip
277
+ # Return the result if it doesn't contain an error
278
+ result = output_content.start_with?('ERROR:') ? nil : output_content
279
+ end
280
+
281
+ # Cache the result (including nil results to avoid repeated failures)
282
+ @dpapi_cache ||= {}
283
+ @dpapi_cache[cache_key] = result
284
+
285
+ result
286
+ ensure
287
+ temp_file&.close
288
+ script_file&.close
289
+ output_file&.close
290
+ File.unlink(temp_file.path) if temp_file && File.exist?(temp_file.path)
291
+ File.unlink(script_file.path) if script_file && File.exist?(script_file.path)
292
+ File.unlink(output_file.path) if output_file && File.exist?(output_file.path)
293
+ end
294
+ end
295
+
296
+ private
297
+
298
+ # Check if running as administrator on Windows using PowerShell
299
+ # @return [Boolean] true if current process has admin privileges
300
+ def check_windows_admin_privileges
301
+ # Use PowerShell to check if current user is in Administrators group and process is elevated
302
+ cmd = '([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent())' \
303
+ '.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)'
304
+ result = `powershell.exe -Command "#{cmd}" 2>nul`.strip
305
+ result.downcase == 'true'
306
+ rescue StandardError
307
+ # If PowerShell fails for any reason, assume not admin
308
+ false
309
+ end
310
+
311
+ # Read binary file content
312
+ # @param path [String] file path
313
+ # @return [String] binary file content
314
+ # @raise [IOError] if file cannot be read
315
+ def read_binary_file(path)
316
+ File.binread(path)
317
+ rescue Errno::ENOENT, Errno::EACCES => e
318
+ raise IOError, "Cannot read binary file #{path}: #{e.message}"
319
+ end
320
+
321
+ # Execute a system command
322
+ # @param command [String] command to execute
323
+ # @param args [Array<String>] command arguments
324
+ # @return [Boolean] true if command succeeded
325
+ def execute_command(command, *args)
326
+ if args.empty?
327
+ system(command)
328
+ else
329
+ system(command, *args)
330
+ end
331
+ end
332
+
333
+ # Execute PowerShell script and return output
334
+ # @param script_content [String] PowerShell script content
335
+ # @return [String, nil] script output or nil if failed
336
+ def execute_powershell_script(script_content)
337
+ return nil unless windows?
338
+
339
+ require 'tempfile'
340
+ script_file = nil
341
+ output_file = nil
342
+
343
+ begin
344
+ script_file = create_temp_file('powershell_script', '.ps1')
345
+ output_file = create_temp_file('powershell_output', '.txt')
346
+
347
+ write_file(script_file.path, script_content)
348
+
349
+ # Execute PowerShell script
350
+ script_path = script_file.path.gsub('/', '\\')
351
+ output_path = output_file.path.gsub('/', '\\')
352
+
353
+ # Modify script to redirect output to file
354
+ unless script_content.include?('Out-File')
355
+ modified_script = script_content + "\n} | Out-File -FilePath '#{output_path}' -Encoding UTF8"
356
+ end
357
+ write_file(script_file.path, modified_script || script_content)
358
+
359
+ success = execute_command('powershell.exe', '-ExecutionPolicy', 'Bypass', '-NoProfile', '-File', script_path)
360
+
361
+ if success && file_exists?(output_file.path)
362
+ result = read_file(output_file.path).strip
363
+ result.empty? ? nil : result
364
+ end
365
+ rescue StandardError
366
+ nil
367
+ ensure
368
+ script_file&.close
369
+ output_file&.close
370
+ File.unlink(script_file.path) if script_file && File.exist?(script_file.path)
371
+ File.unlink(output_file.path) if output_file && File.exist?(output_file.path)
372
+ end
373
+ end
374
+
375
+ def user_config_path
376
+ if windows?
377
+ # Windows: Use APPDATA
378
+ get_environment_variable('APPDATA') || File.expand_path('~/AppData/Roaming')
379
+ else
380
+ # Unix-like: Use XDG_CONFIG_HOME or fallback to ~/.config
381
+ get_environment_variable('XDG_CONFIG_HOME') || File.expand_path('~/.config')
382
+ end
383
+ end
384
+
385
+ def system_config_path
386
+ if windows?
387
+ # Windows: Use PROGRAMDATA
388
+ get_environment_variable('PROGRAMDATA', 'C:/ProgramData')
389
+ else
390
+ # Unix-like: Use /etc
391
+ '/etc'
392
+ end
393
+ end
394
+
395
+ def create_dpapi_decrypt_script(encrypted_file_path, output_file_path, entropy_value)
396
+ <<~PS1
397
+ try {
398
+ # Load required .NET assemblies
399
+ Add-Type -AssemblyName System.Security -ErrorAction Stop
400
+ #{' '}
401
+ $encryptedBytes = [System.IO.File]::ReadAllBytes('#{encrypted_file_path}')
402
+ #{' '}
403
+ # Prepare entropy values to try
404
+ $entropyValues = @()
405
+ #{' '}
406
+ # First try the provided entropy (identity endpoint URI)
407
+ #{"$entropyValues += ,[System.Text.Encoding]::UTF8.GetBytes('#{entropy_value}')" if entropy_value}
408
+ #{' '}
409
+ # Also try some fallback values
410
+ $entropyValues += ,$null
411
+ $entropyValues += ,[System.Text.Encoding]::UTF8.GetBytes("eryph")
412
+ $entropyValues += ,[System.Text.Encoding]::UTF8.GetBytes("zero")
413
+ #{' '}
414
+ $scopes = @([System.Security.Cryptography.DataProtectionScope]::CurrentUser, [System.Security.Cryptography.DataProtectionScope]::LocalMachine)
415
+ #{' '}
416
+ foreach ($entropy in $entropyValues) {
417
+ foreach ($scope in $scopes) {
418
+ try {
419
+ $decryptedBytes = [System.Security.Cryptography.ProtectedData]::Unprotect($encryptedBytes, $entropy, $scope)
420
+ $decryptedString = [System.Text.Encoding]::UTF8.GetString($decryptedBytes)
421
+ [System.IO.File]::WriteAllText('#{output_file_path}', $decryptedString)
422
+ exit 0
423
+ } catch {
424
+ # Continue to next combination
425
+ }
426
+ }
427
+ }
428
+ #{' '}
429
+ # If we get here, all combinations failed
430
+ [System.IO.File]::WriteAllText('#{output_file_path}', "ERROR: All DPAPI decryption attempts failed")
431
+ exit 1
432
+ } catch {
433
+ [System.IO.File]::WriteAllText('#{output_file_path}', "ERROR: $($_.Exception.Message)")
434
+ exit 1
435
+ }
436
+ PS1
437
+ end
438
+ end
439
+ end
440
+ end
@@ -0,0 +1,153 @@
1
+ require 'json'
2
+ require 'uri'
3
+ require 'logger'
4
+
5
+ module Eryph
6
+ module ClientRuntime
7
+ # Provides information about locally running eryph identity providers
8
+ # Contains decision logic for interpreting identity provider data
9
+ class LocalIdentityProviderInfo
10
+ # @return [Environment] environment abstraction
11
+ attr_reader :environment
12
+
13
+ # @return [String] identity provider name
14
+ attr_reader :identity_provider_name
15
+
16
+ # @return [Logger] logger instance
17
+ attr_reader :logger
18
+
19
+ # Initialize local identity provider info
20
+ # @param environment [Environment] environment abstraction
21
+ # @param identity_provider_name [String] identity provider name
22
+ # @param logger [Logger, nil] logger instance
23
+ def initialize(environment, identity_provider_name = 'identity', logger: nil)
24
+ @environment = environment
25
+ @identity_provider_name = identity_provider_name
26
+ @logger = logger || Logger.new($stdout).tap { |l| l.level = Logger::WARN }
27
+ end
28
+
29
+ # Check if the identity provider is running
30
+ # @return [Boolean] true if the identity provider is running
31
+ def running?
32
+ @logger.debug("provider.running?: name=#{@identity_provider_name}")
33
+
34
+ meta = metadata
35
+ @logger.debug("provider.running?: metadata=#{meta.empty? ? 'empty' : 'found'}")
36
+
37
+ return false if meta.empty?
38
+
39
+ process_name = meta['processName']
40
+ process_id = meta['processId']
41
+ @logger.debug("provider.running?: process=#{process_name}, pid=#{process_id}")
42
+
43
+ if process_name.nil? || process_name.empty? || process_id.nil?
44
+ @logger.debug('provider.running?: invalid process info')
45
+ return false
46
+ end
47
+
48
+ # Check if the process is actually running
49
+ is_running = @environment.process_running?(process_id.to_i, process_name)
50
+ @logger.debug("provider.running?: process_check=#{is_running}")
51
+ is_running
52
+ end
53
+
54
+ # Get endpoints from the running identity provider
55
+ # @return [Hash] endpoint name -> URI mapping
56
+ def endpoints
57
+ return {} unless running?
58
+
59
+ meta = metadata
60
+ endpoints_data = meta['endpoints']
61
+ return {} unless endpoints_data
62
+
63
+ result = {}
64
+ endpoints_data.each do |key, value|
65
+ uri = URI.parse(value.to_s)
66
+ # Only include valid HTTP/HTTPS URIs
67
+ result[key] = uri if uri.is_a?(URI::HTTP) || uri.is_a?(URI::HTTPS)
68
+ rescue URI::InvalidURIError
69
+ # Skip invalid URIs
70
+ next
71
+ end
72
+
73
+ result
74
+ end
75
+
76
+ # Get the system client private key for eryph-zero
77
+ # @return [String, nil] private key content or nil if not found
78
+ def system_client_private_key
79
+ @logger.debug('system_client_private_key: getting endpoints')
80
+ endpoints_hash = endpoints
81
+ @logger.debug("system_client_private_key: endpoints=#{endpoints_hash.keys.join(',')}")
82
+
83
+ unless endpoints_hash.key?('identity')
84
+ @logger.debug('system_client_private_key: no identity endpoint')
85
+ return nil
86
+ end
87
+
88
+ identity_endpoint = endpoints_hash['identity']&.to_s
89
+ @logger.debug("system_client_private_key: identity_endpoint=#{identity_endpoint}")
90
+
91
+ private_key = @environment.get_encrypted_system_client(@identity_provider_name, identity_endpoint)
92
+ @logger.debug("system_client_private_key: result=#{private_key ? 'found' : 'nil'}")
93
+ private_key
94
+ end
95
+
96
+ # Get system client credentials for eryph-zero
97
+ # @return [Hash, nil] client credentials hash or nil if not available
98
+ def system_client_credentials
99
+ @logger.debug('system_client_credentials: checking endpoints')
100
+ endpoints_hash = endpoints
101
+ @logger.debug("system_client_credentials: endpoints=#{endpoints_hash.keys.join(',')}")
102
+
103
+ unless endpoints_hash.key?('identity')
104
+ @logger.debug('system_client_credentials: no identity endpoint')
105
+ return nil
106
+ end
107
+
108
+ private_key = system_client_private_key
109
+ @logger.debug("system_client_credentials: private_key=#{private_key ? 'found' : 'nil'}")
110
+ return nil unless private_key
111
+
112
+ @logger.debug('system_client_credentials: creating credentials hash')
113
+ {
114
+ 'id' => 'system-client',
115
+ 'name' => 'Eryph Zero System Client',
116
+ 'private_key' => private_key,
117
+ 'identity_endpoint' => endpoints_hash['identity'].to_s,
118
+ }
119
+ end
120
+
121
+ private
122
+
123
+ def metadata
124
+ lock_file_path = File.join(
125
+ @environment.get_application_data_path,
126
+ @identity_provider_name,
127
+ '.lock'
128
+ )
129
+ @logger.debug("get_metadata: checking #{lock_file_path}")
130
+
131
+ unless @environment.file_exists?(lock_file_path)
132
+ @logger.debug('get_metadata: file not found')
133
+ return {}
134
+ end
135
+
136
+ begin
137
+ content = @environment.read_file(lock_file_path)
138
+ # Strip BOM if present
139
+ content = content.sub(/\A\xEF\xBB\xBF/, '')
140
+ result = JSON.parse(content)
141
+ @logger.debug('get_metadata: parsed successfully')
142
+ result
143
+ rescue JSON::ParserError
144
+ @logger.debug('get_metadata: JSON parse error')
145
+ {}
146
+ rescue IOError
147
+ @logger.debug('get_metadata: IO error')
148
+ {}
149
+ end
150
+ end
151
+ end
152
+ end
153
+ end