makit 0.0.157 → 0.0.158
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 +4 -4
- data/README.md +41 -41
- data/exe/makit +5 -5
- data/lib/makit/apache.rb +28 -28
- data/lib/makit/auto.rb +48 -48
- data/lib/makit/azure/blob_storage.rb +257 -257
- data/lib/makit/azure/cli.rb +284 -284
- data/lib/makit/cli/base.rb +17 -17
- data/lib/makit/cli/build_commands.rb +500 -500
- data/lib/makit/cli/generators/base_generator.rb +74 -74
- data/lib/makit/cli/generators/dotnet_generator.rb +50 -50
- data/lib/makit/cli/generators/generator_factory.rb +49 -49
- data/lib/makit/cli/generators/node_generator.rb +50 -50
- data/lib/makit/cli/generators/ruby_generator.rb +77 -77
- data/lib/makit/cli/generators/rust_generator.rb +50 -50
- data/lib/makit/cli/generators/templates/dotnet_templates.rb +167 -167
- data/lib/makit/cli/generators/templates/node_templates.rb +161 -161
- data/lib/makit/cli/generators/templates/ruby/gemfile.rb +26 -26
- data/lib/makit/cli/generators/templates/ruby/gemspec.rb +41 -41
- data/lib/makit/cli/generators/templates/ruby/main_lib.rb +33 -33
- data/lib/makit/cli/generators/templates/ruby/rakefile.rb +35 -35
- data/lib/makit/cli/generators/templates/ruby/readme.rb +63 -63
- data/lib/makit/cli/generators/templates/ruby/test.rb +39 -39
- data/lib/makit/cli/generators/templates/ruby/test_helper.rb +29 -29
- data/lib/makit/cli/generators/templates/ruby/version.rb +29 -29
- data/lib/makit/cli/generators/templates/rust_templates.rb +128 -128
- data/lib/makit/cli/main.rb +78 -78
- data/lib/makit/cli/pipeline_commands.rb +311 -311
- data/lib/makit/cli/project_commands.rb +868 -868
- data/lib/makit/cli/repository_commands.rb +661 -661
- data/lib/makit/cli/strategy_commands.rb +207 -207
- data/lib/makit/cli/utility_commands.rb +521 -521
- data/lib/makit/commands/factory.rb +359 -359
- data/lib/makit/commands/middleware/base.rb +73 -73
- data/lib/makit/commands/middleware/cache.rb +248 -248
- data/lib/makit/commands/middleware/command_logger.rb +312 -312
- data/lib/makit/commands/middleware/validator.rb +269 -269
- data/lib/makit/commands/request.rb +316 -316
- data/lib/makit/commands/result.rb +323 -323
- data/lib/makit/commands/runner.rb +386 -386
- data/lib/makit/commands/strategies/base.rb +171 -171
- data/lib/makit/commands/strategies/child_process.rb +162 -162
- data/lib/makit/commands/strategies/factory.rb +136 -136
- data/lib/makit/commands/strategies/synchronous.rb +139 -139
- data/lib/makit/commands.rb +50 -50
- data/lib/makit/configuration/dotnet_project.rb +48 -48
- data/lib/makit/configuration/gitlab_helper.rb +61 -61
- data/lib/makit/configuration/project.rb +292 -292
- data/lib/makit/configuration/rakefile_helper.rb +43 -43
- data/lib/makit/configuration/step.rb +34 -34
- data/lib/makit/configuration/timeout.rb +74 -74
- data/lib/makit/configuration.rb +21 -21
- data/lib/makit/content/default_gitignore.rb +7 -7
- data/lib/makit/content/default_gitignore.txt +225 -225
- data/lib/makit/content/default_rakefile.rb +13 -13
- data/lib/makit/content/gem_rakefile.rb +16 -16
- data/lib/makit/context.rb +1 -1
- data/lib/makit/data.rb +49 -49
- data/lib/makit/directories.rb +170 -170
- data/lib/makit/directory.rb +262 -262
- data/lib/makit/docs/files.rb +89 -89
- data/lib/makit/docs/rake.rb +102 -102
- data/lib/makit/dotnet/cli.rb +69 -69
- data/lib/makit/dotnet/project.rb +217 -217
- data/lib/makit/dotnet/solution.rb +38 -38
- data/lib/makit/dotnet/solution_classlib.rb +239 -239
- data/lib/makit/dotnet/solution_console.rb +264 -264
- data/lib/makit/dotnet/solution_maui.rb +354 -354
- data/lib/makit/dotnet/solution_wasm.rb +275 -275
- data/lib/makit/dotnet/solution_wpf.rb +304 -304
- data/lib/makit/dotnet.rb +102 -102
- data/lib/makit/email.rb +90 -90
- data/lib/makit/environment.rb +142 -142
- data/lib/makit/examples/runner.rb +370 -370
- data/lib/makit/exceptions.rb +45 -45
- data/lib/makit/fileinfo.rb +32 -32
- data/lib/makit/files.rb +43 -43
- data/lib/makit/gems.rb +40 -40
- data/lib/makit/git/cli.rb +78 -54
- data/lib/makit/git/repository.rb +100 -100
- data/lib/makit/git.rb +104 -104
- data/lib/makit/gitlab/pipeline.rb +857 -857
- data/lib/makit/gitlab/pipeline_service_impl.rb +1535 -1535
- data/lib/makit/gitlab_runner.rb +59 -59
- data/lib/makit/humanize.rb +218 -218
- data/lib/makit/indexer.rb +47 -47
- data/lib/makit/io/filesystem.rb +111 -111
- data/lib/makit/io/filesystem_service_impl.rb +337 -337
- data/lib/makit/lint.rb +212 -212
- data/lib/makit/logging/configuration.rb +309 -309
- data/lib/makit/logging/format_registry.rb +84 -84
- data/lib/makit/logging/formatters/base.rb +39 -39
- data/lib/makit/logging/formatters/console_formatter.rb +140 -140
- data/lib/makit/logging/formatters/json_formatter.rb +65 -65
- data/lib/makit/logging/formatters/plain_text_formatter.rb +71 -71
- data/lib/makit/logging/formatters/text_formatter.rb +64 -64
- data/lib/makit/logging/log_request.rb +119 -119
- data/lib/makit/logging/logger.rb +199 -199
- data/lib/makit/logging/sinks/base.rb +91 -91
- data/lib/makit/logging/sinks/console.rb +72 -72
- data/lib/makit/logging/sinks/file_sink.rb +92 -92
- data/lib/makit/logging/sinks/structured.rb +123 -123
- data/lib/makit/logging/sinks/unified_file_sink.rb +296 -296
- data/lib/makit/logging.rb +578 -578
- data/lib/makit/markdown.rb +75 -75
- data/lib/makit/mp/basic_object_mp.rb +17 -17
- data/lib/makit/mp/command_mp.rb +13 -13
- data/lib/makit/mp/command_request.mp.rb +17 -17
- data/lib/makit/mp/project_mp.rb +199 -199
- data/lib/makit/mp/string_mp.rb +205 -205
- data/lib/makit/nuget.rb +74 -74
- data/lib/makit/podman/podman.rb +458 -458
- data/lib/makit/podman/podman_service_impl.rb +1081 -1081
- data/lib/makit/port.rb +32 -32
- data/lib/makit/process.rb +377 -377
- data/lib/makit/protoc.rb +112 -112
- data/lib/makit/rake/cli.rb +196 -196
- data/lib/makit/rake/trace_controller.rb +174 -174
- data/lib/makit/rake.rb +81 -81
- data/lib/makit/ruby/cli.rb +185 -185
- data/lib/makit/ruby.rb +25 -25
- data/lib/makit/rubygems.rb +137 -0
- data/lib/makit/secrets/azure_key_vault.rb +322 -322
- data/lib/makit/secrets/azure_secrets.rb +183 -183
- data/lib/makit/secrets/local_secrets.rb +72 -72
- data/lib/makit/secrets/secrets_manager.rb +105 -105
- data/lib/makit/secrets.rb +16 -16
- data/lib/makit/serializer.rb +130 -130
- data/lib/makit/services/builder.rb +186 -186
- data/lib/makit/services/error_handler.rb +226 -226
- data/lib/makit/services/repository_manager.rb +367 -367
- data/lib/makit/services/validator.rb +112 -112
- data/lib/makit/setup/classlib.rb +101 -101
- data/lib/makit/setup/gem.rb +268 -268
- data/lib/makit/setup/pages.rb +11 -11
- data/lib/makit/setup/razorclasslib.rb +101 -101
- data/lib/makit/setup/runner.rb +54 -54
- data/lib/makit/setup.rb +5 -5
- data/lib/makit/show.rb +110 -110
- data/lib/makit/storage.rb +126 -126
- data/lib/makit/symbols.rb +175 -175
- data/lib/makit/task_info.rb +130 -130
- data/lib/makit/tasks/at_exit.rb +15 -15
- data/lib/makit/tasks/build.rb +22 -22
- data/lib/makit/tasks/bump.rb +7 -7
- data/lib/makit/tasks/clean.rb +13 -13
- data/lib/makit/tasks/configure.rb +10 -10
- data/lib/makit/tasks/format.rb +10 -10
- data/lib/makit/tasks/hook_manager.rb +443 -443
- data/lib/makit/tasks/info.rb +368 -368
- data/lib/makit/tasks/init.rb +49 -49
- data/lib/makit/tasks/integrate.rb +60 -56
- data/lib/makit/tasks/pull_incoming.rb +13 -13
- data/lib/makit/tasks/secrets.rb +7 -7
- data/lib/makit/tasks/setup.rb +16 -16
- data/lib/makit/tasks/sync.rb +14 -17
- data/lib/makit/tasks/tag.rb +27 -27
- data/lib/makit/tasks/task_monkey_patch.rb +81 -81
- data/lib/makit/tasks/test.rb +22 -22
- data/lib/makit/tasks/update.rb +18 -18
- data/lib/makit/tasks/version.rb +6 -6
- data/lib/makit/tasks.rb +24 -24
- data/lib/makit/test_cache.rb +239 -239
- data/lib/makit/tree.rb +37 -37
- data/lib/makit/v1/configuration/project_service_impl.rb +370 -370
- data/lib/makit/v1/git/git_repository_service_impl.rb +295 -295
- data/lib/makit/v1/makit.v1_pb.rb +35 -35
- data/lib/makit/v1/makit.v1_services_pb.rb +27 -27
- data/lib/makit/v1/services/repository_manager_service_impl.rb +572 -572
- data/lib/makit/version.rb +661 -503
- data/lib/makit/version_util.rb +21 -21
- data/lib/makit/wix.rb +95 -95
- data/lib/makit/yaml.rb +29 -29
- data/lib/makit/zip.rb +17 -17
- data/lib/makit copy.rb +44 -44
- data/lib/makit.rb +115 -114
- metadata +3 -2
|
@@ -1,257 +1,257 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# Azure Blob Storage helper methods
|
|
4
|
-
module Makit
|
|
5
|
-
module Azure
|
|
6
|
-
class BlobStorage
|
|
7
|
-
# Get Azure Storage account from environment variable or Azure Key Vault
|
|
8
|
-
# @return [String, nil] The storage account name, or nil if not found
|
|
9
|
-
def self.storage_account
|
|
10
|
-
# First try environment variable
|
|
11
|
-
return ENV["AZURE_STORAGE_ACCOUNT"] if ENV["AZURE_STORAGE_ACCOUNT"] && !ENV["AZURE_STORAGE_ACCOUNT"].empty?
|
|
12
|
-
|
|
13
|
-
# Fall back to Azure Key Vault if available (requires az login)
|
|
14
|
-
retrieve_from_key_vault("AZURE_STORAGE_ACCOUNT")
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
# Get Azure Storage account key from environment variable or Azure Key Vault
|
|
18
|
-
# @return [String, nil] The storage account key, or nil if not found
|
|
19
|
-
def self.storage_account_key
|
|
20
|
-
# First try environment variable
|
|
21
|
-
return ENV["AZURE_STORAGE_ACCOUNT_KEY"] if ENV["AZURE_STORAGE_ACCOUNT_KEY"] && !ENV["AZURE_STORAGE_ACCOUNT_KEY"].empty?
|
|
22
|
-
|
|
23
|
-
# Fall back to Azure Key Vault if available (requires az login)
|
|
24
|
-
retrieve_from_key_vault("AZURE_STORAGE_ACCOUNT_KEY")
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
# Retrieve a secret from Azure Key Vault
|
|
28
|
-
# Only attempts retrieval if Azure CLI is authenticated (az login has been performed)
|
|
29
|
-
# @param key [String] The secret key name (e.g., "AZURE_STORAGE_ACCOUNT")
|
|
30
|
-
# @return [String, nil] The secret value, or nil if not found or Azure CLI not authenticated
|
|
31
|
-
def self.retrieve_from_key_vault(key)
|
|
32
|
-
# Check if Azure Key Vault is configured
|
|
33
|
-
keyvault_name = ENV["AZURE_KEYVAULT_NAME"]
|
|
34
|
-
return nil unless keyvault_name && !keyvault_name.empty?
|
|
35
|
-
|
|
36
|
-
# Check if Azure CLI is authenticated
|
|
37
|
-
require_relative "../secrets/azure_key_vault"
|
|
38
|
-
return nil unless Makit::Secrets::AzureKeyVault.azure_cli_authenticated?
|
|
39
|
-
|
|
40
|
-
# Try to retrieve from Key Vault
|
|
41
|
-
begin
|
|
42
|
-
require_relative "../secrets/azure_secrets"
|
|
43
|
-
azure_secrets = Makit::Secrets::AzureSecrets.new(keyvault_name: keyvault_name)
|
|
44
|
-
value = azure_secrets.get(key)
|
|
45
|
-
return value if value && !value.empty?
|
|
46
|
-
rescue => e
|
|
47
|
-
# Silently fail - Key Vault retrieval is optional
|
|
48
|
-
# This allows the code to work without Key Vault if env vars are set
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
nil
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
# Validate Azure Storage credentials are set
|
|
55
|
-
# Checks environment variables first, then Azure Key Vault (if az login has been performed)
|
|
56
|
-
def self.validate_storage_credentials!
|
|
57
|
-
account = storage_account
|
|
58
|
-
key = storage_account_key
|
|
59
|
-
|
|
60
|
-
if account.nil? || account.empty?
|
|
61
|
-
error_msg = "AZURE_STORAGE_ACCOUNT is not set"
|
|
62
|
-
# Provide helpful message about Key Vault fallback
|
|
63
|
-
if ENV["AZURE_KEYVAULT_NAME"] && !ENV["AZURE_KEYVAULT_NAME"].empty?
|
|
64
|
-
require_relative "../secrets/azure_key_vault"
|
|
65
|
-
if Makit::Secrets::AzureKeyVault.azure_cli_authenticated?
|
|
66
|
-
error_msg += " (checked environment variables and Azure Key Vault '#{ENV["AZURE_KEYVAULT_NAME"]}')"
|
|
67
|
-
else
|
|
68
|
-
error_msg += " (Azure Key Vault available but 'az login' not performed)"
|
|
69
|
-
end
|
|
70
|
-
end
|
|
71
|
-
raise error_msg
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
if key.nil? || key.empty?
|
|
75
|
-
error_msg = "AZURE_STORAGE_ACCOUNT_KEY is not set"
|
|
76
|
-
# Provide helpful message about Key Vault fallback
|
|
77
|
-
if ENV["AZURE_KEYVAULT_NAME"] && !ENV["AZURE_KEYVAULT_NAME"].empty?
|
|
78
|
-
require_relative "../secrets/azure_key_vault"
|
|
79
|
-
if Makit::Secrets::AzureKeyVault.azure_cli_authenticated?
|
|
80
|
-
error_msg += " (checked environment variables and Azure Key Vault '#{ENV["AZURE_KEYVAULT_NAME"]}')"
|
|
81
|
-
else
|
|
82
|
-
error_msg += " (Azure Key Vault available but 'az login' not performed)"
|
|
83
|
-
end
|
|
84
|
-
end
|
|
85
|
-
raise error_msg
|
|
86
|
-
end
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
# Delete all blobs in a storage path
|
|
90
|
-
# @param path [String] The storage path (e.g., '$web/apps/portal/v0.1.0')
|
|
91
|
-
def self.delete_blobs(path)
|
|
92
|
-
validate_storage_credentials!
|
|
93
|
-
puts " Deleting existing files in #{path}/".colorize(:yellow)
|
|
94
|
-
|
|
95
|
-
cmd = [
|
|
96
|
-
"az storage blob delete-batch",
|
|
97
|
-
"--source '#{path}'",
|
|
98
|
-
"--account-name #{storage_account}",
|
|
99
|
-
"--account-key '#{storage_account_key}'",
|
|
100
|
-
"--pattern '*'",
|
|
101
|
-
"2>/dev/null",
|
|
102
|
-
].join(" ")
|
|
103
|
-
|
|
104
|
-
system(cmd)
|
|
105
|
-
# Don't fail if path doesn't exist (first deployment)
|
|
106
|
-
true
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
# Upload files to Azure Storage
|
|
110
|
-
# @param source [String] Local directory to upload from
|
|
111
|
-
# @param destination [String] Storage path destination (e.g., '$web/apps/portal/v0.1.0')
|
|
112
|
-
def self.upload_blobs(source, destination)
|
|
113
|
-
validate_storage_credentials!
|
|
114
|
-
puts " Uploading files from #{source} to #{destination}/".colorize(:green)
|
|
115
|
-
|
|
116
|
-
unless Dir.exist?(source)
|
|
117
|
-
raise "Source directory does not exist: #{source}"
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
cmd = [
|
|
121
|
-
"az storage blob upload-batch",
|
|
122
|
-
"--destination '#{destination}'",
|
|
123
|
-
"--source #{source}",
|
|
124
|
-
"--account-name #{storage_account}",
|
|
125
|
-
"--account-key '#{storage_account_key}'",
|
|
126
|
-
"--overwrite",
|
|
127
|
-
].join(" ")
|
|
128
|
-
|
|
129
|
-
unless system(cmd)
|
|
130
|
-
raise "Failed to upload blobs to Azure Storage"
|
|
131
|
-
end
|
|
132
|
-
true
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
# Get static website endpoint URL for the storage account
|
|
136
|
-
# @return [String] The static website endpoint URL (e.g., 'https://louparslow.z22.web.core.windows.net')
|
|
137
|
-
def self.static_website_endpoint
|
|
138
|
-
validate_storage_credentials!
|
|
139
|
-
|
|
140
|
-
unless Cli.available?
|
|
141
|
-
# Fallback if Azure CLI not available
|
|
142
|
-
return "https://#{storage_account}.z22.web.core.windows.net"
|
|
143
|
-
end
|
|
144
|
-
|
|
145
|
-
# Try to get the static website endpoint from Azure
|
|
146
|
-
cmd = "az storage account show --name #{storage_account} --query 'primaryEndpoints.web' --output tsv 2>/dev/null"
|
|
147
|
-
endpoint = `#{cmd}`.strip
|
|
148
|
-
|
|
149
|
-
if endpoint.nil? || endpoint.empty?
|
|
150
|
-
# Fallback: construct URL from storage account name
|
|
151
|
-
# This assumes the standard format, but may not work for all storage accounts
|
|
152
|
-
endpoint = "https://#{storage_account}.z22.web.core.windows.net"
|
|
153
|
-
puts " Warning: Could not retrieve static website endpoint, using default format".colorize(:yellow)
|
|
154
|
-
end
|
|
155
|
-
|
|
156
|
-
# Remove trailing slash if present
|
|
157
|
-
endpoint.chomp("/")
|
|
158
|
-
end
|
|
159
|
-
|
|
160
|
-
# List blobs in a storage path
|
|
161
|
-
# @param path [String] The storage path (e.g., '$web' or '$web/apps/portal')
|
|
162
|
-
# @return [Array<String>] Array of blob names/paths (immediate children only, flat listing)
|
|
163
|
-
def self.list_blobs(path)
|
|
164
|
-
validate_storage_credentials!
|
|
165
|
-
|
|
166
|
-
# Parse path to extract container name and prefix
|
|
167
|
-
# Path format: "container" or "container/prefix/path"
|
|
168
|
-
path_parts = path.split("/", 2)
|
|
169
|
-
container_name = path_parts[0]
|
|
170
|
-
prefix = path_parts[1] || ""
|
|
171
|
-
|
|
172
|
-
# Build Azure CLI command
|
|
173
|
-
cmd_parts = [
|
|
174
|
-
"az storage blob list",
|
|
175
|
-
"--container-name '#{container_name}'",
|
|
176
|
-
"--account-name #{storage_account}",
|
|
177
|
-
"--account-key '#{storage_account_key}'",
|
|
178
|
-
"--output json",
|
|
179
|
-
"--query '[].name'",
|
|
180
|
-
"2>/dev/null",
|
|
181
|
-
]
|
|
182
|
-
|
|
183
|
-
# Add prefix if specified
|
|
184
|
-
unless prefix.empty?
|
|
185
|
-
cmd_parts.insert(4, "--prefix '#{prefix}/'")
|
|
186
|
-
end
|
|
187
|
-
|
|
188
|
-
cmd = cmd_parts.join(" ")
|
|
189
|
-
|
|
190
|
-
# Execute command and capture output
|
|
191
|
-
output = `#{cmd}`.strip
|
|
192
|
-
exit_code = $?.exitstatus
|
|
193
|
-
|
|
194
|
-
# Handle command failure
|
|
195
|
-
# Check exit code - non-zero indicates command failure
|
|
196
|
-
if exit_code != 0
|
|
197
|
-
raise "Failed to list blobs from Azure Storage"
|
|
198
|
-
end
|
|
199
|
-
|
|
200
|
-
# Parse JSON output
|
|
201
|
-
begin
|
|
202
|
-
require "json"
|
|
203
|
-
# Empty string is not valid JSON and indicates command failure
|
|
204
|
-
# Valid empty result from Azure CLI would be "[]", not ""
|
|
205
|
-
if output.empty?
|
|
206
|
-
raise "Failed to list blobs from Azure Storage"
|
|
207
|
-
end
|
|
208
|
-
blob_data = JSON.parse(output)
|
|
209
|
-
rescue JSON::ParserError => e
|
|
210
|
-
# Invalid JSON indicates parsing failure
|
|
211
|
-
raise "Failed to parse Azure CLI output: #{e.message}"
|
|
212
|
-
end
|
|
213
|
-
|
|
214
|
-
# Return empty array if no blobs found
|
|
215
|
-
return [] if blob_data.nil? || blob_data.empty?
|
|
216
|
-
|
|
217
|
-
# Extract blob names from JSON array
|
|
218
|
-
# JSON format from Azure CLI: [{"name":"path/to/blob"}, ...] or ["name1", "name2", ...]
|
|
219
|
-
blob_names = if blob_data.first.is_a?(Hash)
|
|
220
|
-
blob_data.map { |item| item["name"] }
|
|
221
|
-
else
|
|
222
|
-
blob_data
|
|
223
|
-
end
|
|
224
|
-
|
|
225
|
-
# Filter to get only immediate children (flat listing)
|
|
226
|
-
prefix_with_slash = prefix.empty? ? "" : "#{prefix}/"
|
|
227
|
-
immediate_children = []
|
|
228
|
-
|
|
229
|
-
blob_names.each do |blob_name|
|
|
230
|
-
# Remove prefix if specified to get relative path
|
|
231
|
-
relative_path = if prefix_with_slash.empty?
|
|
232
|
-
blob_name
|
|
233
|
-
else
|
|
234
|
-
# Remove prefix from blob name
|
|
235
|
-
blob_name.sub(/^#{Regexp.escape(prefix_with_slash)}/, "")
|
|
236
|
-
end
|
|
237
|
-
|
|
238
|
-
# Get only immediate children (first level after prefix)
|
|
239
|
-
# Split by '/' and take first part, but preserve trailing slash if present
|
|
240
|
-
parts = relative_path.split("/", 2)
|
|
241
|
-
first_part = parts[0]
|
|
242
|
-
|
|
243
|
-
# If the original blob_name had a trailing slash or ends with "/", preserve it
|
|
244
|
-
# Check if this is a directory (ends with /) in the original name
|
|
245
|
-
is_directory = blob_name.end_with?("/") || (parts.length > 1 && relative_path.end_with?("/"))
|
|
246
|
-
first_part += "/" if is_directory && !first_part.end_with?("/")
|
|
247
|
-
|
|
248
|
-
# Add to results if not already included (avoid duplicates)
|
|
249
|
-
immediate_children << first_part unless immediate_children.include?(first_part)
|
|
250
|
-
end
|
|
251
|
-
|
|
252
|
-
immediate_children
|
|
253
|
-
end
|
|
254
|
-
end
|
|
255
|
-
end
|
|
256
|
-
end
|
|
257
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Azure Blob Storage helper methods
|
|
4
|
+
module Makit
|
|
5
|
+
module Azure
|
|
6
|
+
class BlobStorage
|
|
7
|
+
# Get Azure Storage account from environment variable or Azure Key Vault
|
|
8
|
+
# @return [String, nil] The storage account name, or nil if not found
|
|
9
|
+
def self.storage_account
|
|
10
|
+
# First try environment variable
|
|
11
|
+
return ENV["AZURE_STORAGE_ACCOUNT"] if ENV["AZURE_STORAGE_ACCOUNT"] && !ENV["AZURE_STORAGE_ACCOUNT"].empty?
|
|
12
|
+
|
|
13
|
+
# Fall back to Azure Key Vault if available (requires az login)
|
|
14
|
+
retrieve_from_key_vault("AZURE_STORAGE_ACCOUNT")
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Get Azure Storage account key from environment variable or Azure Key Vault
|
|
18
|
+
# @return [String, nil] The storage account key, or nil if not found
|
|
19
|
+
def self.storage_account_key
|
|
20
|
+
# First try environment variable
|
|
21
|
+
return ENV["AZURE_STORAGE_ACCOUNT_KEY"] if ENV["AZURE_STORAGE_ACCOUNT_KEY"] && !ENV["AZURE_STORAGE_ACCOUNT_KEY"].empty?
|
|
22
|
+
|
|
23
|
+
# Fall back to Azure Key Vault if available (requires az login)
|
|
24
|
+
retrieve_from_key_vault("AZURE_STORAGE_ACCOUNT_KEY")
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Retrieve a secret from Azure Key Vault
|
|
28
|
+
# Only attempts retrieval if Azure CLI is authenticated (az login has been performed)
|
|
29
|
+
# @param key [String] The secret key name (e.g., "AZURE_STORAGE_ACCOUNT")
|
|
30
|
+
# @return [String, nil] The secret value, or nil if not found or Azure CLI not authenticated
|
|
31
|
+
def self.retrieve_from_key_vault(key)
|
|
32
|
+
# Check if Azure Key Vault is configured
|
|
33
|
+
keyvault_name = ENV["AZURE_KEYVAULT_NAME"]
|
|
34
|
+
return nil unless keyvault_name && !keyvault_name.empty?
|
|
35
|
+
|
|
36
|
+
# Check if Azure CLI is authenticated
|
|
37
|
+
require_relative "../secrets/azure_key_vault"
|
|
38
|
+
return nil unless Makit::Secrets::AzureKeyVault.azure_cli_authenticated?
|
|
39
|
+
|
|
40
|
+
# Try to retrieve from Key Vault
|
|
41
|
+
begin
|
|
42
|
+
require_relative "../secrets/azure_secrets"
|
|
43
|
+
azure_secrets = Makit::Secrets::AzureSecrets.new(keyvault_name: keyvault_name)
|
|
44
|
+
value = azure_secrets.get(key)
|
|
45
|
+
return value if value && !value.empty?
|
|
46
|
+
rescue => e
|
|
47
|
+
# Silently fail - Key Vault retrieval is optional
|
|
48
|
+
# This allows the code to work without Key Vault if env vars are set
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
nil
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Validate Azure Storage credentials are set
|
|
55
|
+
# Checks environment variables first, then Azure Key Vault (if az login has been performed)
|
|
56
|
+
def self.validate_storage_credentials!
|
|
57
|
+
account = storage_account
|
|
58
|
+
key = storage_account_key
|
|
59
|
+
|
|
60
|
+
if account.nil? || account.empty?
|
|
61
|
+
error_msg = "AZURE_STORAGE_ACCOUNT is not set"
|
|
62
|
+
# Provide helpful message about Key Vault fallback
|
|
63
|
+
if ENV["AZURE_KEYVAULT_NAME"] && !ENV["AZURE_KEYVAULT_NAME"].empty?
|
|
64
|
+
require_relative "../secrets/azure_key_vault"
|
|
65
|
+
if Makit::Secrets::AzureKeyVault.azure_cli_authenticated?
|
|
66
|
+
error_msg += " (checked environment variables and Azure Key Vault '#{ENV["AZURE_KEYVAULT_NAME"]}')"
|
|
67
|
+
else
|
|
68
|
+
error_msg += " (Azure Key Vault available but 'az login' not performed)"
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
raise error_msg
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
if key.nil? || key.empty?
|
|
75
|
+
error_msg = "AZURE_STORAGE_ACCOUNT_KEY is not set"
|
|
76
|
+
# Provide helpful message about Key Vault fallback
|
|
77
|
+
if ENV["AZURE_KEYVAULT_NAME"] && !ENV["AZURE_KEYVAULT_NAME"].empty?
|
|
78
|
+
require_relative "../secrets/azure_key_vault"
|
|
79
|
+
if Makit::Secrets::AzureKeyVault.azure_cli_authenticated?
|
|
80
|
+
error_msg += " (checked environment variables and Azure Key Vault '#{ENV["AZURE_KEYVAULT_NAME"]}')"
|
|
81
|
+
else
|
|
82
|
+
error_msg += " (Azure Key Vault available but 'az login' not performed)"
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
raise error_msg
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Delete all blobs in a storage path
|
|
90
|
+
# @param path [String] The storage path (e.g., '$web/apps/portal/v0.1.0')
|
|
91
|
+
def self.delete_blobs(path)
|
|
92
|
+
validate_storage_credentials!
|
|
93
|
+
puts " Deleting existing files in #{path}/".colorize(:yellow)
|
|
94
|
+
|
|
95
|
+
cmd = [
|
|
96
|
+
"az storage blob delete-batch",
|
|
97
|
+
"--source '#{path}'",
|
|
98
|
+
"--account-name #{storage_account}",
|
|
99
|
+
"--account-key '#{storage_account_key}'",
|
|
100
|
+
"--pattern '*'",
|
|
101
|
+
"2>/dev/null",
|
|
102
|
+
].join(" ")
|
|
103
|
+
|
|
104
|
+
system(cmd)
|
|
105
|
+
# Don't fail if path doesn't exist (first deployment)
|
|
106
|
+
true
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Upload files to Azure Storage
|
|
110
|
+
# @param source [String] Local directory to upload from
|
|
111
|
+
# @param destination [String] Storage path destination (e.g., '$web/apps/portal/v0.1.0')
|
|
112
|
+
def self.upload_blobs(source, destination)
|
|
113
|
+
validate_storage_credentials!
|
|
114
|
+
puts " Uploading files from #{source} to #{destination}/".colorize(:green)
|
|
115
|
+
|
|
116
|
+
unless Dir.exist?(source)
|
|
117
|
+
raise "Source directory does not exist: #{source}"
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
cmd = [
|
|
121
|
+
"az storage blob upload-batch",
|
|
122
|
+
"--destination '#{destination}'",
|
|
123
|
+
"--source #{source}",
|
|
124
|
+
"--account-name #{storage_account}",
|
|
125
|
+
"--account-key '#{storage_account_key}'",
|
|
126
|
+
"--overwrite",
|
|
127
|
+
].join(" ")
|
|
128
|
+
|
|
129
|
+
unless system(cmd)
|
|
130
|
+
raise "Failed to upload blobs to Azure Storage"
|
|
131
|
+
end
|
|
132
|
+
true
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Get static website endpoint URL for the storage account
|
|
136
|
+
# @return [String] The static website endpoint URL (e.g., 'https://louparslow.z22.web.core.windows.net')
|
|
137
|
+
def self.static_website_endpoint
|
|
138
|
+
validate_storage_credentials!
|
|
139
|
+
|
|
140
|
+
unless Cli.available?
|
|
141
|
+
# Fallback if Azure CLI not available
|
|
142
|
+
return "https://#{storage_account}.z22.web.core.windows.net"
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Try to get the static website endpoint from Azure
|
|
146
|
+
cmd = "az storage account show --name #{storage_account} --query 'primaryEndpoints.web' --output tsv 2>/dev/null"
|
|
147
|
+
endpoint = `#{cmd}`.strip
|
|
148
|
+
|
|
149
|
+
if endpoint.nil? || endpoint.empty?
|
|
150
|
+
# Fallback: construct URL from storage account name
|
|
151
|
+
# This assumes the standard format, but may not work for all storage accounts
|
|
152
|
+
endpoint = "https://#{storage_account}.z22.web.core.windows.net"
|
|
153
|
+
puts " Warning: Could not retrieve static website endpoint, using default format".colorize(:yellow)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Remove trailing slash if present
|
|
157
|
+
endpoint.chomp("/")
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# List blobs in a storage path
|
|
161
|
+
# @param path [String] The storage path (e.g., '$web' or '$web/apps/portal')
|
|
162
|
+
# @return [Array<String>] Array of blob names/paths (immediate children only, flat listing)
|
|
163
|
+
def self.list_blobs(path)
|
|
164
|
+
validate_storage_credentials!
|
|
165
|
+
|
|
166
|
+
# Parse path to extract container name and prefix
|
|
167
|
+
# Path format: "container" or "container/prefix/path"
|
|
168
|
+
path_parts = path.split("/", 2)
|
|
169
|
+
container_name = path_parts[0]
|
|
170
|
+
prefix = path_parts[1] || ""
|
|
171
|
+
|
|
172
|
+
# Build Azure CLI command
|
|
173
|
+
cmd_parts = [
|
|
174
|
+
"az storage blob list",
|
|
175
|
+
"--container-name '#{container_name}'",
|
|
176
|
+
"--account-name #{storage_account}",
|
|
177
|
+
"--account-key '#{storage_account_key}'",
|
|
178
|
+
"--output json",
|
|
179
|
+
"--query '[].name'",
|
|
180
|
+
"2>/dev/null",
|
|
181
|
+
]
|
|
182
|
+
|
|
183
|
+
# Add prefix if specified
|
|
184
|
+
unless prefix.empty?
|
|
185
|
+
cmd_parts.insert(4, "--prefix '#{prefix}/'")
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
cmd = cmd_parts.join(" ")
|
|
189
|
+
|
|
190
|
+
# Execute command and capture output
|
|
191
|
+
output = `#{cmd}`.strip
|
|
192
|
+
exit_code = $?.exitstatus
|
|
193
|
+
|
|
194
|
+
# Handle command failure
|
|
195
|
+
# Check exit code - non-zero indicates command failure
|
|
196
|
+
if exit_code != 0
|
|
197
|
+
raise "Failed to list blobs from Azure Storage"
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Parse JSON output
|
|
201
|
+
begin
|
|
202
|
+
require "json"
|
|
203
|
+
# Empty string is not valid JSON and indicates command failure
|
|
204
|
+
# Valid empty result from Azure CLI would be "[]", not ""
|
|
205
|
+
if output.empty?
|
|
206
|
+
raise "Failed to list blobs from Azure Storage"
|
|
207
|
+
end
|
|
208
|
+
blob_data = JSON.parse(output)
|
|
209
|
+
rescue JSON::ParserError => e
|
|
210
|
+
# Invalid JSON indicates parsing failure
|
|
211
|
+
raise "Failed to parse Azure CLI output: #{e.message}"
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# Return empty array if no blobs found
|
|
215
|
+
return [] if blob_data.nil? || blob_data.empty?
|
|
216
|
+
|
|
217
|
+
# Extract blob names from JSON array
|
|
218
|
+
# JSON format from Azure CLI: [{"name":"path/to/blob"}, ...] or ["name1", "name2", ...]
|
|
219
|
+
blob_names = if blob_data.first.is_a?(Hash)
|
|
220
|
+
blob_data.map { |item| item["name"] }
|
|
221
|
+
else
|
|
222
|
+
blob_data
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# Filter to get only immediate children (flat listing)
|
|
226
|
+
prefix_with_slash = prefix.empty? ? "" : "#{prefix}/"
|
|
227
|
+
immediate_children = []
|
|
228
|
+
|
|
229
|
+
blob_names.each do |blob_name|
|
|
230
|
+
# Remove prefix if specified to get relative path
|
|
231
|
+
relative_path = if prefix_with_slash.empty?
|
|
232
|
+
blob_name
|
|
233
|
+
else
|
|
234
|
+
# Remove prefix from blob name
|
|
235
|
+
blob_name.sub(/^#{Regexp.escape(prefix_with_slash)}/, "")
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# Get only immediate children (first level after prefix)
|
|
239
|
+
# Split by '/' and take first part, but preserve trailing slash if present
|
|
240
|
+
parts = relative_path.split("/", 2)
|
|
241
|
+
first_part = parts[0]
|
|
242
|
+
|
|
243
|
+
# If the original blob_name had a trailing slash or ends with "/", preserve it
|
|
244
|
+
# Check if this is a directory (ends with /) in the original name
|
|
245
|
+
is_directory = blob_name.end_with?("/") || (parts.length > 1 && relative_path.end_with?("/"))
|
|
246
|
+
first_part += "/" if is_directory && !first_part.end_with?("/")
|
|
247
|
+
|
|
248
|
+
# Add to results if not already included (avoid duplicates)
|
|
249
|
+
immediate_children << first_part unless immediate_children.include?(first_part)
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
immediate_children
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
|