NAME

OcToolkit - Open Cloud Toolkit

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');

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

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.

AUTHOR

John Summers <devp2000a@gmail.com>

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;