-
Notifications
You must be signed in to change notification settings - Fork 366
Read storage-cli config JSON from capi (AzureRM) #4581
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,29 +16,108 @@ class << self | |
attr_reader :registry | ||
|
||
def register(provider, klass) | ||
registry[provider] = klass | ||
registry[provider.to_s] = klass | ||
end | ||
|
||
def build(connection_config:, directory_key:, root_dir:, min_size: nil, max_size: nil) | ||
provider = connection_config[:provider] | ||
raise 'Missing connection_config[:provider]' if provider.nil? | ||
def build(directory_key:, root_dir:, resource_type: nil, min_size: nil, max_size: nil) | ||
raise 'Missing resource_type' if resource_type.nil? | ||
|
||
impl_class = registry[provider] | ||
cfg = fetch_and_validate_config!(resource_type) | ||
provider = cfg['provider'] | ||
|
||
key = provider.to_s | ||
impl_class = registry[key] || registry[key.downcase] || registry[key.upcase] | ||
raise "No storage CLI client registered for provider #{provider}" unless impl_class | ||
|
||
impl_class.new(connection_config:, directory_key:, root_dir:, min_size:, max_size:) | ||
impl_class.new(provider:, directory_key:, root_dir:, resource_type:, min_size:, max_size:) | ||
end | ||
|
||
def fetch_and_validate_config!(resource_type) | ||
path = config_path_for!(resource_type) | ||
|
||
begin | ||
json = Oj.load(File.read(path)) | ||
rescue StandardError => e | ||
raise BlobstoreError.new("Failed to parse storage-cli config JSON at #{path}: #{e.message}") | ||
end | ||
|
||
validate_required_keys!(json, path) | ||
json | ||
end | ||
|
||
def config_path_for!(resource_type) | ||
key = | ||
case resource_type.to_s | ||
when 'droplets', 'buildpack_cache' then :storage_cli_config_file_droplets | ||
when 'buildpacks' then :storage_cli_config_file_buildpacks | ||
when 'packages' then :storage_cli_config_file_packages | ||
when 'resource_pool' then :storage_cli_config_file_resource_pool | ||
else | ||
raise BlobstoreError.new("Unknown resource_type: #{resource_type}") | ||
end | ||
|
||
path = VCAP::CloudController::Config.config.get(key) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What if There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If resource_type does not match any of the specified cases line 50-54, the else branch will raise a BlobstoreError before key is assigned, so execution will not reach line 59 with a nil value for key. |
||
raise BlobstoreError.new("storage-cli config file not found or not readable at: #{path.inspect}") unless path && File.file?(path) && File.readable?(path) | ||
|
||
path | ||
end | ||
|
||
def validate_required_keys!(json, path) | ||
validate_provider!(json, path) | ||
required = %w[ | ||
account_key | ||
account_name | ||
container_name | ||
environment | ||
] | ||
missing = required.reject { |k| json.key?(k) && !json[k].to_s.strip.empty? } | ||
return if missing.empty? | ||
|
||
raise BlobstoreError.new("Missing required keys in config file #{path}: #{missing.join(', ')} (json: #{json})") | ||
end | ||
|
||
def validate_provider!(json, path) | ||
provider = json['provider'] | ||
return unless provider.nil? || provider.to_s.strip.empty? | ||
|
||
raise BlobstoreError.new("No provider specified in config file: #{path.inspect} json: #{json}") | ||
end | ||
end | ||
|
||
def initialize(connection_config:, directory_key:, root_dir:, min_size: nil, max_size: nil) | ||
def initialize(provider:, directory_key:, resource_type:, root_dir:, min_size: nil, max_size: nil) | ||
@cli_path = cli_path | ||
@directory_key = directory_key | ||
@resource_type = resource_type.to_s | ||
@root_dir = root_dir | ||
@min_size = min_size || 0 | ||
@max_size = max_size | ||
config = build_config(connection_config) | ||
@config_file = write_config_file(config) | ||
@fork = connection_config.fetch(:fork, false) | ||
@provider = provider | ||
|
||
file_path = case @resource_type | ||
when 'droplets', 'buildpack_cache' | ||
VCAP::CloudController::Config.config.get(:storage_cli_config_file_droplets) | ||
when 'buildpacks' | ||
VCAP::CloudController::Config.config.get(:storage_cli_config_file_buildpacks) | ||
when 'packages' | ||
VCAP::CloudController::Config.config.get(:storage_cli_config_file_packages) | ||
when 'resource_pool' | ||
VCAP::CloudController::Config.config.get(:storage_cli_config_file_resource_pool) | ||
else | ||
raise BlobstoreError.new("Unknown resource_type: #{@resource_type}") | ||
end | ||
|
||
unless file_path && File.file?(file_path) && File.readable?(file_path) | ||
raise BlobstoreError.new("storage-cli config file not found or not readable at: #{file_path.inspect}") | ||
end | ||
|
||
begin | ||
VCAP::CloudController::YAMLConfig.safe_load_file(file_path) | ||
rescue StandardError => e | ||
raise BlobstoreError.new("Failed to load storage-cli config at #{file_path}: #{e.message}") | ||
end | ||
|
||
@config_file = file_path | ||
logger.info('storage_cli_config_selected', resource_type: @resource_type, path: @config_file) | ||
end | ||
|
||
def local? | ||
|
@@ -88,28 +167,16 @@ def cp_to_blobstore(source_path, destination_key) | |
end | ||
|
||
def cp_file_between_keys(source_key, destination_key) | ||
if @fork | ||
run_cli('copy', partitioned_key(source_key), partitioned_key(destination_key)) | ||
else | ||
# Azure CLI doesn't support server-side copy yet, so fallback to local copy | ||
Tempfile.create('blob-copy') do |tmp| | ||
download_from_blobstore(source_key, tmp.path) | ||
cp_to_blobstore(tmp.path, destination_key) | ||
end | ||
end | ||
run_cli('copy', partitioned_key(source_key), partitioned_key(destination_key)) | ||
end | ||
|
||
def delete_all(_=nil) | ||
# page_size is currently not considered. Azure SDK / API has a limit of 5000 | ||
pass unless @fork | ||
|
||
# Currently, storage-cli does not support bulk deletion. | ||
run_cli('delete-recursive', @root_dir) | ||
end | ||
|
||
def delete_all_in_path(path) | ||
pass unless @fork | ||
|
||
# Currently, storage-cli does not support bulk deletion. | ||
run_cli('delete-recursive', partitioned_key(path)) | ||
end | ||
|
@@ -123,29 +190,19 @@ def delete_blob(blob) | |
end | ||
|
||
def blob(key) | ||
if @fork | ||
properties = properties(key) | ||
return nil if properties.nil? || properties.empty? | ||
|
||
signed_url = sign_url(partitioned_key(key), verb: 'get', expires_in_seconds: 3600) | ||
StorageCliBlob.new(key, properties:, signed_url:) | ||
elsif exists?(key) | ||
# Azure CLI does not support getting blob properties directly, so fallback to local check | ||
signed_url = sign_url(partitioned_key(key), verb: 'get', expires_in_seconds: 3600) | ||
StorageCliBlob.new(key, signed_url:) | ||
end | ||
properties = properties(key) | ||
return nil if properties.nil? || properties.empty? | ||
|
||
signed_url = sign_url(partitioned_key(key), verb: 'get', expires_in_seconds: 3600) | ||
StorageCliBlob.new(key, properties:, signed_url:) | ||
end | ||
|
||
def files_for(prefix, _ignored_directory_prefixes=[]) | ||
return nil unless @fork | ||
|
||
files, _status = run_cli('list', prefix) | ||
files.split("\n").map(&:strip).reject(&:empty?).map { |file| StorageCliBlob.new(file) } | ||
end | ||
|
||
def ensure_bucket_exists | ||
return unless @fork | ||
|
||
run_cli('ensure-bucket-exists') | ||
end | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why an exclamation mark in the method name? It doesn't modify the passed object. Same for the other methods in this class.