package OcToolkit;
use v5.16;
use strict;
use warnings;

our $VERSION = "1.01";

use JSON::PP;
use Text::Diff;
use Template;
use File::Slurp;
use File::Find::Rule;
use File::Path qw(make_path rmtree);
use MIME::Base64 qw(encode_base64 decode_base64);
use YAML qw(LoadFile);
use YAML::Safe;

use Data::Dumper;

sub new {
    my $class = shift;
    my $self  = {@_};
    
    $self->{tt}   = Template->new({INTERPOLATE  => 1});
    $self->{json} = JSON::PP->new;
    $self->{json}->convert_blessed();
    
    $self->{secretsDir}           = "secrets"               if not defined $self->{secretsDir};
    $self->{ocConfigFile}         = "oc_config.json"        if not defined $self->{ocConfigFile};
    $self->{templatesTTDir}       = "templates_tt"          if not defined $self->{templatesTTDir};
    $self->{templatesYamlDir}     = "templates_yaml"        if not defined $self->{templatesYamlDir};
    $self->{validationReportFile} = "validation_report.txt" if not defined $self->{validationReportFile};
    $self->{cliCommand}           = "oc"                    if not defined $self->{cloudCommand};;
    if((defined $self->{advanceFeatures}) && ($self->{advanceFeatures} =~ /kubectl/)){
         $self->{cliCommand} = "kubectl";
    }

    qx/mkdir tmp 2>&1/;
    qx/mkdir tmp\/$self->{secretsDir}  2>&1/;
    qx/mkdir tmp\/$self->{templatesTTDir} 2>&1/;
    
    if(not defined $self->{cluster}){
        my $ocConfigFiletext = read_file($self->{ocConfigFile});
        my $oCconfig = $self->{json}->utf8->decode($ocConfigFiletext);
        $self->{cluster} = $oCconfig->{project}->{default_cluster};
        $self->{cluster} = "unknown" if not defined $self->{cluster};
    }
    
    return bless $self, $class;
}

sub backup{
    my ($self, $instance) = @_;

    $self->{instance} = $instance;
    return if not defined $self->generateYaml();
    
    qx/mkdir backups 2>&1/;
    qx/mkdir backups\/$instance 2>&1/;
    
    $self->_loopDir($self->{config}->{templates_yaml_dir}, "*", "_backupInstance");
}

sub backupWholeOCProject{
    my ($self) = @_;

    qx/mkdir backups 2>&1/;
    qx/mkdir backups\/wholeProject 2>&1/;
    $self->_clearDir("backups/wholeProject");
    
    my $ocConfigFileText = read_file($self->{ocConfigFile});
    my $ocConfig = $self->{json}->utf8->decode($ocConfigFileText);
    
    my $ocResourceKinds = $ocConfig->{project}->{oc_resource_kinds};
    $ocResourceKinds = $self->{ocResourceKinds} if defined $self->{ocResourceKinds};
    my @ocResourceKindsArray = split(';', $ocResourceKinds);
    
    foreach my $ocResourceKind (@ocResourceKindsArray){
        my $ocResourceKinds = $ocResourceKind;
        if($ocResourceKind ne "Ingress" &&
           $ocResourceKind ne "StorageClass" &&
           $ocResourceKind ne "NetworkPolicy"
        ){
            $ocResourceKinds .= "s";
        }
        print "$ocResourceKinds:\n";
        my $text = qx/$self->{cliCommand} get $ocResourceKinds/;
        my @textArray = split('\n', $text);
        shift @textArray;
        foreach my $line (@textArray){
            my @lineArray = split(" ", $line);
            my $ocItem = $lineArray[0];
            print "kind: $ocResourceKind  item: $ocItem\n";
            qx/mkdir backups\/wholeProject\/$ocResourceKinds 2>&1/;
            eval { 
                my $ocItemJson = qx/$self->{cliCommand} get $ocResourceKind $ocItem -o json/;
                my $ocItemHash = $self->{json}->utf8->decode($ocItemJson);
                if((defined $self->{advanceFeatures}) && 
                   ($self->{advanceFeatures} =~ /removeClutter/) && 
                   (defined $self->{removeClutterBackup})){
                    my $subParams = {"ocKind" => $ocResourceKind, "ocName" => $ocItem};
                    $ocItemHash = $self->{removeClutterBackup}->($ocItemHash, $subParams);
                }
                my $yamlSaveObj = YAML::Safe->new->boolean("JSON::PP");
                my $yamlText = $yamlSaveObj->Dump($ocItemHash);
                $yamlText =~ s/---\n//;
                write_file("backups\/wholeProject\/$ocResourceKinds/$ocItem".".yaml", $yamlText);
            };
            if($@){
                # if error occured take yaml without calling '->removeClutterBackup()'
                print "Removing clutter has failed, writing yaml file: $ocResourceKinds/$ocItem.yaml without removing clutter.\n";
                my $yamlText = qx/$self->{cliCommand} get $ocResourceKind $ocItem -o yaml/;
                write_file("backups\/wholeProject\/$ocResourceKinds/$ocItem".".yaml", $yamlText);
            }
        }
    }
}

sub convertYamlToTTExtention{
    my ($self, $yamlToTTconvertDir) = @_;

    $self->_loopDir($yamlToTTconvertDir, "yaml", "_convertYamlToTTExtention",
                    {_convertYamlToTTExtention => {yamlToTTconvertDir => $yamlToTTconvertDir}});
}

sub delete{
    my ($self, $instance) = @_;
    
    $self->{instance} = $instance;
    return if not defined $self->generateYaml();

    my $cluster   = $self->{config}->{cluster};
    my $namespace = $self->{config}->{namespace};
    print "\nDeleting Openshift components from instance: '$instance' in cluster: '$cluster' in namespace: '$namespace'\n";
    $self->_loopDir($self->{config}->{templates_yaml_dir}, "yaml", "_deleteOc");
    
    return;
}

sub generateYaml{
    my ($self) = @_;

    print "Instance is missing.\n" and return if not defined $self->{instance};

    $self->_selectInstanceSpecificSecrets();
    $self->{config} = $self->_generateConfig();
    $self->_removeInitFromComponentDirs() if $self->{omit} =~ /init/;
    $self->_loopDir($self->{config}->{templates_tt_dir}, "*", "_selectClusterSpecificTemplates");
    $self->_clearDir($self->{config}->{templates_yaml_dir});
    $self->_loopDir($self->{config}->{templates_tt_dir}, "tt", "_generateYaml");
    $self->_loopDir($self->{config}->{templates_tt_dir}, "*", "_removeClusterSpecificTemplates");   
    $self->_removeInstanceSpecificSecrets();
    
    return 1;
}

sub install{
    my ($self, $instance) = @_;

    $self->{instance} = $instance;
    return if not defined $self->generateYaml();

    my $cluster   = $self->{config}->{cluster};
    my $namespace = $self->{config}->{namespace};
    print "\nInstalling Openshift components for instance: '$instance' in cluster: '$cluster' in namespace: '$namespace'\n";
    # $self->_loopDir($self->{config}->{templates_tt_dir}, "tt", "_callOc", {_callOc => {{param1 : "value1"}, {param2 : "value2"} }}); # sent custom params
    $self->_loopDir($self->{config}->{templates_yaml_dir}, "yaml", "_callOc") if $self->{omit} !~ /oc/;
    
    print qq^\n\nTo get build and deployment status run: $self->{cliCommand} get pods | grep Running | grep 'build\|deploy'\n\n^;
    
    return;
}

sub setParams{
    my ($self, $params) = @_;

    if (@_ == 2) {
        $self->{advanceFeatures}        = $params->{advanceFeatures}      if defined $params->{advanceFeatures};
        $self->{clusterBaseAddress}     = $params->{clusterBaseAddress}   if defined $params->{clusterBaseAddress};
        $self->{cluster}                = $params->{cluster}              if defined $params->{cluster};
        $self->{ocConfigFile}           = $params->{ocConfigFile}         if defined $params->{ocConfigFile};
        $self->{host}                   = $params->{host}                 if defined $params->{host};
        $self->{ocResourceKinds}        = $params->{ocResourceKinds}      if defined $params->{ocResourceKinds};
        $self->{componentDirs}          = $params->{componentDirs}        if defined $params->{componentDirs};
        $self->{namespace}              = $params->{namespace}            if defined $params->{namespace};
        $self->{projectName}            = $params->{projectName}          if defined $params->{projectName};
        $self->{omit}                   = $params->{omit}                 if defined $params->{omit};
        $self->{urlPrefix}              = $params->{urlPrefix}            if defined $params->{urlPrefix};
        $self->{clusterIpRange}         = $params->{clusterIpRange}       if defined $params->{clusterIpRange};
        $self->{secretsDir}             = $params->{secretsDir}           if defined $params->{secretsDir};
        $self->{sortType}               = $params->{sortType}             if defined $params->{sortType};
        $self->{templatesTTDir}         = $params->{templatesTTDir}       if defined $params->{templatesTTDir};
        $self->{yamlToTTconvertDir}     = $params->{yamlToTTconvertDir}   if defined $params->{yamlToTTconvertDir};
        $self->{specificYamlFile}       = $params->{specificYamlFile}     if defined $params->{specificYamlFile};
        $self->{templatesYamlDir}       = $params->{templatesYamlDir}     if defined $params->{templatesYamlDir};
        $self->{validationReportFile}   = $params->{validationReportFile} if defined $params->{validationReportFile};
    }
    return;
}

sub upgrade{
    my ($self, $instance) = @_;
    
    $self->validate($instance);
    
    my $validationReport      = read_file($self->{validationReportFile});
    my @validationReportLines = split /\n/, $validationReport;
    foreach my $line (@validationReportLines){
        my @items = split /;/, $line;
        if($items[-1] eq "MODIFIED"){
            my $pathAndFile = $items[0];
            my $ocKind      = $items[1];
            my $ocName      = $items[2];
            if($ocKind eq "PersistentVolumeClaim"){
                print "You are trying to update PersistentVolumeClaim.
Please make sure that all PODs that use this Persisten Volume are turned down before update.
If update operation start hanging at this step press ctrl+c to abort. \n\n";
            }
            print "Upgrading ocKind:$ocKind, ocName: $ocName from $pathAndFile\n";
            qx/$self->{cliCommand} delete $ocKind $ocName/;
            qx/$self->{cliCommand} create -f .\/$self->{config}->{templates_yaml_dir}\/$pathAndFile/;
        }
    }
}

sub validate{
    my ($self, $instance) = @_;

    qx/> $self->{validationReportFile}/; # clear file
    $self->{instance} = $instance;
    return if not defined $self->generateYaml();
    
    my $cluster   = $self->{config}->{cluster};
    my $namespace = $self->{config}->{namespace};
    print "\nValidating Openshift components for instance: '$instance' in cluster: '$cluster' in namespace: '$namespace'\n";
    $self->_loopDir($self->{config}->{templates_yaml_dir}, "yaml", "_validateInstance");
}

sub _backupInstance{
    my ($self, $params) = @_;

    my $dir          = $params->{dir};
    my $templateName = $params->{templateName};

    my $instance = $self->{config}->{instance};
    qx/mkdir backups\/$instance\/$dir 2>&1/;
    my $pathToYamlFile = "$self->{config}->{templates_yaml_dir}/$dir/$templateName".".yaml";
    my $templateData = LoadFile($pathToYamlFile);
    
    my $yamlText;
    if((defined $self->{advanceFeatures}) && 
       ($self->{advanceFeatures} =~ /removeClutter/) && 
       (defined $self->{removeClutterBackup})){
        print "$self->{cliCommand} get $templateData->{kind} $templateData->{metadata}->{name} -o json\n";
        my $ocJson = qx/$self->{cliCommand} get $templateData->{kind} $templateData->{metadata}->{name} -o json/;
        my $ocHash  = $self->{json}->utf8->decode($ocJson); 
        my $subParams = {"ocKind" => $templateData->{kind}, "ocName" => $templateData->{metadata}->{name}};
        $ocHash = $self->{removeClutterBackup}->($ocHash, $subParams);
        my $yamlSaveObj = YAML::Safe->new->boolean("JSON::PP");
        $yamlText = $yamlSaveObj->Dump($ocHash);
        $yamlText =~ s/---\n//;
    }else{
         print "$self->{cliCommand} get $templateData->{kind} $templateData->{metadata}->{name} -o yaml\n";
         $yamlText = qx/$self->{cliCommand} get $templateData->{kind} $templateData->{metadata}->{name} -o yaml/;
    }

    write_file("backups\/$instance\/$dir/$templateName".".yaml", $yamlText);     
}

sub _callOc{
    my ($self, $params) = @_;
    
    my @funcName = split /::/, (caller(0))[3];
    my $dir          = $params->{dir};
    my $templateName = $params->{templateName};
    my $customParams = $params->{params}->{$funcName[-1]};

    return if (defined $self->{componentIsAllowed}) && (not $self->{componentIsAllowed}->($templateName, $dir, $self->{cluster}, $self->{instance}));

    my $templateNameYaml = $templateName.".yaml";
    
    my $yamlData = LoadFile("$self->{config}->{templates_yaml_dir}/$dir/$templateNameYaml");
    if($yamlData->{kind} eq "CronJob"){
        print "$self->{cliCommand} apply -f templates_yaml/$dir/$templateNameYaml\n";
        qx/$self->{cliCommand} apply -f .\/$self->{config}->{templates_yaml_dir}\/$dir\/$templateNameYaml/;
    }else{
        print "$self->{cliCommand} create -f templates_yaml/$dir/$templateNameYaml\n";
        qx/$self->{cliCommand} create -f .\/$self->{config}->{templates_yaml_dir}\/$dir\/$templateNameYaml/;
    }
}

sub _clusterDirExist{
    my ($self, $templateName) = @_;

    my @allowedClustersArr = split(';', $self->{config}->{allowed_clusters});
    foreach my $myCluster (@allowedClustersArr){
        return 1 if ($templateName eq $myCluster) && ($self->{cluster} eq $myCluster);       
    }
    return 0;
}

sub _clearDir{
    my ($self, $dir) = @_;

    rmtree $dir;
    make_path $dir;
}

sub _convertYamlToTTExtention{
    my ($self, $params) = @_;

    my @funcName           = split /::/, (caller(0))[3];
    my $dir                = $params->{dir};
    my $templateName       = $params->{templateName};
    my $customParams       = $params->{params}->{$funcName[-1]};
    my $yamlToTTconvertDir = $customParams->{yamlToTTconvertDir};
    
    my $path = "$yamlToTTconvertDir/$dir";
    qx/mv $path\/$templateName\.yaml $path\/$templateName\.tt 2>&1/;
}

sub _deleteOc{
    my ($self, $params) = @_;

    my $dir          = $params->{dir};
    my $templateName = $params->{templateName};

    my $data = LoadFile("$self->{config}->{templates_yaml_dir}/$dir/$templateName".".yaml");
    print "$self->{cliCommand} delete $data->{kind} $data->{metadata}->{name}\n";
    qx/$self->{cliCommand} delete $data->{kind} $data->{metadata}->{name}/;
}

sub _generateYaml{
    my ($self, $params) = @_;

    my @funcName = split /::/, (caller(0))[3];
    my $dir          = $params->{dir};
    my $templateName = $params->{templateName};
    my $customParams = $params->{params}->{$funcName[-1]};

    return if $dir =~ /init/ && $self->{omit} =~ /init/;
    return if (defined $self->{specificYamlFile}) && ($templateName !~ $self->{specificYamlFile});
    return if (defined $self->{componentIsAllowed}) && (not $self->{componentIsAllowed}->($templateName, $dir, $self->{cluster}, $self->{instance}));
    
    my $yamlText;
    my $templatesYamlPath = "$self->{config}->{templates_yaml_dir}/$dir";
    my $templatesTTPath   = "$self->{config}->{templates_tt_dir}/$dir";
    make_path $templatesYamlPath or die("Failed to create path: $templatesYamlPath") if !-d $templatesYamlPath;
    $self->{tt}->process("$templatesTTPath/$templateName\.tt", $self->{config}, \$yamlText);    
    
    my $yamlHash;
    eval { $yamlHash = Load($yamlText);};
    if($@){
        print "Error occured during conversion to yaml in: $dir  $templateName\n", Dumper($@), "\n";
        return;
    }
    
    return if (defined $self->{ocResourceKinds}) && ($self->{ocResourceKinds} !~ $yamlHash->{kind});

    write_file("$templatesYamlPath/$templateName\.yaml", $yamlText);
    $self->{config}->{ip_last_number}++ if $yamlHash->{kind} eq "Service";
}

sub _generateConfig{
    my ($self) = @_;

    my $ocConfigJson = read_file($self->{ocConfigFile});
    my $config->{oc_config} = $self->{json}->utf8->decode($ocConfigJson);

    $config = $self->{addFlagValuesToConfig}->($config) if defined $self->{addFlagValuesToConfig};
    
    # set/generate instance specific names
    foreach my $entry (keys %{$config->{oc_config}->{instance_specific_name}}){
        $config->{oc_config}->{instance_specific_name}->{$entry} .= "-$self->{instance}";
    }

    $config->{allowed_clusters}  = $config->{oc_config}->{project}->{allowed_clusters};
    if(not defined $config->{allowed_clusters}){
        print "Warning: oc_config->project->allowed_clusters json node is empty. Marking 'allowed_clusters' as '$self->{cluster}'\n";
        $config->{allowed_clusters} = $self->{cluster};
    }else{
        print "Warning: Unknown cluster $self->{cluster}\n" if $config->{allowed_clusters} !~ $self->{cluster};
    }
    
    $config->{cluster_ip_range} = $config->{oc_config}->{project}->{cluster_ip_range};
    $config->{cluster_ip_range} = $self->{clusterIpRange}       if defined $self->{clusterIpRange};
    $config->{project_name}     = $config->{oc_config}->{project}->{name};
    $config->{project_name}     = $self->{projectName}          if defined $self->{projectName};
    $config->{host}             = $config->{oc_config}->{project}->{host};
    $config->{host}             = $self->{host}                 if defined $self->{host};
    $config->{namespace}        = $self->_getCurrentProject();
    $config->{namespace}        = $self->{namespace}            if defined $self->{namespace};
    # default component dirs are set here, dirs in 'templates_tt' not set as default will be omitted
    $config->{component_dirs}   = $config->{oc_config}->{project}->{component_dirs};
    $config->{component_dirs}   = $self->{componentDirs}        if defined $self->{componentDirs};
    # component dirs can contains numbers e.g.: '50-solr' so regexp match is used => 
    # separate 'init' components in order to avoid false matches (e.g.: 20-init-api vs 50-api when 'api' searched)
    my @componentDirs                  = split(';', $config->{component_dirs});
    my $standardComponentDirs = "";
    my $initComponentDirs = "";
    foreach my $componentDir (@componentDirs){
        if($componentDir =~ /init/){
            $initComponentDirs     .= $componentDir.";";
        }else{
            $standardComponentDirs .= $componentDir.";";
        }
    }
    chop($initComponentDirs);
    chop($standardComponentDirs); 
    $config->{init_component_dirs}        = $initComponentDirs;
    $config->{standard_component_dirs}    = $standardComponentDirs;
    $config->{oc_resource_kinds}          = $config->{oc_config}->{project}->{oc_resource_kinds};
    $config->{oc_resource_kinds}          = $self->{ocResourceKinds} if defined $self->{ocResourceKinds};
    $config->{templates_yaml_dir}         = $self->{templatesYamlDir};
    $config->{templates_tt_dir}           = $self->{templatesTTDir};
    $config->{cluster_camelcase}          = $self->{cluster};
    $config->{cluster}                    = lc $self->{cluster};
    $config->{instance}                   = lc $self->{instance};
    $config->{instance_capitalized_first} = ucfirst $config->{instance};
    $config->{url_prefix}                 = $self->{urlPrefix} if defined $self->{urlPrefix};
    $config->{cluster_base_address}       = $self->{clusterBaseAddress} if defined $self->{clusterBaseAddress}; 
    
    my $componentConfigNodes  = $self->_getComponentConfigNodes($config);
    foreach my $componentConfNode (@$componentConfigNodes){
        # set/generate default urls
        if((defined $config->{oc_config}->{instance_specific_data}->{$componentConfNode}) && 
           (ref($config->{oc_config}->{instance_specific_data}->{$componentConfNode}) eq 'HASH')){
            foreach my $instanceKey (keys %{$config->{oc_config}->{instance_specific_data}->{$componentConfNode}}){
                if(not defined $config->{oc_config}->{instance_specific_data}->{$componentConfNode}->{$instanceKey}->{url}){                    
                    my $componentNameKebab = $componentConfNode;
                    $componentNameKebab    =~ s/_/\-/;
                    my $lcInstanceKey   = lc $instanceKey;
                    my $url = "";
                    if(defined $self->{generateUrl}){
                        $url = $self->{generateUrl}->($config->{url_prefix}, 
                                                      $config->{project_name},
                                                      $componentNameKebab,
                                                      $lcInstanceKey,
                                                      $config->{cluster_base_address},
                                                      $config->{host});
                    }
                    $config->{oc_config}->{instance_specific_data}->{$componentConfNode}->{$instanceKey}->{url} = $url;  
                }
            }
        }
        # select instance specific data
        if(defined $config->{oc_config}->{instance_specific_data}->{$componentConfNode}->{$self->{instance}}){
            $config->{oc_config}->{instance_specific_data}->{$componentConfNode} = 
                $config->{oc_config}->{instance_specific_data}->{$componentConfNode}->{$self->{instance}};
        }
    }
    # in worse case 36 available IP addresses(see _generateYaml), make number smaller if more needed(max is 256)
    $config->{ip_last_number} = int(rand(220));
    
    print "'Info: component_dirs' parameter is missing\n"   if not defined $config->{component_dirs};
    print "'Info: cluster_ip_range' parameter is missing\n" if not defined $config->{cluster_ip_range};
    print "'Info: host' parameter is missing\n"             if not defined $config->{host};

    $self->_getSecrets($config);
    
    return $config;
}


sub _getSecrets{
    my ($self, $config) = @_;

    for my $dirFileName (File::Find::Rule->file()->name("*")->in($self->{secretsDir})) {
        my @dirFileNameArr = split('/', $dirFileName);
        if(scalar @dirFileNameArr == 2){
            my $secretText = read_file($dirFileName);
            $secretText =~ s/\n//g;
            $secretText =~ s/\r//g;
            my $secretTextBase64 = encode_base64($secretText);
            $secretTextBase64 =~ s/\n//g;
            $secretTextBase64 =~ s/\r//g;
            $config->{secrets}->{$dirFileNameArr[1]} = $secretTextBase64;
        }
    }
    # instance specific secrets e.g.: secrets/instance/test/my-secret.txt
    for my $dirFileName (File::Find::Rule->file()->name("*")->in("$self->{secretsDir}/instance/$self->{instance}")) {
        my @dirFileNameArr = split('/', $dirFileName);
        if(scalar @dirFileNameArr == 4){
            my $secretText = read_file($dirFileName);
            $secretText =~ s/\n//g;
            $secretText =~ s/\r//g;
            my $secretTextBase64 = encode_base64($secretText);
            $secretTextBase64 =~ s/\n//g;
            $secretTextBase64 =~ s/\r//g;
            $config->{secrets}->{instance_specific}->{$dirFileNameArr[3]} = $secretTextBase64;
        }
    }
}

sub _getComponentConfigNodes{
    my ($self, $config) = @_;
    
    my @componentConfigNodes;
    my $componentConfigNodesString;
    if((defined $config->{oc_config}) && (defined $config->{oc_config}->{instance_specific_data})){
        foreach my $componentConfigNode (keys %{$config->{oc_config}->{instance_specific_data}}){
            push @componentConfigNodes, $componentConfigNode;
        }
    }

    return \@componentConfigNodes;
}

sub _getCurrentProject{
    my ($self) = @_; 

    my $projectCmdLine    = qx/$self->{cliCommand} config current-context/;
    my @projectCmdLineArr = split('/', $projectCmdLine);
    my $project = $projectCmdLineArr[0];
    $project = "unknown" if not defined $project;

    return $project;
}

sub _loopDir {
    my ($self, $dirToLoop, $extention, $injectedSubName, $params) = @_;  

    my $templates = {};
    for my $dirFileName (File::Find::Rule->file()->name("*.$extention")->in($dirToLoop)) {
        $dirFileName =~ s/$dirToLoop\///;
        my @dirFile  = split('\/', $dirFileName);
        my @dir      = split('\-', $dirFile[0]);
        my @file     = split('\-', $dirFile[1]);
        # e.g: $templates->{30-solr}->{dirNumber}               = 30 # in order so sort as integer
        #                           ->{20-build-config-solr.tt} = 20
        $templates->{$dirFile[0]}->{dirNumber}   = $dir[0];
        $templates->{$dirFile[0]}->{$dirFile[1]} = $file[0];
    }

    my @dirArray;
    if((defined $self->{sortType}) && ($self->{sortType}) eq "alphabetic"){
        foreach my $dir (sort { lc $templates->{$a}->{dirNumber} cmp lc $templates->{$b}->{dirNumber} } keys %{$templates}){
            push @dirArray, $dir;
        }
    }else{
        # numeric
        no warnings 'numeric';
        foreach my $dir (sort { $templates->{$a}->{dirNumber} <=> $templates->{$b}->{dirNumber} } keys %{$templates}){
            push @dirArray, $dir;
        }
    }
    @dirArray = reverse @dirArray if $injectedSubName eq "_deleteOc";

    foreach my $dir (@dirArray){
        print "\nInstalling components from: $dir:\n" if $injectedSubName eq "_callOc";
        
        my @fileArray;
        if((defined $self->{sortType}) && ($self->{sortType}) eq "alphabetic"){
            foreach my $file (sort { lc $templates->{$dir}->{$a} cmp  lc $templates->{$dir}->{$b} } keys %{$templates->{$dir}}){
                push @fileArray, $file;
            }
        }else{
            # numeric
            no warnings 'numeric';
            foreach my $file (sort { $templates->{$dir}->{$a} <=> $templates->{$dir}->{$b} } keys %{$templates->{$dir}}){
                push @fileArray, $file;
            }
        }
        @fileArray = reverse @fileArray if $injectedSubName eq "_deleteOc";

        foreach my $file (@fileArray){
            # when calling '_generateYaml' skip cluster directories defined in oc_project.json->project->allowed_clusters,
            # cluster specific tt files are already copied to the component directory
            if((($injectedSubName eq "_generateYaml") && ($self->{config}->{allowed_clusters} =~ /$file/)) ||
                 $file eq "dirNumber"                                                                      || 
                 $self->_skipComponent($dir)) {
                next;
            }
            my @fileArr = split('\.', $file);            
            if(($injectedSubName eq "_generateYaml") && (not defined $fileArr[1]) && ($self->{config}->{allowed_clusters} !~ /$file/)){
                print "Warning : Unknown cluster: $file\n";
            }
            my $injectedSub = \&$injectedSubName;
            $injectedSub->($self, {dir => $dir, templateName => $fileArr[0], params => $params});
        }
    }
}

sub _removeClusterSpecificTemplates{
    my ($self, $params) = @_;

    return if (not defined $self->{advanceFeatures}) || ($self->{advanceFeatures} !~ /multipleClusters/);

    my $dir          = $params->{dir};
    my $templateName = $params->{templateName};
    
    if($self->_clusterDirExist($templateName)){
        qx/rm templates_tt\/$dir\/*.tt 2>&1/;
        qx/cp tmp\/templates_tt\/*.tt templates_tt\/$dir\/ 2>&1/;
        qx/rm tmp\/templates_tt\/*.tt  2>&1/;
    }
}

sub _removeInitFromComponentDirs{
    my ($self) = @_;
    
    my @componentsYamlDirArray       = split(';', $self->{config}->{component_dirs});
    @componentsYamlDirArray          = (grep {$_ !~ /init/} @componentsYamlDirArray);
    $self->{config}->{component_dirs} = join( ';', @componentsYamlDirArray);
}

sub _removeInstanceSpecificSecrets{
    my ($self) = @_;  
    
    return if (not defined $self->{advanceFeatures}) || ($self->{advanceFeatures} !~ /multipleClusters/);
    
    qx/rm $self->{secretsDir}\/* 2>&1/;
    qx/cp tmp\/$self->{secretsDir}\/* $self->{secretsDir}\/ 2>&1/;
    qx/rm tmp\/$self->{secretsDir}\/* 2>&1/;
}

sub _selectClusterSpecificTemplates{
    my ($self, $params) = @_;

    return if (not defined $self->{advanceFeatures}) || ($self->{advanceFeatures} !~ /multipleClusters/);
    
    my $dir          = $params->{dir};
    my $templateName = $params->{templateName};
    
    if($self->_clusterDirExist($templateName)){
        qx/cp templates_tt\/$dir\/*.tt tmp\/templates_tt\/ 2>&1/;
        qx/cp templates_tt\/$dir\/$self->{cluster}\/*.tt templates_tt\/$dir\//;
    }
}

sub _selectInstanceSpecificSecrets{
    my ($self) = @_;  
    
    return if (not defined $self->{advanceFeatures}) || ($self->{advanceFeatures} !~ /multipleClusters/);
    
    qx/cp $self->{secretsDir}\/* tmp\/$self->{secretsDir}\/ 2>&1/;
    qx/cp $self->{secretsDir}\/$self->{cluster}\/* $self->{secretsDir}\/ 2>&1/;
}

sub _skipComponent{
    my ($self, $dir) = @_;

    # ignore skiping if called by this flag
    return 0 if defined $self->{yamlToTTconvertDir};
    
    my @installComponents = [];
    if($dir =~ /init/){
        if(defined $self->{config}->{init_component_dirs}){
            @installComponents = split(';', $self->{config}->{init_component_dirs});
        }
        foreach my $installComponent (@installComponents){
            return 0 if $dir =~ /$installComponent/;
        }  
    }else{
        if(defined $self->{config}->{standard_component_dirs}){
            @installComponents = split(';', $self->{config}->{standard_component_dirs});
        }
        foreach my $installComponent (@installComponents){
            return 0 if $dir =~ /$installComponent/;
        }    
    }

    return 1;
}

sub _validateInstance{
    my ($self, $params) = @_;
    
    my @funcName = split /::/, (caller(0))[3];
    my $dir          = $params->{dir};
    my $templateName = $params->{templateName};
    
    my $templateNameYaml = $templateName.".yaml";
    my $yamlData         = LoadFile("$self->{config}->{templates_yaml_dir}/$dir/$templateNameYaml");
    my $ocName           = $yamlData->{metadata}->{name};
    my $ocKind           = $yamlData->{kind};
    my $ocJson           = qx/$self->{cliCommand} get $ocKind $ocName -o json/;
    my $ocHash = {};
    eval { 
        $ocHash  = $self->{json}->utf8->decode($ocJson); 
        if(($ocKind eq "Secret") && (ref($ocHash->{data}) eq 'HASH')){
            foreach my $key (keys %{$ocHash->{data}}){
                my $secret = decode_base64($ocHash->{data}->{$key});
                $secret =~ s/\n//g;
                $secret =~ s/\r//g;
                $ocHash->{data}->{$key} = $secret;
            }
        }
    };
    print Dumper($@) if $@;
    
    my $templateYamlText = read_file("$self->{config}->{templates_yaml_dir}/$dir/$templateNameYaml");
    my $yamlObj = YAML::Safe->new->boolean("JSON::PP");
    my $templateHash = $yamlObj->Load($templateYamlText);

    my $subParams = {"dir" => $dir, "templateName" => $templateName, "ocKind" => $ocKind, "ocName" => $ocName};
    $ocHash       = $self->{removeClutter}->($ocHash, $subParams)       if defined $self->{removeClutter};
    $templateHash = $self->{removeClutter}->($templateHash, $subParams) if defined $self->{removeClutter};
    
    if($ocKind eq "Secret"){
        if((defined $templateHash->{data}) && (ref($templateHash->{data}) eq 'HASH')){
            foreach my $key (keys %{$templateHash->{data}}){
                my $dataBase64Encoded = $templateHash->{data}->{$key};
                my $dataBase64Decoded = decode_base64($dataBase64Encoded);
                $dataBase64Decoded =~ s/\n//g;
                $dataBase64Decoded =~ s/\r//g;
                $templateHash->{data}->{$key} = $dataBase64Decoded;
            }
        }
    }
    
    $ocJson          =  $self->{json}->utf8->pretty->canonical->encode($ocHash);
    my $templateJson =  $self->{json}->utf8->pretty->canonical->encode($templateHash);
    my @ocJsonArr       = split /\n/, $ocJson;
    my @templateJsonArr = split /\n/, $templateJson;
    my $diff = diff \@ocJsonArr, \@templateJsonArr, { STYLE => "Table", CONTEXT => 0 };
    if(($ocKind eq "Secret") && ($diff ne "")){
        $diff = "+---+-----------------------------------------------------+---+---------------------------------+
*   |           different secret, not displayed           *   | different secret, not displayed |
+---+-----------------------------------------------------+---+---------------------------------+
";
    }
    $diff =~ s/\\ No newline at end of file\s//g;
   
    my $diffStatus;
    if((defined $diff) && ($diff ne "")){
        $diffStatus = "MODIFIED";
    }else{
         $diffStatus = "OK";
    }
    my $line = "$dir/$templateNameYaml;$ocKind;$ocName;$diffStatus\n";
    print $line;
    print $diff if $diff ne "";
    write_file($self->{validationReportFile}, {append => 1}, $line);
}

1;

__END__

=encoding utf-8

=head1 NAME

OcToolkit - Open Cloud Toolkit

=head1 SYNOPSIS

    use OcToolkit;
    
    my $ocObj = OcToolkit->new( 
                            advanceFeatures       => $advanceFeatures,
                            clusterBaseAddress    => $clusterBaseAddress,
                            cluster               => $cluster,
                            ocConfigFile          => $ocConfigFile,
                            host                  => $host,
                            ocResourceKinds       => $ocResourceKinds,
                            componentDirs         => $componentDirs,
                            namespace             => $namespace,
                            projectName           => $projectName,
                            omit                  => $omit,
                            urlPrefix             => $urlPrefix,
                            clusterIpRange        => $clusterIpRange,
                            secretsDir            => $secretsDir,
                            sortType              => $sortType,
                            templatesTTDir        => $templatesTTDir,
                            yamlToTTconvertDir    => $yamlToTTconvertDir,
                            specificYamlFile      => $specificYamlFile,
                            templatesYamlDir      => $templatesYamlDir,
                            addFlagValuesToConfig => \&addFlagValuesToConfig,
                            componentIsAllowed    => \&componentIsAllowed,
                            generateUrl           => \&generateUrl,
                            removeClutter         => \&removeClutter,
                            removeClutterBackup   => \&removeClutterBackup);
    $ocObj->install('test');
    $ocObj->validate('test');
    $ocObj->update('test');
    $ocObj->backup('prod');
    $ocObj->delete('dev');

=head1 DESCRIPTION

Helm like tool for Openshift and Kubernetes with multi cluster support.
See https://gitlab.com/code7143615/octoolkit/-/blob/master/README.md how to use this library in ocToolkit.pl script
and use it as 'Helm-like' command line tool

=head1 LICENSE

Copyright (C) John Summers.

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.

=head1 AUTHOR

John Summers E<lt>devp2000a@gmail.comE<gt>

=cut

=head1 SCRIPT TO RUN IT

#!/usr/bin/perl

use strict;
use warnings;

# search for Module in same directory where this script is
use FindBin;
use File::Spec;
use lib File::Spec->catdir($FindBin::Bin);

use Getopt::Std;
use OcToolkit;
use Data::Dumper;

my $help = "Install/uninstall/backup/validate/upgrade instances and they components into/from Openshift/Kubernetes cluster
Put your templates in 'templates_tt' dir located in current dir. Every component should have own dir(10-api,20-solr,...) inside of it.
Write your templates with Template Toolkit(https://template-toolkit.org) templating engine.
Put your config into oc_config.json. Located in current dir.
   'instance_specific_data' : put your instance specific data in this json node, see example belove and in git
   'instance_specific_name' : instance string will be automatically added at the end of every entry, see example belove and in git
   'project': project specifc data
   oc_config.json root data are fix for every instance
  
Flags description:
  -A cluster base address, e.g.: 'apps.clusterintern' or 'apps.clusterpublic', used in default route url generation
  -a advance features:
    '-a multipleClusters'  if cluster specific templates are required, put them into nested dir
                           e.g.: templates_tt/50-api/clusterIntern/50-deployment-config-api.tt, 
                           ocToolkit will automatically select corresponding templates
                           (currently this flag is selected by default)
     '-a kubectl'          use Kubernetes 'kubectl' instead of default Openshift 'oc' command
     '-a removeClutter'    remove clutter yaml fields during backup
    Use multiple: -a 'multipleClusters;kubectl;removeClutter'
  -b instance name(s), backups specific instance(s) sorted by components e.g.: -b 'dev;test' or -b all to backup whole project(unsorted) 
     if '-a multipleClusters' and not '-b all' is used then '-c' flag is mandatory
  -c cluster name e.g.: 'clusterIntern','clusterPublic'... Default is defined in oc_config.json->project->default_cluster. 
     You should be logged in into corresponding cluster
     If cluster label in url should be different then cluster name, use '-A' flag to set custom cluster url label
  -C config file, default: 'oc_config.json'
  -d instanceName(s), deletes instances from logged in project e.g.: '-d test', see -i documentation
  -h help, prints this help
  -H host, used in default route url generation
  -i instance name(s) e.g.: installs given instances e.g.: 'test;prod'
  -k use(install/delete/validate/upgrade/generate yamls/backup) only specific Openshift/Kubernetes resource kind(s) 
     e.g.: -k 'DeploymentConfig;Service;Route' Default is defined in oc_config.json->project->oc_resource_kinds
  -m component directory names e.g.: 'init-project;init-api;init-gateway;solr;api;gateway;public-ui;admin-ui;swagger;cron-jobs'
     omit flag or use '-m all' to select all defined in oc_config.json->project->component_dirs
  -n openshift project namespace e.g.: 'myNameSpace' if not set, current oc project name will be used as default namespace
  -N project name, used in default route url generation
  -o 'init', all directories which name string includes 'init' will be omitted,
           useful if you like to preserve data volumes during install/delete operations
     'oc', only yaml files will be generated
  -p url prefix, adds prefix to all route urls, useful when running in Openshift sandbox in oder to avoid network routes conflicts
  -r openshift cluster IP range(first three numbers) e.g.: '112.20.14', last number will be randomly generated
  -s use this flag to change secrets directory. Default is 'secrets'
  -S 'numeric' or 'alphabetic', dirs and files sort type(relevant for running order), default is 'numeric'
  -t set custom 'templates_tt' directory
  -T directoryName, convert '.yaml' files extension into '.tt' extention inside of given directory
  -u instanceName(s), runs validation, creates 'validation_report.txt' und then runs upgrade of components that are modified,
     e.g.: -u test
  -v instanceName(s), validates given instance(s) between template version and Openshift version in cloud e.g.: -v test, 
     report is written in 'validation_report.txt' file
  -y use(install/delete/validate/upgrade/generate yamls) only for yaml files that includes given substring
  -Y set custom 'templates_yaml' directory
  

  see '-a multipleClusters' flag
  oc_config.json magic nodes:
      'instance_specific_data': instance will be automatically selected, e.g.: for json node 'instance_specific_data.api.test.limits.memory'  
                                you can access instance specific data in tt template by 
                                [% oc_config.instance_specific_data.api.limits.memory %] if current instance is 'test' 
      'instance_specific_name': '-instanceName' will be added at the end of each value, e.g.: if you have json node 'instance_specific_name.api'
                                with value 'my-api' then by accessing [% oc_config.instance_specific_name.api %] in tt template, in yaml file will be 
                                written 'my-api-test' if current instance is 'test
  see more examples in git: https://gitlab.com/code7143615/octoolkit/-/tree/master
  
  Examples: 
        # install 'dev' and 'test' instances in 'clusterIntern' cluster
        # you shoud be logged in in same cluster that you specified in '-c' flag
        ocToolkit -c clusterIntern -i 'dev;test' 
        
        # deletes 'api' and 'solr' components on 'test' instance in currently logged in project
        ocToolkit -d test -m 'api;solr'
        
        # backups all components on 'test' instance in logged in project and remove clutter yaml nodes
        ocToolkit -b test -a removeClutter
        
        # validates 'api' and 'solr' components on 'test' instance in logged in project
        # you shoud be logged in in same cluster that you specified in '-c' flag
        ocToolkit -c clusterIntern -v test -m 'api;solr'
        
        # upgrades 'solr' component on 'test' instance in logged in project but all dirs that have 'init' string in its name will me omitted
        # you shoud be logged in in same cluster that you specified in '-c' flag
        ocToolkit -c clusterIntern -u test -m solr -o init
        
        # generate yaml templates(no installing) for oc resourse kinds 'DeploymentConfig' and 'Service' 
        # of 'solr' component for 'test' instance for 'clusterPublic' cluster
        ocToolkit -c clusterPublic -i test -m solr -k 'DeploymentConfig;Service' -o oc
  
  Place instance specific secretes in e.g.: secrets/instance/test/my_secret.txt for 'test' instance and 
  access them by [% secrets.instance_specific.item('my_secret.txt') %] from tt template
  Make sure that oc_config.json->project node values are set correctly.

  ";
  
sub addFlagValuesToConfig($);
sub componentIsAllowed($$$$);
sub generateUrl($$$$$$);
sub removeClutter($$);
sub _loopInstances($$);

my $clusterBaseAddress;
my $advanceFeatures;
my $backupSpecificInstances;
my $cluster = "clusterPublic"; # default
my $ocConfigFile;
my $deleteInstances;
my $host;
my $installInstances;
my $ocResourceKinds;
my $componentDirs;
my $namespace;
my $projectName; # used in default url generation
my $omit   = ""; 
my $urlPrefix;
my $clusterIpRange;
my $secretsDir;
my $sortType;
my $templatesTTDir;
my $yamlToTTconvertDir;
my $upgradeInstances;
my $validateInstances;
my $specificYamlFile;
my $templatesYamlDir;

my %opts;
getopts("a:A:b:c:d:H:h:i:k:m:M:n:N:o:O:p:r:s:S:t:T:u:v:y:Y:", \%opts);

if($opts{h}){
    print $help;
    exit;
}

$clusterBaseAddress      = $opts{A} if defined $opts{A};
$advanceFeatures         = $opts{a} if defined $opts{a};
$backupSpecificInstances = $opts{b} if defined $opts{b};
$cluster                 = $opts{c} if defined $opts{c};
$ocConfigFile            = $opts{C} if defined $opts{C};
$deleteInstances         = $opts{d} if defined $opts{d};
$host                    = $opts{H} if defined $opts{H};
$installInstances        = $opts{i} if defined $opts{i};
$ocResourceKinds         = $opts{k} if defined $opts{k};
$componentDirs           = $opts{m} if (defined $opts{m}) && ($opts{m} ne "all");
$namespace               = $opts{n} if defined $opts{n};
$projectName             = $opts{N} if defined $opts{N};
$omit                    = $opts{o} if defined $opts{o};
$urlPrefix               = $opts{p} if defined $opts{p};
$clusterIpRange          = $opts{r} if defined $opts{r};
$secretsDir              = $opts{s} if defined $opts{s};
$sortType                = $opts{S} if defined $opts{S};
$templatesTTDir          = $opts{t} if defined $opts{t};
$yamlToTTconvertDir      = $opts{T} if defined $opts{T};
$upgradeInstances        = $opts{u} if defined $opts{u};
$validateInstances       = $opts{v} if defined $opts{v};
$specificYamlFile        = $opts{y} if defined $opts{y};
$templatesYamlDir        = $opts{Y} if defined $opts{Y};

if(defined $installInstances){
    die("Please set working cluster with -c flag, e.g.: perl ocToolkit.pl -c clusterPublic -i prod") if not defined $cluster;
}

my $flag;
$flag = $deleteInstances         if defined $deleteInstances;
$flag = $installInstances        if defined $installInstances;
$flag = $backupSpecificInstances if defined $backupSpecificInstances;
$flag = $validateInstances       if defined $validateInstances;
$flag = $upgradeInstances        if defined $upgradeInstances;
die("Flags -i -d -b -v -u can't be left empty.") if (defined $flag) && (length($flag) eq 2) && ($flag =~ /\-/ ); 

if(not defined $clusterBaseAddress){
    my $clusterLc = lc $cluster;
    $clusterBaseAddress = "apps.$clusterLc";
}

if(defined $deleteInstances){
    my $myComponentDirs = $componentDirs;
    if($omit =~ /init/ && (defined $componentDirs)){
        # remove init dirs
        my @componentsDirArray = split(';', $componentDirs);
        @componentsDirArray    = (grep {$_ !~ /init/} @componentsDirArray);
        $myComponentDirs       = join( ';', @componentsDirArray);
    }
    $myComponentDirs = "all" if not defined $myComponentDirs;
    print qx/oc project/;
    print "Deleting $myComponentDirs component(s) in '$deleteInstances' instance in '$cluster' cluster.
    \nPress enter to continue or ctrl+c to abort";
    my $continue = <>;
}

############################################################################
# install/delete/validate/update/backup/create yamls for specific instance #
############################################################################

# set 'multipleClusters' as default
if(not defined $advanceFeatures){
    $advanceFeatures = "multipleClusters";
}else{
    $advanceFeatures .= ";multipleClusters";
}
my $ocObj = OcToolkit->new( advanceFeatures       => $advanceFeatures,
                            clusterBaseAddress    => $clusterBaseAddress,
                            cluster               => $cluster,
                            ocConfigFile          => $ocConfigFile,
                            host                  => $host,
                            ocResourceKinds       => $ocResourceKinds,
                            componentDirs         => $componentDirs,
                            namespace             => $namespace,
                            projectName           => $projectName,
                            omit                  => $omit,
                            urlPrefix             => $urlPrefix,
                            clusterIpRange        => $clusterIpRange,
                            secretsDir            => $secretsDir,
                            sortType              => $sortType,
                            templatesTTDir        => $templatesTTDir,
                            yamlToTTconvertDir    => $yamlToTTconvertDir,
                            specificYamlFile      => $specificYamlFile,
                            templatesYamlDir      => $templatesYamlDir,
                            addFlagValuesToConfig => \&addFlagValuesToConfig,
                            componentIsAllowed    => \&componentIsAllowed,
                            generateUrl           => \&generateUrl,
                            removeClutter         => \&removeClutter,
                            removeClutterBackup   => \&removeClutterBackup);

# $ocObj->setParams({omit => "oc"});
_loopInstances($deleteInstances,         "delete")   if defined $deleteInstances;
_loopInstances($installInstances,        "install")  if defined $installInstances;
_loopInstances($upgradeInstances,        "upgrade")  if defined $upgradeInstances;
_loopInstances($validateInstances,       "validate") if defined $validateInstances;
if(defined $backupSpecificInstances){
    if($backupSpecificInstances eq "all"){
        $ocObj->backupWholeOCProject();
    }else{
        _loopInstances($backupSpecificInstances, "backup");
    }
}
$ocObj->convertYamlToTTExtention($yamlToTTconvertDir) if defined $yamlToTTconvertDir;

#####################################################################################
# use this functions to add custom config/logic without need to change OcToolkit.pm # 
#####################################################################################

# add some script input flag value to config file, access them then in TT Template by [% my_custom_value %]
sub addFlagValuesToConfig($){
    my ($config) = @_;

    $config->{my_custom_value} = "some custom value received from flag";

    return $config;
}

# some specific rules about what components are allowed to be installed on given cluster and instance
# if you need completely different tt template files for specific clusters use '-a multipleClusters' flag
# put tt template file into nested directory, named by your cluster, inside of your tt template component dirs
sub componentIsAllowed($$$$){
    my ($myTemplateName, $myDir, $myCluster, $myInstance) = @_;

#     my $clusterLowerCase = lc $myCluster;
#     if($clusterLowerCase =~ /clusterPublic/){
#         if($myTemplateName =~ /route/){
#             return 0 if $myDir =~ /solr/;
#             return 0 if $myDir =~ /api/;
#             return 0 if $myDir =~ /admin\-ui/;
#         }
#         return 0 if $myDir =~ /swagger/;
#     }

    return 1;
}

# define default routes url pattern
sub generateUrl($$$$$$){
    my ($urlPrefix, $projectName, $componentName, $instanceKey, $clusterBaseAddress, $host) = @_;
    
    if(defined $urlPrefix){
        $urlPrefix .= "-";
    }else{
        $urlPrefix = "";
    }
    if(defined $projectName){
        $projectName .= "-";
    }else{
        $projectName = "";
    }
    if(defined $clusterBaseAddress){
        $clusterBaseAddress .= ".";
    }else{
        $clusterBaseAddress = "";
    }

    return $urlPrefix.$projectName.$componentName."-".$instanceKey.".".$clusterBaseAddress.$host;
}

# ignore some specific fields during validation and upgrade
sub removeClutter($$){
    my ($ocJsonHash, $params) = @_;

    $ocJsonHash = removeClutterBackup($ocJsonHash, $params);
    
    my $dir          = $params->{dir};
    my $templateName = $params->{templateName};
    my $ocKind       = $params->{ocKind};
    my $ocName       = $params->{ocName};

    # some extra fields(in comparison to backup) to be ignored during validation and upgrade
    foreach my $i ((0..7)){
        if($ocKind eq "BuildConfig"){
            if((defined $ocJsonHash->{spec}) &&
               (defined $ocJsonHash->{spec}->{triggers}) &&
               (defined $ocJsonHash->{spec}->{triggers}->[$i]) &&
               (defined $ocJsonHash->{spec}->{triggers}->[$i]->{github}) &&
               (defined $ocJsonHash->{spec}->{triggers}->[$i]->{github}->{secret})
            ){
                delete $ocJsonHash->{spec}->{triggers}->[$i]->{github}->{secret};
            }
            if((defined $ocJsonHash->{spec}) &&
               (defined $ocJsonHash->{spec}->{triggers}) &&
               (defined $ocJsonHash->{spec}->{triggers}->[$i]) &&
               (defined $ocJsonHash->{spec}->{triggers}->[$i]->{generic}) &&
               (defined $ocJsonHash->{spec}->{triggers}->[$i]->{generic}->{secret})
            ){
                delete $ocJsonHash->{spec}->{triggers}->[$i]->{generic}->{secret};
            }
        }
        if($ocKind eq "ImageStream"){
            if((defined $ocJsonHash->{spec}) &&
               (defined $ocJsonHash->{spec}->{tags}) &&
               (defined $ocJsonHash->{spec}->{tags}->[$i]) &&
               (defined $ocJsonHash->{spec}->{tags}->[$i]->{importPolicy})
            ){
                delete $ocJsonHash->{spec}->{tags}->[$i]->{importPolicy};
            }
        }
        if($ocKind eq "DeploymentConfig"){
            if((defined $ocJsonHash->{spec}) &&
               (defined $ocJsonHash->{spec}->{template}) &&
               (defined $ocJsonHash->{spec}->{template}->{spec}) &&
               (defined $ocJsonHash->{spec}->{template}->{spec}->{containers}) &&
               (defined $ocJsonHash->{spec}->{template}->{spec}->{containers}->[$i]) &&
               (defined $ocJsonHash->{spec}->{template}->{spec}->{containers}->[$i]->{resources}) &&
               (not defined $ocJsonHash->{spec}->{template}->{spec}->{containers}->[$i]->{resources}->{limits}) &&
               (not defined $ocJsonHash->{spec}->{template}->{spec}->{containers}->[$i]->{resources}->{requests})
            ){
                $ocJsonHash->{spec}->{template}->{spec}->{containers}->[$i]->{resources} = {};
            }
        }
    }

    delete $ocJsonHash->{spec}->{clusterIPs};
    delete $ocJsonHash->{spec}->{clusterIP};
    delete $ocJsonHash->{spec}->{volumeName} if $ocKind eq "PersistentVolumeClaim";
    
    return $ocJsonHash;
}

# ignore some specific fields during backup of the whole project
sub removeClutterBackup($$){
    my ($ocJsonHash, $params) = @_;

    my $dir          = $params->{dir};
    my $templateName = $params->{templateName};
    my $ocKind       = $params->{ocKind};
    my $ocName       = $params->{ocName};
    
    delete $ocJsonHash->{status};
    
    delete $ocJsonHash->{metadata}->{annotations}->{'openshift.io/generated-by'};
    delete $ocJsonHash->{metadata}->{annotations}->{'openshift.io/restore-server-version'};
    delete $ocJsonHash->{metadata}->{annotations}->{'openshift.io/backup-server-version'};
    delete $ocJsonHash->{metadata}->{annotations}->{'openshift.io/backup-registry-hostname'};
    delete $ocJsonHash->{metadata}->{annotations}->{'openshift.io/migration-registry'};
    delete $ocJsonHash->{metadata}->{annotations}->{'openshift.io/restore-registry-hostname'};
    delete $ocJsonHash->{metadata}->{annotations}->{'openshift.io/image.dockerRepositoryCheck'};
    delete $ocJsonHash->{metadata}->{annotations}->{'kubectl.kubernetes.io/last-applied-configuration'};
    delete $ocJsonHash->{metadata}->{annotations}->{'kubernetes.io/service-account.name'};
    delete $ocJsonHash->{metadata}->{annotations}->{'kubernetes.io/service-account.uid'};
    delete $ocJsonHash->{metadata}->{annotations}->{'openshift.io/token-secret.value'};
    delete $ocJsonHash->{metadata}->{annotations}->{'openshift.io/token-secret.name'};
    delete $ocJsonHash->{metadata}->{annotations}->{'kubernetes.io/created-by'};
    delete $ocJsonHash->{metadata}->{annotations}->{'volume.beta.kubernetes.io/storage-provisioner'};
    delete $ocJsonHash->{metadata}->{annotations}->{'pv.kubernetes.io/bind-completed'};
    delete $ocJsonHash->{metadata}->{annotations}->{'pv.kubernetes.io/bound-by-controller'};
    delete $ocJsonHash->{metadata}->{annotations}->{'volume.kubernetes.io/storage-provisioner'};
    
    delete $ocJsonHash->{metadata}->{labels}->{'migration.openshift.io/migrated-by-migmigration'};
    delete $ocJsonHash->{metadata}->{labels}->{'migration.openshift.io/migrated-by-migplan'};
    delete $ocJsonHash->{metadata}->{labels}->{'velero.io/backup-name'};
    delete $ocJsonHash->{metadata}->{labels}->{'velero.io/restore-name'};
    delete $ocJsonHash->{metadata}->{labels}->{'app.kubernetes.io/component'};
    delete $ocJsonHash->{metadata}->{labels}->{'app.kubernetes.io/instance'};
    
    delete $ocJsonHash->{metadata}->{resourceVersion};
    delete $ocJsonHash->{metadata}->{uid};
    delete $ocJsonHash->{metadata}->{creationTimestamp};
    delete $ocJsonHash->{metadata}->{generation};
    delete $ocJsonHash->{metadata}->{managedFields};
    delete $ocJsonHash->{metadata}->{selfLink};
    delete $ocJsonHash->{metadata}->{deletionTimestamp};
    delete $ocJsonHash->{metadata}->{deletionGracePeriodSeconds};

    foreach my $i ((0..7)){
        if((defined $ocJsonHash->{spec}) &&
           (defined $ocJsonHash->{spec}->{triggers}) &&
           (defined $ocJsonHash->{spec}->{triggers}->[$i]) &&
           (defined $ocJsonHash->{spec}->{triggers}->[$i]->{imageChangeParams}) &&
           (defined $ocJsonHash->{spec}->{triggers}->[$i]->{imageChangeParams}->{lastTriggeredImage})
        ){
            delete $ocJsonHash->{spec}->{triggers}->[$i]->{imageChangeParams}->{lastTriggeredImage};
        }
        
        if((defined $ocJsonHash->{spec}) &&
           (defined $ocJsonHash->{spec}->{tags}) &&
           (defined $ocJsonHash->{spec}->{tags}->[$i]) &&
           (defined $ocJsonHash->{spec}->{tags}->[$i]->{generation})
        ){
            delete $ocJsonHash->{spec}->{tags}->[$i]->{generation};
        }
        
        if((defined $ocJsonHash->{spec}) &&
           (defined $ocJsonHash->{spec}->{template}) &&
           (defined $ocJsonHash->{spec}->{template}->{spec}) &&
           (defined $ocJsonHash->{spec}->{template}->{spec}->{containers}) &&
           (defined $ocJsonHash->{spec}->{template}->{spec}->{containers}->[$i]) &&
           (defined $ocJsonHash->{spec}->{template}->{spec}->{containers}->[$i]->{image})
        ){
            delete $ocJsonHash->{spec}->{template}->{spec}->{containers}->[$i]->{image};
        }
    }

    return $ocJsonHash;
}

sub _loopInstances($$){
    my ($instancesString, $methodName) = @_;
    
    my @instances =  split(';', $instancesString);
     foreach my $instance (@instances){
        my $methodNameU = ucfirst $methodName;
        print "$methodNameU instance: $instance\n";
        $ocObj->$methodName($instance) if $ocObj->can($methodName); 
    }
}

1;

=cut
