-
Notifications
You must be signed in to change notification settings - Fork 72
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
config: Transformer for old version of config
Refs: #2130 - Generic transformer for cnf-testsuite.yml configs to newer versions. - Extendable through addition of new transformation rules. - The current functionality transforms to configv2, the structure of which was proposed in #2129. Signed-off-by: svteb <[email protected]>
- Loading branch information
Showing
6 changed files
with
367 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
require "sam" | ||
require "totem" | ||
require "colorize" | ||
require "./utils/cnf_installation/transformer/config_transformer" | ||
|
||
desc "Converts an old configuration file to the latest version and saves it to the specified location" | ||
task "transform_config" do |_, args| | ||
# Ensure both arguments are provided | ||
if !((args.named.keys.includes? "old_config_path") && (args.named.keys.includes? "new_config_path")) | ||
stdout_warning "Usage: transform_config old_config_path=OLD_CONFIG_PATH new_config_path=NEW_CONFIG_PATH" | ||
exit(1) | ||
end | ||
|
||
old_config_path = args.named["old_config_path"].as(String) | ||
new_config_path = args.named["new_config_path"].as(String) | ||
|
||
# Check if the old config file exists | ||
unless File.exists?(old_config_path) | ||
stdout_failure "Error: The old config file '#{old_config_path}' does not exist." | ||
exit(1) | ||
end | ||
|
||
begin | ||
# Initialize the ConfigTransformer | ||
transformer = CNFInstall::Config::ConfigTransformer.new(old_config_path) | ||
transformer.transform | ||
|
||
# Serialize the transformed config to the new file | ||
transformer.serialize_to_file(new_config_path) | ||
|
||
stdout_success "Configuration successfully transformed and saved to '#{new_config_path}'." | ||
rescue ex : CNFInstall::Config::UnsupportedConfigVersionError | ||
stdout_failure ex.message | ||
rescue ex : Exception | ||
stdout_failure "Unexpected error: #{ex.class}" | ||
end | ||
end |
68 changes: 68 additions & 0 deletions
68
src/tasks/utils/cnf_installation/config_versions/config_v1.cr
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
module CNFInstall | ||
module Config | ||
@[YAML::Serializable::Options(emit_nulls: true)] | ||
class ConfigV1 < ConfigBase | ||
getter config_version : String? | ||
getter destination_cnf_dir : String? | ||
getter source_cnf_dir : String? | ||
getter manifest_directory : String? | ||
getter helm_directory : String? | ||
getter release_name : String? | ||
getter helm_repository : HelmRepository? | ||
getter helm_chart : String? | ||
getter helm_values : String? | ||
getter helm_install_namespace : String? | ||
getter container_names : Array(Container)? | ||
getter white_list_container_names : Array(String)? | ||
getter docker_insecure_registries : Array(String)? | ||
getter image_registry_fqdns : Hash(String, String?)? | ||
|
||
# Unused properties | ||
getter install_script : String? | ||
getter service_name : String? | ||
getter git_clone_url : String? | ||
getter docker_repository : String? | ||
|
||
# 5G related properties | ||
getter amf_label : String? | ||
getter smf_label : String? | ||
getter upf_label : String? | ||
getter ric_label : String? | ||
getter core : String? | ||
getter amf_service_name : String? | ||
getter mmc : String? | ||
getter mnc : String? | ||
getter sst : String? | ||
getter sd : String? | ||
getter tac : String? | ||
getter protectionScheme : String? | ||
getter publicKey : String? | ||
getter publicKeyId : String? | ||
getter routingIndicator : String? | ||
getter enabled : String? | ||
getter count : String? | ||
getter initialMSISDN : String? | ||
getter key : String? | ||
getter op : String? | ||
getter opType : String? | ||
getter type : String? | ||
getter apn : String? | ||
getter emergency : String? | ||
|
||
# Nested class for Helm Repository details | ||
class HelmRepository < ConfigBase | ||
getter name : String? | ||
getter repo_url : String? | ||
end | ||
|
||
# Nested class for Container details | ||
class Container < ConfigBase | ||
getter name : String? | ||
getter rollback_from_tag : String? | ||
getter rolling_update_test_tag : String? | ||
getter rolling_downgrade_test_tag : String? | ||
getter rolling_version_change_test_tag : String? | ||
end | ||
end | ||
end | ||
end |
97 changes: 97 additions & 0 deletions
97
src/tasks/utils/cnf_installation/transformer/config_transformer.cr
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
require "yaml" | ||
|
||
module CNFInstall | ||
module Config | ||
# REQUIRES FUTURE EXTENSION in case of new config format. | ||
enum ConfigVersion | ||
V1 | ||
V2 | ||
Latest = V2 | ||
end | ||
|
||
class ConfigTransformer | ||
@new_config : YAML::Any | ||
@old_config : ConfigBase | ||
@version : ConfigVersion | ||
|
||
# The initializer reads the config file twice to determine the config version | ||
# of @old_config, this cannot be removed due to later parsing to appropriate class. | ||
def initialize(old_config_path : String) | ||
tmp_content = File.read(old_config_path) | ||
yaml_content = YAML.parse(tmp_content).as_h | ||
|
||
# Automatic version detection to streamline the transformation | ||
@version = detect_version(yaml_content) | ||
if @version == ConfigVersion::Latest | ||
stdout_warning "Your config is the latest version." | ||
exit(1) | ||
end | ||
|
||
@new_config = YAML::Any.new({} of YAML::Any => YAML::Any) | ||
@old_config = parse_old_config(tmp_content) | ||
end | ||
|
||
# Serialize the transformed config to a string. | ||
def serialize_to_string : String | ||
YAML.dump(@new_config) | ||
end | ||
|
||
# Serialize the transformed config to a file and return the file path. | ||
def serialize_to_file(file_path : String) : String | ||
File.write(file_path, serialize_to_string) | ||
file_path | ||
end | ||
|
||
# Detects the config version. | ||
# REQUIRES FUTURE EXTENSION in case of new config format. | ||
private def detect_version(yaml_content : Hash(YAML::Any, YAML::Any)) : ConfigVersion | ||
version_value = yaml_content["config_version"]?.try(&.to_s) | ||
|
||
if version_value | ||
begin | ||
ConfigVersion.parse(version_value.upcase) | ||
rescue ex : Exception | ||
raise UnsupportedConfigVersionError.new(version_value) | ||
end | ||
else | ||
ConfigVersion::V1 | ||
end | ||
end | ||
|
||
# Parses the config to the correct class. | ||
# REQUIRES FUTURE EXTENSION in case of new config format. | ||
private def parse_old_config(tmp_content : String) : ConfigBase | ||
begin | ||
case @version | ||
when ConfigVersion::V1 | ||
ConfigV1.from_yaml(tmp_content) | ||
else | ||
raise UnsupportedConfigVersionError.new(@version) | ||
end | ||
rescue ex : YAML::ParseException | ||
# This is usually raised in case of unexpected YAML keys. | ||
stdout_failure "Failed to parse config: #{ex.message}." | ||
stdout_failure "Please check your YAML fields for correctness." | ||
exit(1) | ||
end | ||
end | ||
|
||
# Performs the transformation from V1 to V2. | ||
# REQUIRES FUTURE EXTENSION in case of new config format. | ||
def transform | ||
case @version | ||
when ConfigVersion::V1 | ||
@new_config = V1ToV2Transformation.new(@old_config.as(ConfigV1)).transform | ||
else | ||
raise UnsupportedConfigVersionError.new(@version) | ||
end | ||
end | ||
end | ||
|
||
class UnsupportedConfigVersionError < Exception | ||
def initialize(version : ConfigVersion | String) | ||
super "Unsupported configuration version: #{version.is_a?(ConfigVersion) ? version.to_s.downcase : version}" | ||
end | ||
end | ||
end | ||
end |
37 changes: 37 additions & 0 deletions
37
src/tasks/utils/cnf_installation/transformer/transformation_base.cr
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
module CNFInstall | ||
module Config | ||
# The rules need to be somewhat explicit, different approaches have been attempted | ||
# but due to crystals strict typing system they have not been viable/would be too complicated. | ||
# | ||
# In case of future extension, create a new transformation rules class (VxToVyTransformation), | ||
# This class should inherit the TransformationBase class and make use of process_data | ||
# function at the end of its transform function. | ||
class TransformationBase | ||
@new_config : YAML::Any | ||
|
||
def initialize(@old_config : ConfigV1) | ||
@new_config = YAML::Any.new({} of YAML::Any => YAML::Any) | ||
end | ||
|
||
# Recursively remove any empty hashes/arrays/values and convert data to YAML::Any. | ||
private def process_data(data : Hash | Array | String | Nil) : YAML::Any? | ||
case data | ||
when Array | ||
processed_array = data.map { |item| process_data(item) }.compact | ||
processed_array.empty? ? nil : YAML::Any.new(processed_array) | ||
when Hash | ||
processed_hash = Hash(YAML::Any, YAML::Any).new | ||
data.each do |k, v| | ||
processed_value = process_data(v) | ||
processed_hash[YAML::Any.new(k)] = processed_value unless processed_value.nil? | ||
end | ||
processed_hash.empty? ? nil : YAML::Any.new(processed_hash) | ||
when String | ||
YAML::Any.new(data) | ||
else | ||
nil | ||
end | ||
end | ||
end | ||
end | ||
end |
120 changes: 120 additions & 0 deletions
120
src/tasks/utils/cnf_installation/transformer/v1_to_v2_transformation.cr
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
module CNFInstall | ||
module Config | ||
# Rules for configV1 to configV2 transformation | ||
class V1ToV2Transformation < TransformationBase | ||
def transform : YAML::Any | ||
new_config_hash = { | ||
"config_version" => "v2", | ||
"common_parameters" => transform_common_parameters, | ||
"dynamic_parameters" => transform_dynamic_parameters, | ||
"deployments" => transform_deployments, | ||
} | ||
|
||
# Convert the entire native hash to stripped YAML::Any at the end. | ||
@new_config = process_data(new_config_hash).not_nil! | ||
end | ||
|
||
private def transform_common_parameters : Hash(String, Array(Hash(String, String | Nil)) | Array(String) | Hash(String, String | Nil)) | ||
common_params = {} of String => Array(Hash(String, String | Nil)) | Array(String) | Hash(String, String | Nil) | ||
|
||
common_params = { | ||
"white_list_container_names" => @old_config.white_list_container_names, | ||
"docker_insecure_registries" => @old_config.docker_insecure_registries, | ||
"image_registry_fqdns" => @old_config.image_registry_fqdns, | ||
"container_names" => transform_container_names, | ||
"five_g_parameters" => transform_five_g_parameters | ||
}.compact | ||
|
||
common_params | ||
end | ||
|
||
private def transform_container_names : Array(Hash(String, String | Nil)) | ||
if @old_config.container_names | ||
containers = @old_config.container_names.not_nil!.map do |container| | ||
{ | ||
"name" => container.name, | ||
"rollback_from_tag" => container.rollback_from_tag, | ||
"rolling_update_test_tag" => container.rolling_update_test_tag, | ||
"rolling_downgrade_test_tag" => container.rolling_downgrade_test_tag, | ||
"rolling_version_change_test_tag" => container.rolling_version_change_test_tag | ||
} | ||
end | ||
|
||
return containers | ||
end | ||
|
||
[] of Hash(String, String | Nil) | ||
end | ||
|
||
private def transform_dynamic_parameters : Hash(String, String | Nil) | ||
{ | ||
"source_cnf_dir" => @old_config.source_cnf_dir, | ||
"destination_cnf_dir" => @old_config.destination_cnf_dir | ||
} | ||
end | ||
|
||
private def transform_deployments : Hash(String, Array(Hash(String, String | Nil))) | ||
deployments = {} of String => Array(Hash(String, String | Nil)) | ||
|
||
if @old_config.manifest_directory | ||
deployments["manifests"] = [{ | ||
"name" => @old_config.release_name, | ||
"manifest_directory" => @old_config.manifest_directory | ||
}] | ||
elsif @old_config.helm_directory | ||
deployments["helm_directories"] = [{ | ||
"name" => @old_config.release_name, | ||
"helm_directory" => @old_config.helm_directory, | ||
"helm_values" => @old_config.helm_values, | ||
"namespace" => @old_config.helm_install_namespace | ||
}] | ||
elsif @old_config.helm_chart | ||
helm_chart_data = { | ||
"name" => @old_config.release_name, | ||
"helm_chart_name" => @old_config.helm_chart, | ||
"helm_values" => @old_config.helm_values, | ||
"namespace" => @old_config.helm_install_namespace | ||
} | ||
|
||
if @old_config.helm_repository | ||
helm_chart_data["helm_repo_name"] = @old_config.helm_repository.not_nil!.name | ||
helm_chart_data["helm_repo_url"] = @old_config.helm_repository.not_nil!.repo_url | ||
end | ||
|
||
deployments["helm_charts"] = [helm_chart_data] | ||
end | ||
|
||
deployments | ||
end | ||
|
||
private def transform_five_g_parameters : Hash(String, String | Nil) | ||
{ | ||
"core" => @old_config.core, | ||
"amf_label" => @old_config.amf_label, | ||
"smf_label" => @old_config.smf_label, | ||
"upf_label" => @old_config.upf_label, | ||
"ric_label" => @old_config.ric_label, | ||
"amf_service_name" => @old_config.amf_service_name, | ||
"mmc" => @old_config.mmc, | ||
"mnc" => @old_config.mnc, | ||
"sst" => @old_config.sst, | ||
"sd" => @old_config.sd, | ||
"tac" => @old_config.tac, | ||
"protectionScheme" => @old_config.protectionScheme, | ||
"publicKey" => @old_config.publicKey, | ||
"publicKeyId" => @old_config.publicKeyId, | ||
"routingIndicator" => @old_config.routingIndicator, | ||
"enabled" => @old_config.enabled, | ||
"count" => @old_config.count, | ||
"initialMSISDN" => @old_config.initialMSISDN, | ||
"key" => @old_config.key, | ||
"op" => @old_config.op, | ||
"opType" => @old_config.opType, | ||
"type" => @old_config.type, | ||
"apn" => @old_config.apn, | ||
"emergency" => @old_config.emergency | ||
} | ||
end | ||
end | ||
end | ||
end |