File Coverage

blib/lib/OcToolkit.pm
Criterion Covered Total %
statement 50 593 8.4
branch 0 248 0.0
condition 0 73 0.0
subroutine 17 49 34.6
pod 6 11 54.5
total 73 974 7.4


line stmt bran cond sub pod time code
1             # John Summers, hereby disclaims all copyright interest in the program Open Cloud Toolkit aka "ocToolkit" written by John Summers
2             #
3             # John Summers, devp2000a@gmail.com
4             #
5             # This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or any later version.
6             #
7             # This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
8             #
9             # You should have received a copy of the GNU General Public License along with this program. If not, see .
10              
11             package OcToolkit;
12              
13 1     1   215537 use v5.16; # or newer
  1         3  
14 1     1   4 use strict;
  1         1  
  1         16  
15 1     1   3 use warnings;
  1         2  
  1         64  
16              
17             our $VERSION = "1.15";
18              
19 1     1   6 use JSON::PP;
  1         1  
  1         67  
20 1     1   482 use Tie::IxHash;
  1         3425  
  1         24  
21 1     1   414 use Text::Diff;
  1         7188  
  1         50  
22 1     1   527 use Template;
  1         15502  
  1         33  
23 1     1   512 use File::Slurp;
  1         23690  
  1         58  
24 1     1   492 use File::Find::Rule;
  1         7804  
  1         6  
25 1     1   63 use File::Path qw(make_path rmtree);
  1         2  
  1         50  
26 1     1   463 use MIME::Base64 qw(encode_base64 decode_base64);
  1         810  
  1         59  
27 1     1   473 use YAML qw(LoadFile);
  1         5811  
  1         47  
28 1     1   445 use YAML::Safe;
  1         1648  
  1         54  
29              
30 1     1   523 use Data::Dumper;
  1         5913  
  1         3159  
31              
32              
33             sub new{
34 0     0 0   my $class = shift;
35 0           my $self = {@_};
36            
37 0           $self->{tt} = Template->new({INTERPOLATE => 1, ABSOLUTE => 1});
38 0           $self->{json} = JSON::PP->new;
39 0           $self->{json}->convert_blessed();
40            
41 0           my $projectDir = $self->{projectDir}; # e.g.: /home/user/myProject/
42            
43 0 0         $self->{secretsDir} = $projectDir."secrets" if not defined $self->{secretsDir};
44 0 0         $self->{secretsJson} = "secrets.json" if not defined $self->{secretsJson};
45 0 0         $self->{ocConfigFile} = $projectDir."oc_config.json" if not defined $self->{ocConfigFile};
46 0 0         $self->{templatesTTDir} = $projectDir."templates_tt" if not defined $self->{templatesTTDir};
47 0 0         $self->{templatesYamlDir} = $projectDir."templates_yaml" if not defined $self->{templatesYamlDir};
48 0 0         $self->{validationReportFile} = $projectDir."validation_report.txt" if not defined $self->{validationReportFile};
49 0 0         $self->{cliCommand} = "oc" if not defined $self->{cloudCommand};
50 0 0 0       if((defined $self->{advanceFeatures}) && ($self->{advanceFeatures} =~ /kubectl/)){
51 0           $self->{cliCommand} = "kubectl";
52             }
53            
54 0 0         if(!-e $self->{ocConfigFile}){
55 0 0         open(my $fh, '>', $self->{ocConfigFile}) or die "Could not create '$self->{ocConfigFile}': $!";
56 0           print $fh '{}';
57 0           close($fh);
58 0           print "$self->{ocConfigFile} is missing. Empty $self->{ocConfigFile} is created.\n";
59             }
60              
61 0 0         if(not defined $self->{cluster}){
62 0           my $ocConfigFiletext = read_file($self->{ocConfigFile});
63 0           my $oCconfig = $self->{json}->utf8->decode($ocConfigFiletext);
64 0           $self->{cluster} = $oCconfig->{project}->{default_cluster};
65 0 0         $self->{cluster} = "unknown" if not defined $self->{cluster};
66             }
67              
68 0           return bless $self, $class;
69             }
70              
71             sub backup{
72 0     0 1   my ($self, $instance) = @_;
73              
74 0           $self->{instance} = $instance;
75 0 0         return if not defined $self->generateYaml();
76              
77 0           $self->_createDir("backups");
78 0           $self->_createDir("backups\/$instance");
79            
80 0           $self->_loopDir($self->{config}->{templates_yaml_dir}, "*", "_backupInstance");
81             }
82              
83             sub backupWholeOCProject{
84 0     0 1   my ($self) = @_;
85              
86 0           $self->_createDir("backups");
87 0           $self->_createDir("backups\/wholeProject");
88 0           $self->_clearDir("backups/wholeProject");
89            
90 0           my $ocConfigFileText = read_file($self->{ocConfigFile});
91 0           my $ocConfig = $self->{json}->utf8->decode($ocConfigFileText);
92            
93 0           my $ocResourceKinds = $ocConfig->{project}->{oc_resource_kinds};
94 0 0         $ocResourceKinds = $self->{ocResourceKinds} if defined $self->{ocResourceKinds};
95 0 0         $ocResourceKinds = $self->_getDefaultKinds() if not defined $ocResourceKinds;
96 0           my @ocResourceKindsArray = split(';', $ocResourceKinds);
97            
98 0           foreach my $ocResourceKind (@ocResourceKindsArray){
99 0           my $ocResourceKinds = $ocResourceKind;
100 0 0 0       if($ocResourceKind ne "Ingress" &&
      0        
101             $ocResourceKind ne "StorageClass" &&
102             $ocResourceKind ne "NetworkPolicy"
103             ){
104 0           $ocResourceKinds .= "s";
105             }
106 0           print "$ocResourceKinds:\n";
107 0           my $text = qx/$self->{cliCommand} get $ocResourceKinds/;
108 0           my @textArray = split('\n', $text);
109 0           shift @textArray;
110 0           foreach my $line (@textArray){
111 0           my @lineArray = split(" ", $line);
112 0           my $ocItem = $lineArray[0];
113 0           print "kind: $ocResourceKind item: $ocItem\n";
114 0           $self->_createDir("backups\/wholeProject\/$ocResourceKinds");
115 0           eval {
116 0           my $ocItemJson = qx/$self->{cliCommand} get $ocResourceKind $ocItem -o json/;
117 0           my $ocItemHash = $self->{json}->utf8->decode($ocItemJson);
118 0 0 0       if((defined $self->{advanceFeatures}) &&
      0        
119             ($self->{advanceFeatures} =~ /removeClutter/) &&
120             (defined $self->{removeClutterBackup})){
121 0           my $subParams = {"ocKind" => $ocResourceKind, "ocName" => $ocItem};
122 0           $ocItemHash = $self->{removeClutterBackup}->($ocItemHash, $subParams);
123             }
124 0           my $yamlSaveObj = YAML::Safe->new->boolean("JSON::PP");
125 0           my $yamlText = $yamlSaveObj->Dump($ocItemHash);
126 0           $yamlText =~ s/---\n//;
127 0           write_file("$self->{projectDir}backups\/wholeProject\/$ocResourceKinds/$ocItem".".yaml", $yamlText);
128             };
129 0 0         if($@){
130             # if error occured take yaml without calling '->removeClutterBackup()'
131 0           print "Removing clutter has failed, writing yaml file: $ocResourceKinds/$ocItem.yaml without removing clutter.\n";
132 0           my $yamlText = qx/$self->{cliCommand} get $ocResourceKind $ocItem -o yaml/;
133 0           write_file("$self->{projectDir}backups\/wholeProject\/$ocResourceKinds/$ocItem".".yaml", $yamlText);
134             }
135             }
136             }
137             }
138              
139             sub convertYamlToTTExtention{
140 0     0 0   my ($self, $yamlToTTconvertDir) = @_;
141              
142 0           $self->_loopDir($yamlToTTconvertDir, "yaml", "_convertYamlToTTExtention",
143             {_convertYamlToTTExtention => {yamlToTTconvertDir => $yamlToTTconvertDir}});
144             }
145              
146             sub delete{
147 0     0 1   my ($self, $instance) = @_;
148            
149 0           $self->{instance} = $instance;
150 0 0         return if not defined $self->generateYaml();
151              
152 0           my $cluster = $self->{config}->{cluster};
153 0           my $namespace = $self->{config}->{namespace};
154 0           print "\nDeleting Openshift components from instance: '$instance' in cluster: '$cluster' in namespace: '$namespace'\n";
155 0           $self->_loopDir($self->{config}->{templates_yaml_dir}, "yaml", "_deleteOc");
156            
157 0           return;
158             }
159              
160             sub generateConfigJsonTemplate{
161 0     0 0   my ($self, $instances) = @_;
162            
163 0           print "Existing config file: $self->{ocConfigFile} will be overwriten do you want to continue?
164             Press enter to contiue or ctrl+c to abort";
165 0           my $continue = <>;
166              
167             # preserve order in hash
168 0           my $componentFromTemplatesTTDir = $self->_getComponentFromTemplatesTTDir();
169 0           $componentFromTemplatesTTDir =~ s/\-/_/g;
170 0           my @componentsArr = split(';', $componentFromTemplatesTTDir);
171 0           tie my %configHash, 'Tie::IxHash';
172 0           tie my %instanceSpecificData, 'Tie::IxHash';
173 0           $configHash{instance_specific_data} = \%instanceSpecificData;
174 0           foreach my $component (@componentsArr) {
175 0           tie my %componentHash, 'Tie::IxHash';
176 0           $instanceSpecificData{$component} = \%componentHash;
177 0           my @instancesArr = split(';', $instances);
178 0           foreach my $instance (@instancesArr) {
179 0           $componentHash{$instance} = {};
180             }
181             }
182 0           $configHash{instance_specific_name} = {};
183 0           $configHash{git_repo} = {};
184 0           tie my %projectHash, 'Tie::IxHash';
185 0           $projectHash{name} = "";
186 0           $projectHash{host} = "";
187 0           $projectHash{cluster_ip_range} = "";
188 0           $configHash{project} = \%projectHash;
189            
190 0           write_file($self->{ocConfigFile}, $self->{json}->pretty->encode(\%configHash));
191              
192 0           return;
193             }
194              
195             sub generateYaml{
196 0     0 0   my ($self) = @_;
197              
198 0 0 0       print "Instance is missing.\n" and return if not defined $self->{instance};
199              
200 0           $self->{config} = $self->_generateConfig();
201 0 0         $self->_removeInitFromComponentDirs() if $self->{omit} =~ /init/;
202 0           $self->_loopDir($self->{config}->{templates_tt_dir}, "*", "_createTemplatesTTDirHash");
203 0           $self->_clearDir($self->{config}->{templates_yaml_dir});
204 0           $self->_createYamlFiles();
205            
206 0           return 1;
207             }
208              
209              
210              
211             sub install{
212 0     0 1   my ($self, $instance) = @_;
213              
214 0           $self->{instance} = $instance;
215 0 0         return if not defined $self->generateYaml();
216              
217 0           my $cluster = $self->{config}->{cluster};
218 0           my $namespace = $self->{config}->{namespace};
219 0           print "\nInstalling Openshift components for instance: '$instance' in cluster: '$cluster' in namespace: '$namespace'\n";
220             # sent custom params
221             # $self->_loopDir($self->{config}->{templates_tt_dir}, "tt", "_callOc", {_callOc => {{param1 : "value1"}, {param2 : "value2"} }});
222 0 0         $self->_loopDir($self->{config}->{templates_yaml_dir}, "yaml", "_callOc") if $self->{omit} !~ /oc/;
223            
224 0           print qq^\n\nTo get build and deployment status run: $self->{cliCommand} get pods | grep Running\n\n^;
225            
226 0           return;
227             }
228              
229             sub setParams{
230 0     0 0   my ($self, $params) = @_;
231              
232 0 0         if (@_ == 2) {
233 0 0         $self->{advanceFeatures} = $params->{advanceFeatures} if defined $params->{advanceFeatures};
234 0 0         $self->{clusterBaseAddress} = $params->{clusterBaseAddress} if defined $params->{clusterBaseAddress};
235 0 0         $self->{cluster} = $params->{cluster} if defined $params->{cluster};
236 0 0         $self->{ocConfigFile} = $params->{ocConfigFile} if defined $params->{ocConfigFile};
237 0 0         $self->{host} = $params->{host} if defined $params->{host};
238 0 0         $self->{ocResourceKinds} = $params->{ocResourceKinds} if defined $params->{ocResourceKinds};
239 0 0         $self->{componentDirs} = $params->{componentDirs} if defined $params->{componentDirs};
240 0 0         $self->{namespace} = $params->{namespace} if defined $params->{namespace};
241 0 0         $self->{projectName} = $params->{projectName} if defined $params->{projectName};
242 0 0         $self->{omit} = $params->{omit} if defined $params->{omit};
243 0 0         $self->{urlPrefix} = $params->{urlPrefix} if defined $params->{urlPrefix};
244 0 0         $self->{clusterIpRange} = $params->{clusterIpRange} if defined $params->{clusterIpRange};
245 0 0         $self->{secretsDir} = $params->{secretsDir} if defined $params->{secretsDir};
246 0 0         $self->{sortType} = $params->{sortType} if defined $params->{sortType};
247 0 0         $self->{templatesTTDir} = $params->{templatesTTDir} if defined $params->{templatesTTDir};
248 0 0         $self->{yamlToTTconvertDir} = $params->{yamlToTTconvertDir} if defined $params->{yamlToTTconvertDir};
249 0 0         $self->{specificYamlFile} = $params->{specificYamlFile} if defined $params->{specificYamlFile};
250 0 0         $self->{templatesYamlDir} = $params->{templatesYamlDir} if defined $params->{templatesYamlDir};
251 0 0         $self->{validationReportFile} = $params->{validationReportFile} if defined $params->{validationReportFile};
252 0 0         $self->{projectDir} = $params->{projectDir} if defined $params->{projectDir};
253 0 0         $self->{addFlagValuesToConfig} = $params->{addFlagValuesToConfig} if defined $params->{addFlagValuesToConfig};
254 0 0         $self->{componentIsAllowed} = $params->{componentIsAllowed} if defined $params->{componentIsAllowed};
255 0 0         $self->{generateUrl} = $params->{generateUrl} if defined $params->{generateUrl};
256 0 0         $self->{removeClutter} = $params->{removeClutter} if defined $params->{removeClutter};
257 0 0         $self->{removeClutterBackup} = $params->{removeClutterBackup} if defined $params->{removeClutterBackup};
258             }
259 0           return;
260             }
261              
262             sub upgrade{
263 0     0 1   my ($self, $instance) = @_;
264            
265 0           $self->validate($instance);
266            
267 0           my $validationReport = read_file($self->{validationReportFile});
268 0           my @validationReportLines = split /\n/, $validationReport;
269 0           foreach my $line (@validationReportLines){
270 0           my @items = split /;/, $line;
271 0 0         if($items[-1] eq "MODIFIED"){
272 0           my $pathAndFile = $items[0];
273 0           my $ocKind = $items[1];
274 0           my $ocName = $items[2];
275 0 0         if($ocKind eq "PersistentVolumeClaim"){
276 0           print "You are trying to update PersistentVolumeClaim.
277             Please make sure that all PODs that use this Persisten Volume are turned down before update.
278             If update operation start hanging at this step press ctrl+c to abort. \n\n";
279             }
280 0           print "Upgrading ocKind:$ocKind, ocName: $ocName from $pathAndFile\n";
281 0           qx/$self->{cliCommand} delete $ocKind $ocName/;
282 0           qx/$self->{cliCommand} create -f $self->{config}->{templates_yaml_dir}\/$pathAndFile/;
283             }
284             }
285             }
286              
287             sub validate{
288 0     0 1   my ($self, $instance) = @_;
289              
290 0           qx/> $self->{validationReportFile}/; # clear file
291 0           $self->{instance} = $instance;
292 0 0         return if not defined $self->generateYaml();
293            
294 0           my $cluster = $self->{config}->{cluster};
295 0           my $namespace = $self->{config}->{namespace};
296 0           print "\nValidating Openshift components for instance: '$instance' in cluster: '$cluster' in namespace: '$namespace'\n";
297 0           $self->_loopDir($self->{config}->{templates_yaml_dir}, "yaml", "_validateInstance");
298             }
299              
300             sub _addSecretsToConfigHash{
301 0     0     my ($self, $config, $dirFileName) = @_;
302              
303 0           my @dirFileNameArr = split('/', $dirFileName); # test, prod etc.
304 0           my $secretText = read_file($dirFileName);
305 0           $config->{secrets}->{$dirFileNameArr[-1]} = $secretText;
306            
307 0           my $secretTextBase64 = encode_base64($secretText);
308 0           $secretTextBase64 =~ s/\n//g;
309 0           $secretTextBase64 =~ s/\r//g;
310 0           $config->{secrets}->{base64}->{$dirFileNameArr[-1]} = $secretTextBase64;
311              
312 0           return;
313             }
314              
315             sub _backupInstance{
316 0     0     my ($self, $params) = @_;
317              
318 0           my $dir = $params->{dir};
319 0           my $templateName = $params->{templateName};
320              
321 0           my $instance = $self->{config}->{instance};
322 0           $self->_createDir("backups\/$instance\/$dir");
323 0           my $pathToYamlFile = "$self->{config}->{templates_yaml_dir}/$dir/$templateName".".yaml";
324 0           my $templateData = LoadFile($pathToYamlFile);
325            
326 0           my $yamlText;
327 0 0 0       if((defined $self->{advanceFeatures}) &&
      0        
328             ($self->{advanceFeatures} =~ /removeClutter/) &&
329             (defined $self->{removeClutterBackup})){
330 0           print "$self->{cliCommand} get $templateData->{kind} $templateData->{metadata}->{name} -o json\n";
331 0           my $ocJson = qx/$self->{cliCommand} get $templateData->{kind} $templateData->{metadata}->{name} -o json/;
332 0           my $ocHash = $self->{json}->utf8->decode($ocJson);
333 0           my $subParams = {"ocKind" => $templateData->{kind}, "ocName" => $templateData->{metadata}->{name}};
334 0           $ocHash = $self->{removeClutterBackup}->($ocHash, $subParams);
335 0           my $yamlSaveObj = YAML::Safe->new->boolean("JSON::PP");
336 0           $yamlText = $yamlSaveObj->Dump($ocHash);
337 0           $yamlText =~ s/---\n//;
338             }else{
339 0           print "$self->{cliCommand} get $templateData->{kind} $templateData->{metadata}->{name} -o yaml\n";
340 0           $yamlText = qx/$self->{cliCommand} get $templateData->{kind} $templateData->{metadata}->{name} -o yaml/;
341             }
342              
343 0           write_file("$self->{projectDir}backups\/$instance\/$dir/$templateName".".yaml", $yamlText);
344             }
345              
346             sub _callOc{
347 0     0     my ($self, $params) = @_;
348            
349 0           my @funcName = split /::/, (caller(0))[3];
350 0           my $dir = $params->{dir};
351 0           my $templateName = $params->{templateName};
352 0           my $customParams = $params->{params}->{$funcName[-1]};
353              
354             return if (defined $self->{componentIsAllowed}) &&
355 0 0 0       (not $self->{componentIsAllowed}->($templateName, $dir, $self->{cluster}, $self->{instance}));
356              
357 0           my $templateNameYaml = $templateName.".yaml";
358 0           my $pathAndFile = $self->{config}->{templates_yaml_dir}."\/$dir\/$templateNameYaml";
359 0           my $yamlData = LoadFile($pathAndFile);
360              
361 0 0         if($yamlData->{kind} eq "CronJob"){
362 0           print "$self->{cliCommand} apply -f $pathAndFile\n";
363 0           qx/$self->{cliCommand} apply -f $pathAndFile/;
364             }else{
365 0           print "$self->{cliCommand} create -f $pathAndFile\n";
366 0           qx/$self->{cliCommand} create -f $pathAndFile/;
367             }
368             }
369              
370             sub _clearDir{
371 0     0     my ($self, $dir) = @_;
372              
373 0           rmtree $dir;
374 0           make_path $dir;
375             }
376              
377             sub _convertYamlToTTExtention{
378 0     0     my ($self, $params) = @_;
379              
380 0           my @funcName = split /::/, (caller(0))[3];
381 0           my $dir = $params->{dir};
382 0           my $templateName = $params->{templateName};
383 0           my $customParams = $params->{params}->{$funcName[-1]};
384 0           my $yamlToTTconvertDir = $customParams->{yamlToTTconvertDir};
385            
386 0           my $path = "$yamlToTTconvertDir/$dir";
387 0           qx/mv $path\/$templateName\.yaml $path\/$templateName\.tt 2>&1/;
388             }
389              
390             sub _createTemplatesTTDirHash{
391 0     0     my ($self, $params) = @_;
392            
393 0           my $dir = $params->{dir};
394 0           my $templateName = $params->{templateName};
395              
396             # $self->{templatesTTHash}->{"40-api"}->{"40-build-config-api"} = "40-api/clusterPublic/40-build-config-api";
397 0 0         if(not -d "$self->{templatesTTDir}/$dir/$templateName"){
    0          
398 0           $self->{templatesTTHash}->{$dir}->{$templateName} = "$dir/$templateName";
399             }elsif($templateName eq $self->{cluster}){
400 0           for my $dirFileName (File::Find::Rule->file()->name("*")->in("$self->{templatesTTDir}/$dir/$self->{cluster}")) {
401 0           my @dirFileNameArr = split('/', $dirFileName);
402 0           my $templName = substr($dirFileNameArr[-1], 0, -3);
403 0           $self->{templatesTTHash}->{$dir}->{$templName} = "$dir/$self->{cluster}/$templName";
404             }
405             }
406              
407 0           return;
408             }
409              
410             sub _createDir{
411 0     0     my ($self, $relativeDirPath) = @_;
412 0           qx/mkdir $self->{projectDir}$relativeDirPath 2>&1/;
413 0           return;
414             }
415              
416             sub _createYamlFiles{
417 0     0     my ($self) = @_;
418            
419             # $self->{templatesTTHash}->{"40-api"}->{"40-build-config-api"} = "40-api/clusterPublic/40-build-config-api.tt";
420 0           my $dirs = $self->{templatesTTHash};
421 0 0 0       if((defined $self->{sortType}) && ($self->{sortType}) eq "alphabetic"){
422             # alphabetic
423 0           foreach my $dir (sort {lc($a) cmp lc($b)} keys %{$dirs}){
  0            
  0            
424 0           my $files = $dirs->{$dir};
425 0           foreach my $templateName (sort {lc($a) cmp lc($b)} keys %{$files}){
  0            
  0            
426 0           $self->_generateYaml({dir => $dir, templateName => $templateName});
427             }
428             }
429             }else{
430             # numeric
431 1     1   8 no warnings 'numeric';
  1         29  
  1         2365  
432 0           foreach my $dir (sort {lc($a) <=> lc($b)} keys %{$dirs}){
  0            
  0            
433 0           my $files = $dirs->{$dir};
434 0           foreach my $templateName (sort {lc($a) <=> lc($b)} keys %{$files}){
  0            
  0            
435 0           $self->_generateYaml({dir => $dir, templateName => $templateName});
436             }
437             }
438             }
439              
440 0           return;
441             }
442              
443             sub _deleteOc{
444 0     0     my ($self, $params) = @_;
445              
446 0           my $dir = $params->{dir};
447 0           my $templateName = $params->{templateName};
448              
449 0           my $data = LoadFile("$self->{config}->{templates_yaml_dir}/$dir/$templateName".".yaml");
450 0           print "$self->{cliCommand} delete $data->{kind} $data->{metadata}->{name}\n";
451 0           qx/$self->{cliCommand} delete $data->{kind} $data->{metadata}->{name}/;
452             }
453              
454             sub _generateConfig{
455 0     0     my ($self) = @_;
456              
457 0           my $ocConfigJson = read_file($self->{ocConfigFile});
458 0           my $config->{oc_config} = $self->{json}->utf8->decode($ocConfigJson);
459              
460 0 0         $config = $self->{addFlagValuesToConfig}->($config) if defined $self->{addFlagValuesToConfig};
461              
462             # set/generate instance specific names
463 0           foreach my $entry (keys %{$config->{oc_config}->{instance_specific_name}}){
  0            
464 0           $config->{oc_config}->{instance_specific_name}->{$entry} .= "-$self->{instance}";
465             }
466              
467 0           $config->{allowed_clusters} = $config->{oc_config}->{project}->{allowed_clusters};
468 0 0         if(not defined $config->{allowed_clusters}){
469 0           print "INFO: oc_config->project->allowed_clusters json node is empty. Marking 'allowed_clusters' as '$self->{cluster}'\n";
470 0           $config->{allowed_clusters} = $self->{cluster};
471             }else{
472 0 0         print "Warning: Unknown cluster $self->{cluster}\n" if $config->{allowed_clusters} !~ $self->{cluster};
473             }
474              
475 0           $config->{cluster_ip_range} = $config->{oc_config}->{project}->{cluster_ip_range};
476 0 0         $config->{cluster_ip_range} = $self->{clusterIpRange} if defined $self->{clusterIpRange};
477 0           $config->{project_name} = $config->{oc_config}->{project}->{name};
478 0 0         $config->{project_name} = $self->{projectName} if defined $self->{projectName};
479 0           $config->{host} = $config->{oc_config}->{project}->{host};
480 0 0         $config->{host} = $self->{host} if defined $self->{host};
481 0           $config->{namespace} = $config->{oc_config}->{project}->{namespace};
482 0 0         $config->{namespace} = $self->_getCurrentProject() if not defined $config->{namespace};
483 0 0         $config->{namespace} = $self->{namespace} if defined $self->{namespace};# from -n flag
484             # default component dirs are set here, dirs in 'templates_tt' not set as default will be omitted
485 0           $config->{component_dirs} = $config->{oc_config}->{project}->{component_dirs};
486 0 0         $config->{component_dirs} = $self->{componentDirs} if defined $self->{componentDirs};
487 0 0         $config->{component_dirs} = $self->_getComponentFromTemplatesTTDir() if not defined $config->{component_dirs};
488             # component dirs can contains numbers e.g.: '50-solr' so regexp match is used =>
489             # separate 'init' components in order to avoid false matches (e.g.: 20-init-api vs 50-api when 'api' searched)
490 0           my @componentDirs = split(';', $config->{component_dirs});
491 0           my $standardComponentDirs = "";
492 0           my $initComponentDirs = "";
493 0           foreach my $componentDir (@componentDirs){
494 0 0         if($componentDir =~ /init/){
495 0           $initComponentDirs .= $componentDir.";";
496             }else{
497 0           $standardComponentDirs .= $componentDir.";";
498             }
499             }
500 0           chop($initComponentDirs);
501 0           chop($standardComponentDirs);
502 0           $config->{init_component_dirs} = $initComponentDirs;
503 0           $config->{standard_component_dirs} = $standardComponentDirs;
504 0           $config->{oc_resource_kinds} = $config->{oc_config}->{project}->{oc_resource_kinds};
505 0 0         $config->{oc_resource_kinds} = $self->{ocResourceKinds} if defined $self->{ocResourceKinds};
506 0 0         $config->{oc_resource_kinds} = $self->_getDefaultKinds() if not defined $config->{oc_resource_kinds};
507 0           $config->{templates_yaml_dir} = $self->{templatesYamlDir};
508 0           $config->{templates_tt_dir} = $self->{templatesTTDir};
509 0           $config->{cluster_camelcase} = $self->{cluster};
510 0           $config->{cluster} = lc $self->{cluster};
511 0           $config->{instance} = lc $self->{instance};
512 0           $config->{instance_capitalized_first} = ucfirst $config->{instance};
513 0 0         $config->{url_prefix} = $self->{urlPrefix} if defined $self->{urlPrefix};
514 0 0         $config->{cluster_base_address} = $self->{clusterBaseAddress} if defined $self->{clusterBaseAddress};
515 0 0         $config->{cluster_base_address} = $config->{oc_config}->{project}->{cluster_base_address} if not defined $config->{cluster_base_address};
516            
517 0           my $componentConfigNodes = $self->_getComponentConfigNodes($config);
518 0           foreach my $componentConfNode (@$componentConfigNodes){
519             # set/generate default urls
520 0 0 0       if((defined $config->{oc_config}->{instance_specific_data}->{$componentConfNode}) &&
521             (ref($config->{oc_config}->{instance_specific_data}->{$componentConfNode}) eq 'HASH')){
522 0           foreach my $instanceKey (keys %{$config->{oc_config}->{instance_specific_data}->{$componentConfNode}}){
  0            
523 0 0         if(not defined $config->{oc_config}->{instance_specific_data}->{$componentConfNode}->{$instanceKey}->{url}){
524 0           my $componentNameKebab = $componentConfNode;
525 0           $componentNameKebab =~ s/_/\-/g;
526 0           my $lcInstanceKey = lc $instanceKey;
527 0           my $url = "";
528 0 0         if(defined $self->{generateUrl}){
529             $url = $self->{generateUrl}->($config->{url_prefix},
530             $config->{project_name},
531             $componentNameKebab,
532             $lcInstanceKey,
533             $config->{cluster_base_address},
534 0           $config->{host});
535             }
536 0           $config->{oc_config}->{instance_specific_data}->{$componentConfNode}->{$instanceKey}->{url} = $url;
537             }
538             }
539             }
540             # select instance specific data
541 0 0         if(defined $config->{oc_config}->{instance_specific_data}->{$componentConfNode}->{$self->{instance}}){
542             $config->{oc_config}->{instance_specific_data}->{$componentConfNode} =
543 0           $config->{oc_config}->{instance_specific_data}->{$componentConfNode}->{$self->{instance}};
544             }
545             }
546             # in worse case 36 available IP addresses(see _generateYaml), make number smaller if more needed(max is 256)
547 0           $config->{ip_last_number} = int(rand(220));
548            
549 0 0         print "'Info: component_dirs' parameter is missing\n" if not defined $config->{component_dirs};
550 0 0         print "'Info: cluster_ip_range' parameter is missing\n" if not defined $config->{cluster_ip_range};
551 0 0         print "'Info: host' parameter is missing\n" if not defined $config->{host};
552              
553 0           $self->_getSecrets($config);
554              
555 0           return $config;
556             }
557              
558             sub _generateYaml{
559 0     0     my ($self, $params) = @_;
560              
561 0           my $dir = $params->{dir};
562 0           my $templateName = $params->{templateName};
563              
564 0 0 0       return if $dir =~ /init/ && $self->{omit} =~ /init/;
565 0 0 0       return if (defined $self->{specificYamlFile}) && ($templateName !~ $self->{specificYamlFile});
566             return if (defined $self->{componentIsAllowed}) &&
567 0 0 0       (not $self->{componentIsAllowed}->($templateName, $dir, $self->{cluster}, $self->{instance}));
568            
569 0           my $yamlText;
570 0           my $templatesYamlFilePath = "$self->{config}->{templates_yaml_dir}/$dir";
571 0 0 0       make_path $templatesYamlFilePath or die("Failed to create path: $templatesYamlFilePath") if !-d $templatesYamlFilePath;
572              
573             # $self->{templatesTTHash}->{"40-api"}->{"40-build-config-api"} = "40-api/clusterPublic/40-build-config-api";
574 0           my $templateTTFilePath = "$self->{templatesTTDir}/$self->{templatesTTHash}->{$dir}->{$templateName}";
575 0           $templateTTFilePath = $templateTTFilePath.".tt";
576            
577 0           eval { $self->{tt}->process($templateTTFilePath, $self->{config}, \$yamlText); };
  0            
578 0 0         if($@){
579 0           print "Error occured during generating yaml in: $dir $templateName\n", Dumper($@), "\n";
580 0           return;
581             }
582            
583 0           my $yamlHash;
584 0           eval { $yamlHash = Load($yamlText);};
  0            
585 0 0         if($@){
586 0           print "Error occured during conversion to yaml in: $dir $templateName\n", Dumper($@), "\n";
587 0           return;
588             }
589            
590 0 0 0       return if (defined $self->{ocResourceKinds}) && ($self->{ocResourceKinds} !~ $yamlHash->{kind});
591              
592 0           write_file("$templatesYamlFilePath/$templateName\.yaml", $yamlText);
593 0 0         $self->{config}->{ip_last_number}++ if $yamlHash->{kind} eq "Service";
594             }
595              
596             sub _getComponentConfigNodes{
597 0     0     my ($self, $config) = @_;
598            
599 0           my @componentConfigNodes;
600             my $componentConfigNodesString;
601 0 0 0       if((defined $config->{oc_config}) && (defined $config->{oc_config}->{instance_specific_data})){
602 0           foreach my $componentConfigNode (keys %{$config->{oc_config}->{instance_specific_data}}){
  0            
603 0           push @componentConfigNodes, $componentConfigNode;
604             }
605             }
606              
607 0           return \@componentConfigNodes;
608             }
609              
610             sub _getComponentFromTemplatesTTDir{
611 0     0     my ($self) = @_;
612              
613 0           my $componentDirs = "";
614 0           foreach my $dirPath (glob "$self->{templatesTTDir}/*") {
615 0 0         next if not -d $dirPath;
616 0           my @dirPathArr = split('/', $dirPath);
617 0           my $componentDir = $dirPathArr[-1];
618 0           $componentDir =~ s/^\d+-//;
619 0           $componentDirs .= $componentDir.";";
620             }
621 0 0         chop($componentDirs) if $componentDirs ne "";
622              
623 0           return $componentDirs;
624             }
625              
626             sub _getCurrentProject{
627 0     0     my ($self) = @_;
628              
629 0           my $projectCmdLine = qx/$self->{cliCommand} config current-context/;
630 0           my @projectCmdLineArr = split('/', $projectCmdLine);
631 0           my $project = $projectCmdLineArr[0];
632 0 0         $project = "unknown" if not defined $project;
633              
634 0           return $project;
635             }
636              
637             sub _getDefaultKinds{
638 0     0     return "PersistentVolumeClaim;StorageClass;VolumeSnapshot;ImageStream;BuildConfig;Deployment;DeploymentConfig;StatefulSet;Secret;ConfigMap;CronJob;Job;DaemonSet;ReplicaSet;ReplicationController;HorizontalPodAutoscaler;PodDisruptionBudget;Service;Route;Ingress;NetworkPolicy;ServiceAccount;ClusterRole;RoleBinding;ResourceQuota;LimitRange";
639             }
640              
641             sub _getSecrets{
642 0     0     my ($self, $config) = @_;
643              
644 0           my $secretJsonFileName = $self->{secretsJson};
645             # secrets files for all instances and all clusters e.g.: secrets/my-secret.txt
646 0           for my $dirFileName (File::Find::Rule->file()->name("*")->maxdepth(1)->in($self->{secretsDir})) {
647 0           my @dirFileNameArr = split('/', $dirFileName);
648 0 0         next if $dirFileNameArr[-1] eq $secretJsonFileName;
649 0           $self->_addSecretsToConfigHash($config, $dirFileName);
650             }
651              
652             # instance specific secrets files and for all clusters e.g.: secrets/instance/test/my-secret.txt
653 0           for my $dirFileName (File::Find::Rule->file()->name("*")->maxdepth(1)->in("$self->{secretsDir}/instance/$self->{instance}")) {
654 0           my @dirFileNameArr = split('/', $dirFileName);
655 0 0         next if $dirFileNameArr[-1] eq $secretJsonFileName;
656 0           $self->_addSecretsToConfigHash($config, $dirFileName);
657             }
658            
659 0           my $clusterSpecificSecretsDirExist = 0;
660 0           foreach my $dirPath (glob "$self->{secretsDir}/*") {
661 0 0         next if not -d $dirPath;
662 0           my @dirPathArr = split('/', $dirPath);
663 0           my $dirName = $dirPathArr[-1];
664 0 0         next if $dirName eq "instance";
665 0 0         $clusterSpecificSecretsDirExist = 1 if $dirName eq $self->{cluster};
666             }
667            
668             # secrets files for all instances and for specific cluster e.g.: secrets/clusterIntern/my_secret.txt
669 0 0         if($clusterSpecificSecretsDirExist){
670 0           for my $dirFileName (File::Find::Rule->file()->name("*")->maxdepth(1)->in("$self->{secretsDir}/$self->{cluster}")) {
671 0           my @dirFileNameArr = split('/', $dirFileName);
672 0 0         next if $dirFileNameArr[-1] eq $secretJsonFileName;
673 0           $self->_addSecretsToConfigHash($config, $dirFileName);
674             }
675             # secret files for specific instance and specific cluster e.g.: secrets/clusterIntern/instance/prod/my_secret.txt
676 0           for my $dirFileName (File::Find::Rule->file()->name("*")->maxdepth(1)->in("$self->{secretsDir}/$self->{cluster}/instance/$self->{instance}")) {
677 0           my @dirFileNameArr = split('/', $dirFileName);
678 0 0         next if $dirFileNameArr[-1] eq $secretJsonFileName;
679 0           $self->_addSecretsToConfigHash($config, $dirFileName);
680             }
681             }
682             # secrets json for all instances and all clusters
683 0           $config->{secrets_json} = {};
684 0           eval {
685 0           my $secretJson = read_file("$self->{secretsDir}/$secretJsonFileName");
686 0           my $secretJsonAllInstancesAllClustersHash = $self->{json}->utf8->decode($secretJson);
687 0           $self->_mergeSecretsJson($config->{secrets_json}, $secretJsonAllInstancesAllClustersHash);
688             };
689 0 0         if($clusterSpecificSecretsDirExist){
690             # secrets json for all instances and specific cluster
691 0           my $path = "$self->{secretsDir}/$self->{cluster}/$secretJsonFileName";
692 0           eval {
693 0           my $secretJsonClusterSpecific = read_file($path);
694 0           my $secretJsonClusterSpecificHash = $self->{json}->utf8->decode($secretJsonClusterSpecific);
695 0           $self->_mergeSecretsJson($config->{secrets_json}, $secretJsonClusterSpecificHash);
696             };
697             # secrets json for specific instance and specific cluster
698 0           $path = "$self->{secretsDir}/$self->{cluster}/instance/$self->{instance}/$secretJsonFileName";
699 0           eval {
700 0           my $secretJsonClusterSpecificInstanceSpecific = read_file($path);
701 0           my $secretJsonClusterSpecificInstanceSpecificHash = $self->{json}->utf8->decode($secretJsonClusterSpecificInstanceSpecific);
702 0           $self->_mergeSecretsJson($config->{secrets_json}, $secretJsonClusterSpecificInstanceSpecificHash);
703             };
704             }else{
705             # secrets json for specific instance and all clusters
706 0           my $path = "$self->{secretsDir}/instance/$self->{instance}/$secretJsonFileName";
707 0           eval {
708 0           my $secretJsonInstanceSpecific = read_file($path);
709 0           my $secretJsonInstanceSpecificHash = $self->{json}->utf8->decode($secretJsonInstanceSpecific);
710 0           $self->_mergeSecretsJson($config->{secrets_json}, $secretJsonInstanceSpecificHash);
711             };
712             }
713              
714 0           return;
715             }
716              
717             sub _loopDir {
718 0     0     my ($self, $dirToLoop, $extention, $injectedSubName, $params) = @_;
719              
720 0           my $templates = {};
721 0           for my $dirFileName (File::Find::Rule->file()->name("*.$extention")->in($dirToLoop)) {
722 0           $dirFileName =~ s/$dirToLoop\///;
723 0           my @dirFile = split('\/', $dirFileName);
724 0           my @dir = split('\-', $dirFile[0]);
725 0           my @file = split('\-', $dirFile[1]);
726             # e.g: $templates->{30-solr}->{dirNumber} = 30 # in order so sort as integer
727             # ->{20-build-config-solr.tt} = 20
728 0           $templates->{$dirFile[0]}->{dirNumber} = $dir[0];
729 0           $templates->{$dirFile[0]}->{$dirFile[1]} = $file[0];
730             }
731              
732 0           my @dirArray;
733 0 0 0       if((defined $self->{sortType}) && ($self->{sortType}) eq "alphabetic"){
734 0           foreach my $dir (sort { lc $templates->{$a}->{dirNumber} cmp lc $templates->{$b}->{dirNumber} } keys %{$templates}){
  0            
  0            
735 0           push @dirArray, $dir;
736             }
737             }else{
738             # numeric
739 1     1   7 no warnings 'numeric';
  1         1  
  1         203  
740 0           foreach my $dir (sort { $templates->{$a}->{dirNumber} <=> $templates->{$b}->{dirNumber} } keys %{$templates}){
  0            
  0            
741 0           push @dirArray, $dir;
742             }
743             }
744 0 0         @dirArray = reverse @dirArray if $injectedSubName eq "_deleteOc";
745              
746 0           foreach my $dir (@dirArray){
747 0 0         print "\nInstalling components from: $dir:\n" if $injectedSubName eq "_callOc";
748            
749 0           my @fileArray;
750 0 0 0       if((defined $self->{sortType}) && ($self->{sortType}) eq "alphabetic"){
751 0           foreach my $file (sort { lc $templates->{$dir}->{$a} cmp lc $templates->{$dir}->{$b} } keys %{$templates->{$dir}}){
  0            
  0            
752 0           push @fileArray, $file;
753             }
754             }else{
755             # numeric
756 1     1   4 no warnings 'numeric';
  1         9  
  1         1116  
757 0           foreach my $file (sort { $templates->{$dir}->{$a} <=> $templates->{$dir}->{$b} } keys %{$templates->{$dir}}){
  0            
  0            
758 0           push @fileArray, $file;
759             }
760             }
761 0 0         @fileArray = reverse @fileArray if $injectedSubName eq "_deleteOc";
762 0           foreach my $file (@fileArray){
763 0 0 0       next if $file eq "dirNumber" || $self->_skipComponent($dir);
764 0           my @fileArr = split('\.', $file);
765 0 0 0       if((not defined $fileArr[1]) && ($self->{config}->{allowed_clusters} !~ /$file/)){
766 0           print "Warning : Unknown cluster: $file\n";
767             }
768 0           my $injectedSub = \&$injectedSubName;
769 0           $injectedSub->($self, {dir => $dir, templateName => $fileArr[0], params => $params});
770             }
771             }
772             }
773              
774             sub _mergeSecretsJson{
775 0     0     my ($self, $jsonHashOriginal, $jsonHashAddition) = @_;
776            
777 0           foreach my $key (keys %{$jsonHashAddition}){
  0            
778 0           $jsonHashOriginal->{$key} = $jsonHashAddition->{$key}
779             }
780            
781 0           return;
782             }
783              
784             sub _removeInitFromComponentDirs{
785 0     0     my ($self) = @_;
786            
787 0           my @componentsYamlDirArray = split(';', $self->{config}->{component_dirs});
788 0           @componentsYamlDirArray = (grep {$_ !~ /init/} @componentsYamlDirArray);
  0            
789 0           $self->{config}->{component_dirs} = join( ';', @componentsYamlDirArray);
790             }
791              
792             sub _skipComponent{
793 0     0     my ($self, $dir) = @_;
794              
795             # ignore skiping if called by this flag
796 0 0         return 0 if defined $self->{yamlToTTconvertDir};
797            
798 0           my @installComponents = [];
799 0 0         if($dir =~ /init/){
800 0 0         if(defined $self->{config}->{init_component_dirs}){
801 0           @installComponents = split(';', $self->{config}->{init_component_dirs});
802             }
803 0           foreach my $installComponent (@installComponents){
804 0 0         return 0 if $dir =~ /$installComponent/;
805             }
806             }else{
807 0 0         if(defined $self->{config}->{standard_component_dirs}){
808 0           @installComponents = split(';', $self->{config}->{standard_component_dirs});
809             }
810 0           foreach my $installComponent (@installComponents){
811 0 0         return 0 if $dir =~ /$installComponent/;
812             }
813             }
814              
815 0           return 1;
816             }
817              
818             sub _validateInstance{
819 0     0     my ($self, $params) = @_;
820            
821 0           my @funcName = split /::/, (caller(0))[3];
822 0           my $dir = $params->{dir};
823 0           my $templateName = $params->{templateName};
824              
825 0           my $templateNameYaml = $templateName.".yaml";
826 0           my $yamlData = LoadFile("$self->{config}->{templates_yaml_dir}/$dir/$templateNameYaml");
827 0           my $ocName = $yamlData->{metadata}->{name};
828 0           my $ocKind = $yamlData->{kind};
829 0           my $ocJson = qx/$self->{cliCommand} get $ocKind $ocName -o json/;
830 0           my $ocHash = {};
831 0           eval {
832 0           $ocHash = $self->{json}->utf8->decode($ocJson);
833 0 0 0       if(($ocKind eq "Secret") && (ref($ocHash->{data}) eq 'HASH')){
834 0           foreach my $key (keys %{$ocHash->{data}}){
  0            
835 0           my $secret = decode_base64($ocHash->{data}->{$key});
836 0           $secret =~ s/\n//g;
837 0           $secret =~ s/\r//g;
838 0           $ocHash->{data}->{$key} = $secret;
839             }
840             }
841             };
842 0 0         print Dumper($@) if $@;
843            
844 0           my $templateYamlText = read_file("$self->{config}->{templates_yaml_dir}/$dir/$templateNameYaml");
845 0           my $yamlObj = YAML::Safe->new->boolean("JSON::PP");
846 0           my $templateHash = $yamlObj->Load($templateYamlText);
847 0           my $subParams = {"dir" => $dir, "templateName" => $templateName, "ocKind" => $ocKind, "ocName" => $ocName};
848 0 0         $ocHash = $self->{removeClutter}->($ocHash, $subParams) if defined $self->{removeClutter};
849 0 0         $templateHash = $self->{removeClutter}->($templateHash, $subParams) if defined $self->{removeClutter};
850            
851 0 0         if($ocKind eq "Secret"){
852 0 0 0       if((defined $templateHash->{data}) && (ref($templateHash->{data}) eq 'HASH')){
853 0           foreach my $key (keys %{$templateHash->{data}}){
  0            
854 0           my $dataBase64Encoded = $templateHash->{data}->{$key};
855 0           my $dataBase64Decoded = decode_base64($dataBase64Encoded);
856 0           $dataBase64Decoded =~ s/\n//g;
857 0           $dataBase64Decoded =~ s/\r//g;
858 0           $templateHash->{data}->{$key} = $dataBase64Decoded;
859             }
860             }
861             }
862            
863 0           $ocJson = $self->{json}->utf8->pretty->canonical->encode($ocHash);
864 0           my $templateJson = $self->{json}->utf8->pretty->canonical->encode($templateHash);
865 0           my @ocJsonArr = split /\n/, $ocJson;
866 0           my @templateJsonArr = split /\n/, $templateJson;
867 0           my $diff = diff \@ocJsonArr, \@templateJsonArr, { STYLE => "Table", CONTEXT => 0 };
868 0 0 0       if(($ocKind eq "Secret") && ($diff ne "")){
869 0           $diff = "+---+-----------------------------------------------------+---+---------------------------------+
870             * | different secret, not displayed * | different secret, not displayed |
871             +---+-----------------------------------------------------+---+---------------------------------+
872             ";
873             }
874 0           $diff =~ s/\\ No newline at end of file\s//g;
875            
876 0           my $diffStatus;
877 0 0 0       if((defined $diff) && ($diff ne "")){
878 0           $diffStatus = "MODIFIED";
879             }else{
880 0           $diffStatus = "OK";
881             }
882 0           my $line = "$dir/$templateNameYaml;$ocKind;$ocName;$diffStatus\n";
883 0           print $line;
884 0 0         print $diff if $diff ne "";
885 0           write_file($self->{validationReportFile}, {append => 1}, $line);
886             }
887              
888             1;
889              
890             __END__