makit 0.0.147 → 0.0.153

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 (58) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generated/makit/v1/configuration/project_pb.rb +22 -0
  3. data/lib/generated/makit/v1/configuration/project_service_pb.rb +34 -0
  4. data/lib/generated/makit/v1/configuration/project_service_services_pb.rb +51 -0
  5. data/lib/generated/makit/v1/git/git_repository_model_pb.rb +22 -0
  6. data/lib/generated/makit/v1/git/git_repository_service_pb.rb +29 -0
  7. data/lib/generated/makit/v1/git/git_repository_service_services_pb.rb +39 -0
  8. data/lib/generated/makit/v1/gitlab/pipeline_pb.rb +26 -0
  9. data/lib/generated/makit/v1/gitlab/pipeline_result_pb.rb +29 -0
  10. data/lib/generated/makit/v1/gitlab/pipeline_service_pb.rb +36 -0
  11. data/lib/generated/makit/v1/gitlab/pipeline_service_services_pb.rb +41 -0
  12. data/lib/generated/makit/v1/grpc/service_specification_pb.rb +27 -0
  13. data/lib/generated/makit/v1/grpc/test_specification_pb.rb +29 -0
  14. data/lib/generated/makit/v1/io/filesystem_pb.rb +27 -0
  15. data/lib/generated/makit/v1/io/filesystem_services_pb.rb +47 -0
  16. data/lib/generated/makit/v1/makit.v1_pb.rb +35 -0
  17. data/lib/generated/makit/v1/makit.v1_services_pb.rb +26 -0
  18. data/lib/generated/makit/v1/podman/podman_service_pb.rb +64 -0
  19. data/lib/generated/makit/v1/podman/podman_service_services_pb.rb +52 -0
  20. data/lib/generated/makit/v1/services/repository_manager_model_pb.rb +23 -0
  21. data/lib/generated/makit/v1/services/repository_manager_service_pb.rb +32 -0
  22. data/lib/generated/makit/v1/services/repository_manager_service_services_pb.rb +35 -0
  23. data/lib/generated/makit/v1/spec/message_proto_generator_pb.rb +33 -0
  24. data/lib/generated/makit/v1/spec/message_proto_generator_services_pb.rb +38 -0
  25. data/lib/generated/makit/v1/spec/message_spec_pb.rb +31 -0
  26. data/lib/generated/makit/v1/spec/message_spec_suite_pb.rb +30 -0
  27. data/lib/generated/makit/v1/spec/message_spec_test_pb.rb +34 -0
  28. data/lib/generated/makit/v1/spec/proto_service_pb.rb +53 -0
  29. data/lib/generated/makit/v1/spec/proto_service_services_pb.rb +42 -0
  30. data/lib/generated/makit/v1/spec/spec_manifest_pb.rb +44 -0
  31. data/lib/generated/makit/v1/web/link_pb.rb +20 -0
  32. data/lib/makit/azure/blob_storage.rb +257 -0
  33. data/lib/makit/azure/cli.rb +285 -0
  34. data/lib/makit/configuration/project.rb +137 -291
  35. data/lib/makit/git/repository.rb +24 -190
  36. data/lib/makit/gitlab/pipeline.rb +16 -16
  37. data/lib/makit/gitlab/pipeline_service_impl.rb +43 -43
  38. data/lib/makit/io/filesystem_service_impl.rb +6 -6
  39. data/lib/makit/lint.rb +212 -0
  40. data/lib/makit/logging/configuration.rb +2 -1
  41. data/lib/makit/logging.rb +15 -2
  42. data/lib/makit/podman/podman.rb +20 -20
  43. data/lib/makit/podman/podman_service_impl.rb +41 -41
  44. data/lib/makit/secrets/azure_key_vault.rb +323 -0
  45. data/lib/makit/secrets/azure_secrets.rb +183 -0
  46. data/lib/makit/secrets/local_secrets.rb +72 -0
  47. data/lib/makit/secrets/secrets_manager.rb +105 -0
  48. data/lib/makit/secrets.rb +10 -45
  49. data/lib/makit/tasks/bump.rb +7 -0
  50. data/lib/makit/tasks/info.rb +204 -0
  51. data/lib/makit/tasks/integrate.rb +28 -1
  52. data/lib/makit/tasks/secrets.rb +7 -0
  53. data/lib/makit/tasks/version.rb +6 -0
  54. data/lib/makit/tasks.rb +4 -0
  55. data/lib/makit/v1/configuration/project_service_impl.rb +1 -1
  56. data/lib/makit/version.rb +382 -1
  57. data/lib/makit.rb +21 -18
  58. metadata +46 -5
@@ -0,0 +1,323 @@
1
+
2
+
3
+ module Makit
4
+ module Secrets
5
+ # Azure Key Vault operations
6
+ class AzureKeyVault
7
+ # Required environment variables
8
+ REQUIRED_VARS = [
9
+ "AZURE_STORAGE_ACCOUNT",
10
+ "AZURE_STORAGE_ACCOUNT_KEY",
11
+ ].freeze
12
+
13
+ # Optional environment variables
14
+ OPTIONAL_VARS = [
15
+ "AZURE_CDN_RESOURCE_GROUP",
16
+ "AZURE_CDN_PROFILE_NAME",
17
+ "AZURE_CDN_ENDPOINT_NAME",
18
+ "GITLAB_NUGET_TOKEN",
19
+ "GITLAB_USERNAME",
20
+ ].freeze
21
+
22
+ # All environment variablesazure_key_vault.r
23
+ ALL_VARS = (REQUIRED_VARS + OPTIONAL_VARS).freeze
24
+
25
+ # Check if kvenv is available
26
+ def self.kvenv_available?
27
+ system("which kvenv > /dev/null 2>&1")
28
+ end
29
+
30
+ # Check if Azure CLI is authenticated
31
+ def self.azure_cli_authenticated?
32
+ return false unless system("which az > /dev/null 2>&1")
33
+ system("az account show > /dev/null 2>&1")
34
+ end
35
+
36
+ # Get Azure Key Vault name from environment or use default
37
+ def self.keyvault_name
38
+ ENV["AZURE_KEYVAULT_NAME"] || "louparslow-secrets"
39
+ end
40
+
41
+ # Get secret name from environment or auto-generate from GIT_REMOTE_URL
42
+ def self.secret_name
43
+ return ENV["AZURE_SECRET_NAME"] if ENV["AZURE_SECRET_NAME"] && !ENV["AZURE_SECRET_NAME"].empty?
44
+
45
+ # Auto-generate from GIT_REMOTE_URL
46
+ git_remote_url = get_git_remote_url
47
+ if git_remote_url && !git_remote_url.empty?
48
+ generate_secret_name_from_url(git_remote_url)
49
+ else
50
+ "portal-secrets" # Fallback default
51
+ end
52
+ end
53
+
54
+ # Get GIT_REMOTE_URL from various sources
55
+ def self.get_git_remote_url
56
+ # Try constant first
57
+ return GIT_REMOTE_URL if defined?(GIT_REMOTE_URL) && !GIT_REMOTE_URL.nil? && !GIT_REMOTE_URL.empty?
58
+
59
+ # Try Git module
60
+ if defined?(Makit::Git) && Makit::Git.git_repo?
61
+ url = Makit::Git.get_remote_url
62
+ return url if url && !url.empty?
63
+ end
64
+
65
+ # Try project configuration
66
+ if defined?(Makit::Configuration::Project)
67
+ project = Makit::Configuration::Project.default
68
+ url = project.git_remote_url
69
+ return url if url && !url.nil? && !url.empty?
70
+ end
71
+
72
+ nil
73
+ end
74
+
75
+ # Generate a valid Azure Key Vault secret name from a Git remote URL
76
+ # Azure Key Vault secret names must be 1-127 characters, alphanumeric and hyphens only,
77
+ # cannot start or end with hyphen, and cannot have consecutive hyphens
78
+ def self.generate_secret_name_from_url(url)
79
+ # Remove protocol (http://, https://, git@)
80
+ name = url.gsub(/^https?:\/\//, "").gsub(/^git@/, "")
81
+
82
+ # Remove .git suffix if present
83
+ name = name.gsub(/\.git$/, "")
84
+
85
+ # Replace invalid characters with hyphens
86
+ name = name.gsub(/[^a-zA-Z0-9\-]/, "-")
87
+
88
+ # Remove consecutive hyphens
89
+ name = name.gsub(/-+/, "-")
90
+
91
+ # Remove leading/trailing hyphens
92
+ name = name.gsub(/^-+|-+$/, "")
93
+
94
+ # Ensure it starts with a letter or number (Azure requirement)
95
+ name = "secret-#{name}" if name.empty? || name.match(/^[^a-zA-Z0-9]/)
96
+
97
+ # Truncate to 127 characters (Azure Key Vault limit)
98
+ name = name[0, 127]
99
+
100
+ # Remove trailing hyphen if truncation created one
101
+ name = name.gsub(/-+$/, "")
102
+
103
+ # Ensure it's not empty
104
+ name = "git-secrets" if name.empty?
105
+
106
+ name
107
+ end
108
+
109
+ # Get secret prefix from environment
110
+ def self.secret_prefix
111
+ ENV["AZURE_SECRET_PREFIX"]
112
+ end
113
+
114
+ # Setup method: Attempt to initialize environment variables
115
+ def self.setup
116
+ puts "Setting up secrets...".colorize(:blue)
117
+
118
+ # Check if required variables are already set
119
+ missing_required = REQUIRED_VARS.select { |var| ENV[var].nil? || ENV[var].empty? }
120
+
121
+ if missing_required.empty?
122
+ puts " All required environment variables are already set".colorize(:green)
123
+ return true
124
+ end
125
+
126
+ puts " Missing required variables: #{missing_required.join(", ")}".colorize(:yellow)
127
+ puts " Attempting to load from Azure Key Vault...".colorize(:yellow)
128
+
129
+ # Try to load from Azure Key Vault
130
+ if load(keyvault_name: nil, secret_name: nil)
131
+ # Verify again after loading
132
+ still_missing = REQUIRED_VARS.select { |var| ENV[var].nil? || ENV[var].empty? }
133
+ if still_missing.empty?
134
+ puts " Successfully loaded all required secrets".colorize(:green)
135
+ return true
136
+ else
137
+ puts " Warning: Still missing required variables: #{still_missing.join(", ")}".colorize(:yellow)
138
+ return false
139
+ end
140
+ else
141
+ puts " Could not load secrets from Azure Key Vault".colorize(:yellow)
142
+ puts " Please set environment variables manually or configure Azure Key Vault access".colorize(:yellow)
143
+ return false
144
+ end
145
+ end
146
+
147
+ # Verify that expected environment variables are set
148
+ def self.verify(warn_only: true)
149
+ issues = []
150
+ warnings = []
151
+
152
+ # Check required variables
153
+ REQUIRED_VARS.each do |var|
154
+ if ENV[var].nil? || ENV[var].empty?
155
+ issues << "Required variable '#{var}' is not set"
156
+ end
157
+ end
158
+
159
+ # Check optional variables (only warn)
160
+ OPTIONAL_VARS.each do |var|
161
+ if ENV[var].nil? || ENV[var].empty?
162
+ warnings << "Optional variable '#{var}' is not set"
163
+ end
164
+ end
165
+
166
+ # Report issues
167
+ if issues.any?
168
+ if warn_only
169
+ issues.each { |issue| puts " ⚠️ #{issue}".colorize(:yellow) }
170
+ else
171
+ issues.each { |issue| puts " ❌ #{issue}".colorize(:red) }
172
+ end
173
+ end
174
+
175
+ if warnings.any?
176
+ warnings.each { |warning| puts " ℹ️ #{warning}".colorize(:cyan) }
177
+ end
178
+
179
+ if issues.empty? && warnings.empty?
180
+ puts " ✅ All environment variables are set".colorize(:green)
181
+ return true
182
+ elsif issues.empty?
183
+ puts " ✅ All required environment variables are set".colorize(:green)
184
+ return true
185
+ else
186
+ return false
187
+ end
188
+ end
189
+
190
+ # Get status of all environment variables
191
+ def self.status
192
+ puts "Environment Variables Status:".colorize(:blue)
193
+ puts ""
194
+
195
+ puts "Required Variables:".colorize(:cyan)
196
+ REQUIRED_VARS.each do |var|
197
+ status = ENV[var].nil? || ENV[var].empty? ? "❌ Not set" : "✅ Set"
198
+ color = ENV[var].nil? || ENV[var].empty? ? :red : :green
199
+ puts " #{var}: #{status}".colorize(color)
200
+ end
201
+
202
+ puts ""
203
+ puts "Optional Variables:".colorize(:cyan)
204
+ OPTIONAL_VARS.each do |var|
205
+ status = ENV[var].nil? || ENV[var].empty? ? "⚪ Not set" : "✅ Set"
206
+ color = ENV[var].nil? || ENV[var].empty? ? :yellow : :green
207
+ puts " #{var}: #{status}".colorize(color)
208
+ end
209
+ end
210
+
211
+ # Load secrets from Azure Key Vault using kvenv
212
+ # @param keyvault_name [String, nil] The Azure Key Vault name (defaults to ENV["AZURE_KEYVAULT_NAME"] or "louparslow-secrets")
213
+ # @param secret_name [String, nil] The secret name in Key Vault (defaults to ENV["AZURE_SECRET_NAME"] or "portal-secrets")
214
+ # @return [Boolean] true if secrets were loaded successfully, false otherwise
215
+ def self.load(keyvault_name: nil, secret_name: nil)
216
+ # Use provided parameters or fall back to defaults
217
+ kv_name = keyvault_name || self.keyvault_name
218
+ sec_name = secret_name || self.secret_name
219
+
220
+ unless kvenv_available?
221
+ puts " Warning: kvenv not installed, secrets will not be loaded".colorize(:yellow)
222
+ return false
223
+ end
224
+
225
+ # Check if Azure CLI is authenticated (kvenv can use this)
226
+ unless azure_cli_authenticated?
227
+ puts " Warning: Azure CLI not authenticated, run 'az login' first".colorize(:yellow)
228
+ return false
229
+ end
230
+
231
+ cache_file = "/tmp/kvenv-#{sec_name}.json"
232
+
233
+ # Build kvenv command
234
+ cmd_parts = [
235
+ "kvenv cache",
236
+ "--azure",
237
+ "--azure-keyvault-name #{kv_name}",
238
+ ]
239
+
240
+ if secret_prefix
241
+ cmd_parts << "--secret-prefix #{secret_prefix}"
242
+ else
243
+ cmd_parts << "--secret-name #{sec_name}"
244
+ end
245
+
246
+ cmd_parts << "--output #{cache_file}"
247
+
248
+ # Run kvenv to cache secrets
249
+ puts " Loading secrets from Key Vault: #{kv_name}, Secret: #{sec_name}".colorize(:cyan)
250
+ success = system(cmd_parts.join(" "))
251
+ return false unless success
252
+ return false unless File.exist?(cache_file)
253
+
254
+ # Load secrets from cache file
255
+ begin
256
+ require "json"
257
+ secrets = JSON.parse(File.read(cache_file))
258
+
259
+ # Set environment variables (don't override existing ones)
260
+ loaded_count = 0
261
+ secrets.each do |key, value|
262
+ unless ENV[key]
263
+ ENV[key] = value
264
+ loaded_count += 1
265
+ end
266
+ end
267
+
268
+ puts " Loaded #{loaded_count} secrets from Azure Key Vault (#{secrets.keys.count} total in secret)".colorize(:green)
269
+ true
270
+ rescue => e
271
+ puts " Warning: Could not parse secrets from cache: #{e.message}".colorize(:yellow)
272
+ false
273
+ end
274
+ end
275
+
276
+ # Set a secret in Azure Key Vault
277
+ # @param secret_name [String] The name of the secret to set
278
+ # @param secret_value [String] The value of the secret
279
+ # @param keyvault_name [String, nil] The Azure Key Vault name (defaults to ENV["AZURE_KEYVAULT_NAME"] or "louparslow-secrets")
280
+ # @return [Boolean] true if secret was set successfully, false otherwise
281
+ def self.set(secret_name, secret_value, keyvault_name: nil)
282
+ # Use provided parameter or fall back to default
283
+ kv_name = keyvault_name || self.keyvault_name
284
+
285
+ if secret_name.nil? || secret_name.empty?
286
+ raise "Secret name is required"
287
+ end
288
+
289
+ if secret_value.nil? || secret_value.empty?
290
+ raise "Secret value is required"
291
+ end
292
+
293
+ unless azure_cli_authenticated?
294
+ raise "Azure CLI is not authenticated. Run 'az login' first"
295
+ end
296
+
297
+ puts " Setting secret '#{secret_name}' in Key Vault: #{kv_name}".colorize(:cyan)
298
+
299
+ # Build Azure CLI command
300
+ cmd = [
301
+ "az keyvault secret set",
302
+ "--vault-name '#{kv_name}'",
303
+ "--name '#{secret_name}'",
304
+ "--value '#{secret_value}'",
305
+ "2>&1",
306
+ ].join(" ")
307
+
308
+ output = `#{cmd}`
309
+ exit_code = $?.exitstatus
310
+
311
+ if exit_code != 0
312
+ puts " Error: Failed to set secret".colorize(:red)
313
+ puts " #{output}".colorize(:red)
314
+ return false
315
+ end
316
+
317
+ puts " Successfully set secret '#{secret_name}' in Key Vault".colorize(:green)
318
+ true
319
+ end
320
+ end
321
+ end
322
+ end
323
+
@@ -0,0 +1,183 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "azure_key_vault"
4
+
5
+ module Makit
6
+ module Secrets
7
+ # Azure Key Vault adapter that implements the LocalSecrets interface
8
+ # Uses Azure CLI to store and retrieve individual secrets
9
+ class AzureSecrets
10
+ def initialize(keyvault_name: nil, secret_prefix: nil)
11
+ @keyvault_name = keyvault_name || AzureKeyVault.keyvault_name
12
+ @secret_prefix = secret_prefix || AzureKeyVault.secret_prefix
13
+ raise "Azure Key Vault name is required" if @keyvault_name.nil? || @keyvault_name.empty?
14
+ end
15
+
16
+ def add(key, value)
17
+ set(key, value)
18
+ end
19
+
20
+ def remove(key)
21
+ secret_name = build_secret_name(key)
22
+ return true unless secret_exists?(secret_name)
23
+
24
+ unless AzureKeyVault.azure_cli_authenticated?
25
+ raise "Azure CLI is not authenticated. Run 'az login' first"
26
+ end
27
+
28
+ cmd = [
29
+ "az keyvault secret delete",
30
+ "--vault-name '#{@keyvault_name}'",
31
+ "--name '#{secret_name}'",
32
+ "2>&1"
33
+ ].join(" ")
34
+
35
+ output = `#{cmd}`
36
+ exit_code = $?.exitstatus
37
+
38
+ if exit_code != 0
39
+ raise "Failed to delete secret '#{secret_name}': #{output}"
40
+ end
41
+
42
+ true
43
+ end
44
+
45
+ def has_key?(key)
46
+ secret_name = build_secret_name(key)
47
+ secret_exists?(secret_name)
48
+ end
49
+
50
+ def get(key)
51
+ secret_name = build_secret_name(key)
52
+ return nil unless secret_exists?(secret_name)
53
+
54
+ unless AzureKeyVault.azure_cli_authenticated?
55
+ raise "Azure CLI is not authenticated. Run 'az login' first"
56
+ end
57
+
58
+ cmd = [
59
+ "az keyvault secret show",
60
+ "--vault-name '#{@keyvault_name}'",
61
+ "--name '#{secret_name}'",
62
+ "--query value",
63
+ "-o tsv",
64
+ "2>&1"
65
+ ].join(" ")
66
+
67
+ output = `#{cmd}`.strip
68
+ exit_code = $?.exitstatus
69
+
70
+ if exit_code != 0
71
+ nil
72
+ else
73
+ output
74
+ end
75
+ end
76
+
77
+ def set(key, value)
78
+ secret_name = build_secret_name(key)
79
+ AzureKeyVault.set(secret_name, value.to_s, keyvault_name: @keyvault_name)
80
+ end
81
+
82
+ def get_secrets_filename
83
+ "#{@keyvault_name}/secrets"
84
+ end
85
+
86
+ def get_secrets_hash
87
+ # Load all secrets from Azure Key Vault
88
+ # This is a simplified version - in practice, you might want to list all secrets
89
+ # For now, we'll return an empty hash and let individual get() calls retrieve secrets
90
+ {}
91
+ end
92
+
93
+ def save_secrets_hash(hash)
94
+ # Save all secrets from hash to Azure Key Vault
95
+ hash.each do |key, value|
96
+ set(key, value)
97
+ end
98
+ end
99
+
100
+ def info
101
+ unless AzureKeyVault.azure_cli_authenticated?
102
+ puts " Azure CLI is not authenticated. Run 'az login' first"
103
+ return
104
+ end
105
+
106
+ cmd = [
107
+ "az keyvault secret list",
108
+ "--vault-name '#{@keyvault_name}'",
109
+ "--query '[].name'",
110
+ "-o tsv",
111
+ "2>&1"
112
+ ].join(" ")
113
+
114
+ output = `#{cmd}`.strip
115
+ exit_code = $?.exitstatus
116
+
117
+ if exit_code != 0
118
+ # Check if it's a permission error
119
+ if output.include?("Forbidden") || output.include?("ForbiddenByRbac")
120
+ puts " Error: Insufficient permissions to list secrets from Azure Key Vault"
121
+ puts " The authenticated identity does not have the required RBAC permissions."
122
+ puts " Required permission: 'Microsoft.KeyVault/vaults/secrets/readMetadata/action'"
123
+ puts " Required role: 'Key Vault Secrets User' or 'Key Vault Secrets Officer'"
124
+ puts " Vault: #{@keyvault_name}"
125
+ puts ""
126
+ puts " To fix this, ask your Azure administrator to grant you one of these roles:"
127
+ puts " - Key Vault Secrets User (read-only access to secret names and values)"
128
+ puts " - Key Vault Secrets Officer (full access to secrets)"
129
+ else
130
+ puts " Error: Failed to list secrets from Azure Key Vault"
131
+ puts " #{output}"
132
+ end
133
+ return
134
+ end
135
+
136
+ secret_names = output.split("\n").map(&:strip).reject(&:empty?)
137
+
138
+ if @secret_prefix
139
+ # Filter secrets that match the prefix and remove the prefix
140
+ filtered_secrets = secret_names.select { |name| name.start_with?("#{@secret_prefix}-") }
141
+ .map { |name| name.sub(/^#{Regexp.escape(@secret_prefix)}-/, "") }
142
+ else
143
+ filtered_secrets = secret_names
144
+ end
145
+
146
+ if filtered_secrets.empty?
147
+ puts " No secrets found"
148
+ else
149
+ puts " Available secrets (#{filtered_secrets.count}):"
150
+ filtered_secrets.sort.each do |key|
151
+ puts " - #{key}"
152
+ end
153
+ end
154
+ end
155
+
156
+ private
157
+
158
+ def build_secret_name(key)
159
+ if @secret_prefix
160
+ "#{@secret_prefix}-#{key}"
161
+ else
162
+ key.to_s
163
+ end
164
+ end
165
+
166
+ def secret_exists?(secret_name)
167
+ unless AzureKeyVault.azure_cli_authenticated?
168
+ return false
169
+ end
170
+
171
+ cmd = [
172
+ "az keyvault secret show",
173
+ "--vault-name '#{@keyvault_name}'",
174
+ "--name '#{secret_name}'",
175
+ "2>&1"
176
+ ].join(" ")
177
+
178
+ system("#{cmd} > /dev/null 2>&1")
179
+ end
180
+ end
181
+ end
182
+ end
183
+
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require_relative "../directories" unless defined?(Makit::Directories)
5
+
6
+ module Makit
7
+ module Secrets
8
+ class LocalSecrets
9
+ def add(key, value)
10
+ secrets_hash = get_secrets_hash
11
+ secrets_hash[key] = value
12
+ save_secrets_hash(secrets_hash)
13
+ end
14
+
15
+ def remove(key)
16
+ secrets_hash = get_secrets_hash
17
+ secrets_hash.delete(key)
18
+ save_secrets_hash(secrets_hash)
19
+ end
20
+
21
+ def has_key?(key)
22
+ secrets_hash = get_secrets_hash
23
+ secrets_hash.key?(key)
24
+ end
25
+
26
+ def get(key)
27
+ secrets_hash = get_secrets_hash
28
+ secrets_hash[key]
29
+ end
30
+
31
+ def set(key, value)
32
+ secrets_hash = get_secrets_hash
33
+ secrets_hash[key] = value
34
+ save_secrets_hash(secrets_hash)
35
+ end
36
+
37
+ def get_secrets_filename
38
+ "#{Makit::Directories::ROOT}/secrets.json"
39
+ end
40
+
41
+ def get_secrets_hash
42
+ secrets_file = get_secrets_filename
43
+ return {} unless File.exist?(secrets_file)
44
+
45
+ text = File.read(secrets_file).strip
46
+ return {} if text.empty?
47
+
48
+ JSON.parse(text)
49
+ rescue JSON::ParserError
50
+ {}
51
+ end
52
+
53
+ def save_secrets_hash(hash)
54
+ secrets_file = get_secrets_filename
55
+ # pretty print the hash
56
+ File.open(secrets_file, "w") { |f| f.puts JSON.pretty_generate(hash) }
57
+ end
58
+
59
+ def info
60
+ secrets_hash = get_secrets_hash
61
+ if secrets_hash.empty?
62
+ puts " No secrets found"
63
+ else
64
+ puts " Available secrets (#{secrets_hash.keys.count}):"
65
+ secrets_hash.keys.sort.each do |key|
66
+ puts " - #{key}"
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "local_secrets"
4
+
5
+ module Makit
6
+ module Secrets
7
+ # Manager class for secrets management
8
+ # Uses AzureSecrets if Azure Key Vault environment variables are set,
9
+ # otherwise falls back to LocalSecrets
10
+ class SecretsManager
11
+ def initialize
12
+ @secrets_backend = choose_backend
13
+ end
14
+
15
+ private
16
+
17
+ def choose_backend
18
+ # Check if Azure Key Vault environment variables are set
19
+ if azure_keyvault_configured?
20
+ require_relative "azure_secrets"
21
+ AzureSecrets.new
22
+ else
23
+ LocalSecrets.new
24
+ end
25
+ end
26
+
27
+ def azure_keyvault_configured?
28
+ # Check for Azure Key Vault configuration
29
+ # Requires both AZURE_SERVICE_PRINCIPAL_APP_ID and AZURE_KEYVAULT_NAME
30
+ ENV["AZURE_SERVICE_PRINCIPAL_APP_ID"] && !ENV["AZURE_SERVICE_PRINCIPAL_APP_ID"].empty? &&
31
+ ENV["AZURE_KEYVAULT_NAME"] && !ENV["AZURE_KEYVAULT_NAME"].empty?
32
+ end
33
+
34
+ public
35
+
36
+ def add(key, value)
37
+ @secrets_backend.add(key, value)
38
+ end
39
+
40
+ def remove(key)
41
+ @secrets_backend.remove(key)
42
+ end
43
+
44
+ def has_key?(key)
45
+ @secrets_backend.has_key?(key)
46
+ end
47
+
48
+ def key?(key)
49
+ @secrets_backend.has_key?(key)
50
+ end
51
+
52
+ def get(key)
53
+ @secrets_backend.get(key)
54
+ end
55
+
56
+ def set(key, value)
57
+ @secrets_backend.set(key, value)
58
+ end
59
+
60
+ def get_secrets_filename
61
+ @secrets_backend.get_secrets_filename
62
+ end
63
+
64
+ def get_secrets_hash
65
+ @secrets_backend.get_secrets_hash
66
+ end
67
+
68
+ def save_secrets_hash(hash)
69
+ @secrets_backend.save_secrets_hash(hash)
70
+ end
71
+
72
+ def info
73
+ backend_name = backend_type
74
+ puts " Backend: #{backend_name}"
75
+
76
+ backend_class_name = @secrets_backend.class.name
77
+ if backend_class_name == "Makit::Secrets::AzureSecrets"
78
+ puts " Key Vault: #{@secrets_backend.instance_variable_get(:@keyvault_name)}"
79
+ secret_name = Makit::Secrets::AzureKeyVault.secret_name
80
+ puts " Secret Name: #{secret_name}"
81
+ prefix = @secrets_backend.instance_variable_get(:@secret_prefix)
82
+ puts " Secret Prefix: #{prefix}" if prefix
83
+ elsif backend_class_name == "Makit::Secrets::LocalSecrets"
84
+ puts " Secrets File: #{@secrets_backend.get_secrets_filename}"
85
+ end
86
+ puts ""
87
+ @secrets_backend.info
88
+ end
89
+
90
+ private
91
+
92
+ def backend_type
93
+ backend_class_name = @secrets_backend.class.name
94
+ case backend_class_name
95
+ when "Makit::Secrets::AzureSecrets"
96
+ "Azure Key Vault"
97
+ when "Makit::Secrets::LocalSecrets"
98
+ "LocalSecrets"
99
+ else
100
+ backend_class_name.split("::").last
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end