File Coverage

blib/lib/OcToolkit.pm
Criterion Covered Total %
statement 50 588 8.5
branch 0 254 0.0
condition 0 73 0.0
subroutine 17 49 34.6
pod 6 11 54.5
total 73 975 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   214277 use v5.16; # or newer
  1         3  
14 1     1   3 use strict;
  1         1  
  1         16  
15 1     1   3 use warnings;
  1         3  
  1         62  
16              
17             our $VERSION = "1.14";
18              
19 1     1   4 use JSON::PP;
  1         1  
  1         67  
20 1     1   466 use Tie::IxHash;
  1         3264  
  1         24  
21 1     1   428 use Text::Diff;
  1         6332  
  1         47  
22 1     1   496 use Template;
  1         14783  
  1         29  
23 1     1   533 use File::Slurp;
  1         23135  
  1         57  
24 1     1   427 use File::Find::Rule;
  1         6639  
  1         6  
25 1     1   47 use File::Path qw(make_path rmtree);
  1         1  
  1         42  
26 1     1   482 use MIME::Base64 qw(encode_base64 decode_base64);
  1         668  
  1         57  
27 1     1   472 use YAML qw(LoadFile);
  1         5529  
  1         50  
28 1     1   428 use YAML::Safe;
  1         1531  
  1         61  
29              
30 1     1   515 use Data::Dumper;
  1         5921  
  1         3041  
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              
305 0           my $secretText = read_file($dirFileName);
306 0           $secretText =~ s/\n//g;
307 0           $secretText =~ s/\r//g;
308 0           $config->{secrets}->{$dirFileNameArr[-1]} = $secretText;
309            
310 0           my $secretTextBase64 = encode_base64($secretText);
311 0           $secretTextBase64 =~ s/\n//g;
312 0           $secretTextBase64 =~ s/\r//g;
313 0           $config->{secrets}->{base64}->{$dirFileNameArr[-1]} = $secretTextBase64;
314              
315 0           return;
316             }
317              
318             sub _backupInstance{
319 0     0     my ($self, $params) = @_;
320              
321 0           my $dir = $params->{dir};
322 0           my $templateName = $params->{templateName};
323              
324 0           my $instance = $self->{config}->{instance};
325 0           $self->_createDir("backups\/$instance\/$dir");
326 0           my $pathToYamlFile = "$self->{config}->{templates_yaml_dir}/$dir/$templateName".".yaml";
327 0           my $templateData = LoadFile($pathToYamlFile);
328            
329 0           my $yamlText;
330 0 0 0       if((defined $self->{advanceFeatures}) &&
      0        
331             ($self->{advanceFeatures} =~ /removeClutter/) &&
332             (defined $self->{removeClutterBackup})){
333 0           print "$self->{cliCommand} get $templateData->{kind} $templateData->{metadata}->{name} -o json\n";
334 0           my $ocJson = qx/$self->{cliCommand} get $templateData->{kind} $templateData->{metadata}->{name} -o json/;
335 0           my $ocHash = $self->{json}->utf8->decode($ocJson);
336 0           my $subParams = {"ocKind" => $templateData->{kind}, "ocName" => $templateData->{metadata}->{name}};
337 0           $ocHash = $self->{removeClutterBackup}->($ocHash, $subParams);
338 0           my $yamlSaveObj = YAML::Safe->new->boolean("JSON::PP");
339 0           $yamlText = $yamlSaveObj->Dump($ocHash);
340 0           $yamlText =~ s/---\n//;
341             }else{
342 0           print "$self->{cliCommand} get $templateData->{kind} $templateData->{metadata}->{name} -o yaml\n";
343 0           $yamlText = qx/$self->{cliCommand} get $templateData->{kind} $templateData->{metadata}->{name} -o yaml/;
344             }
345              
346 0           write_file("$self->{projectDir}backups\/$instance\/$dir/$templateName".".yaml", $yamlText);
347             }
348              
349             sub _callOc{
350 0     0     my ($self, $params) = @_;
351            
352 0           my @funcName = split /::/, (caller(0))[3];
353 0           my $dir = $params->{dir};
354 0           my $templateName = $params->{templateName};
355 0           my $customParams = $params->{params}->{$funcName[-1]};
356              
357             return if (defined $self->{componentIsAllowed}) &&
358 0 0 0       (not $self->{componentIsAllowed}->($templateName, $dir, $self->{cluster}, $self->{instance}));
359              
360 0           my $templateNameYaml = $templateName.".yaml";
361 0           my $pathAndFile = $self->{config}->{templates_yaml_dir}."\/$dir\/$templateNameYaml";
362 0           my $yamlData = LoadFile($pathAndFile);
363              
364 0 0         if($yamlData->{kind} eq "CronJob"){
365 0           print "$self->{cliCommand} apply -f $pathAndFile\n";
366 0           qx/$self->{cliCommand} apply -f $pathAndFile/;
367             }else{
368 0           print "$self->{cliCommand} create -f $pathAndFile\n";
369 0           qx/$self->{cliCommand} create -f $pathAndFile/;
370             }
371             }
372              
373             sub _clearDir{
374 0     0     my ($self, $dir) = @_;
375              
376 0           rmtree $dir;
377 0           make_path $dir;
378             }
379              
380             sub _convertYamlToTTExtention{
381 0     0     my ($self, $params) = @_;
382              
383 0           my @funcName = split /::/, (caller(0))[3];
384 0           my $dir = $params->{dir};
385 0           my $templateName = $params->{templateName};
386 0           my $customParams = $params->{params}->{$funcName[-1]};
387 0           my $yamlToTTconvertDir = $customParams->{yamlToTTconvertDir};
388            
389 0           my $path = "$yamlToTTconvertDir/$dir";
390 0           qx/mv $path\/$templateName\.yaml $path\/$templateName\.tt 2>&1/;
391             }
392              
393             sub _createTemplatesTTDirHash{
394 0     0     my ($self, $params) = @_;
395            
396 0           my $dir = $params->{dir};
397 0           my $templateName = $params->{templateName};
398              
399             # $self->{templatesTTHash}->{"40-api"}->{"40-build-config-api"} = "40-api/clusterPublic/40-build-config-api";
400 0 0         if(not -d "$self->{templatesTTDir}/$dir/$templateName"){
    0          
401 0           $self->{templatesTTHash}->{$dir}->{$templateName} = "$dir/$templateName";
402             }elsif($templateName eq $self->{cluster}){
403 0           for my $dirFileName (File::Find::Rule->file()->name("*")->in("$self->{templatesTTDir}/$dir/$self->{cluster}")) {
404 0           my @dirFileNameArr = split('/', $dirFileName);
405 0           my $templName = substr($dirFileNameArr[-1], 0, -3);
406 0           $self->{templatesTTHash}->{$dir}->{$templName} = "$dir/$self->{cluster}/$templName";
407             }
408             }
409              
410 0           return;
411             }
412              
413             sub _createDir{
414 0     0     my ($self, $relativeDirPath) = @_;
415 0           qx/mkdir $self->{projectDir}$relativeDirPath 2>&1/;
416 0           return;
417             }
418              
419             sub _createYamlFiles{
420 0     0     my ($self) = @_;
421            
422             # $self->{templatesTTHash}->{"40-api"}->{"40-build-config-api"} = "40-api/clusterPublic/40-build-config-api.tt";
423 0           my $dirs = $self->{templatesTTHash};
424 0 0 0       if((defined $self->{sortType}) && ($self->{sortType}) eq "alphabetic"){
425             # alphabetic
426 0           foreach my $dir (sort {lc($a) cmp lc($b)} keys %{$dirs}){
  0            
  0            
427 0           my $files = $dirs->{$dir};
428 0           foreach my $templateName (sort {lc($a) cmp lc($b)} keys %{$files}){
  0            
  0            
429 0           $self->_generateYaml({dir => $dir, templateName => $templateName});
430             }
431             }
432             }else{
433             # numeric
434 1     1   18 no warnings 'numeric';
  1         1  
  1         2288  
435 0           foreach my $dir (sort {lc($a) <=> lc($b)} keys %{$dirs}){
  0            
  0            
436 0           my $files = $dirs->{$dir};
437 0           foreach my $templateName (sort {lc($a) <=> lc($b)} keys %{$files}){
  0            
  0            
438 0           $self->_generateYaml({dir => $dir, templateName => $templateName});
439             }
440             }
441             }
442              
443 0           return;
444             }
445              
446             sub _deleteOc{
447 0     0     my ($self, $params) = @_;
448              
449 0           my $dir = $params->{dir};
450 0           my $templateName = $params->{templateName};
451              
452 0           my $data = LoadFile("$self->{config}->{templates_yaml_dir}/$dir/$templateName".".yaml");
453 0           print "$self->{cliCommand} delete $data->{kind} $data->{metadata}->{name}\n";
454 0           qx/$self->{cliCommand} delete $data->{kind} $data->{metadata}->{name}/;
455             }
456              
457             sub _generateConfig{
458 0     0     my ($self) = @_;
459              
460 0           my $ocConfigJson = read_file($self->{ocConfigFile});
461 0           my $config->{oc_config} = $self->{json}->utf8->decode($ocConfigJson);
462              
463 0 0         $config = $self->{addFlagValuesToConfig}->($config) if defined $self->{addFlagValuesToConfig};
464              
465             # set/generate instance specific names
466 0           foreach my $entry (keys %{$config->{oc_config}->{instance_specific_name}}){
  0            
467 0           $config->{oc_config}->{instance_specific_name}->{$entry} .= "-$self->{instance}";
468             }
469              
470 0           $config->{allowed_clusters} = $config->{oc_config}->{project}->{allowed_clusters};
471 0 0         if(not defined $config->{allowed_clusters}){
472 0           print "INFO: oc_config->project->allowed_clusters json node is empty. Marking 'allowed_clusters' as '$self->{cluster}'\n";
473 0           $config->{allowed_clusters} = $self->{cluster};
474             }else{
475 0 0         print "Warning: Unknown cluster $self->{cluster}\n" if $config->{allowed_clusters} !~ $self->{cluster};
476             }
477              
478 0           $config->{cluster_ip_range} = $config->{oc_config}->{project}->{cluster_ip_range};
479 0 0         $config->{cluster_ip_range} = $self->{clusterIpRange} if defined $self->{clusterIpRange};
480 0           $config->{project_name} = $config->{oc_config}->{project}->{name};
481 0 0         $config->{project_name} = $self->{projectName} if defined $self->{projectName};
482 0           $config->{host} = $config->{oc_config}->{project}->{host};
483 0 0         $config->{host} = $self->{host} if defined $self->{host};
484 0           $config->{namespace} = $config->{oc_config}->{project}->{namespace};
485 0 0         $config->{namespace} = $self->_getCurrentProject() if not defined $config->{namespace};
486 0 0         $config->{namespace} = $self->{namespace} if defined $self->{namespace};# from -n flag
487             # default component dirs are set here, dirs in 'templates_tt' not set as default will be omitted
488 0           $config->{component_dirs} = $config->{oc_config}->{project}->{component_dirs};
489 0 0         $config->{component_dirs} = $self->{componentDirs} if defined $self->{componentDirs};
490 0 0         $config->{component_dirs} = $self->_getComponentFromTemplatesTTDir() if not defined $config->{component_dirs};
491             # component dirs can contains numbers e.g.: '50-solr' so regexp match is used =>
492             # separate 'init' components in order to avoid false matches (e.g.: 20-init-api vs 50-api when 'api' searched)
493 0           my @componentDirs = split(';', $config->{component_dirs});
494 0           my $standardComponentDirs = "";
495 0           my $initComponentDirs = "";
496 0           foreach my $componentDir (@componentDirs){
497 0 0         if($componentDir =~ /init/){
498 0           $initComponentDirs .= $componentDir.";";
499             }else{
500 0           $standardComponentDirs .= $componentDir.";";
501             }
502             }
503 0           chop($initComponentDirs);
504 0           chop($standardComponentDirs);
505 0           $config->{init_component_dirs} = $initComponentDirs;
506 0           $config->{standard_component_dirs} = $standardComponentDirs;
507 0           $config->{oc_resource_kinds} = $config->{oc_config}->{project}->{oc_resource_kinds};
508 0 0         $config->{oc_resource_kinds} = $self->{ocResourceKinds} if defined $self->{ocResourceKinds};
509 0 0         $config->{oc_resource_kinds} = $self->_getDefaultKinds() if not defined $config->{oc_resource_kinds};
510 0           $config->{templates_yaml_dir} = $self->{templatesYamlDir};
511 0           $config->{templates_tt_dir} = $self->{templatesTTDir};
512 0           $config->{cluster_camelcase} = $self->{cluster};
513 0           $config->{cluster} = lc $self->{cluster};
514 0           $config->{instance} = lc $self->{instance};
515 0           $config->{instance_capitalized_first} = ucfirst $config->{instance};
516 0 0         $config->{url_prefix} = $self->{urlPrefix} if defined $self->{urlPrefix};
517 0 0         $config->{cluster_base_address} = $self->{clusterBaseAddress} if defined $self->{clusterBaseAddress};
518            
519 0           my $componentConfigNodes = $self->_getComponentConfigNodes($config);
520 0           foreach my $componentConfNode (@$componentConfigNodes){
521             # set/generate default urls
522 0 0 0       if((defined $config->{oc_config}->{instance_specific_data}->{$componentConfNode}) &&
523             (ref($config->{oc_config}->{instance_specific_data}->{$componentConfNode}) eq 'HASH')){
524 0           foreach my $instanceKey (keys %{$config->{oc_config}->{instance_specific_data}->{$componentConfNode}}){
  0            
525 0 0         if(not defined $config->{oc_config}->{instance_specific_data}->{$componentConfNode}->{$instanceKey}->{url}){
526 0           my $componentNameKebab = $componentConfNode;
527 0           $componentNameKebab =~ s/_/\-/g;
528 0           my $lcInstanceKey = lc $instanceKey;
529 0           my $url = "";
530 0 0         if(defined $self->{generateUrl}){
531             $url = $self->{generateUrl}->($config->{url_prefix},
532             $config->{project_name},
533             $componentNameKebab,
534             $lcInstanceKey,
535             $config->{cluster_base_address},
536 0           $config->{host});
537             }
538 0           $config->{oc_config}->{instance_specific_data}->{$componentConfNode}->{$instanceKey}->{url} = $url;
539             }
540             }
541             }
542             # select instance specific data
543 0 0         if(defined $config->{oc_config}->{instance_specific_data}->{$componentConfNode}->{$self->{instance}}){
544             $config->{oc_config}->{instance_specific_data}->{$componentConfNode} =
545 0           $config->{oc_config}->{instance_specific_data}->{$componentConfNode}->{$self->{instance}};
546             }
547             }
548             # in worse case 36 available IP addresses(see _generateYaml), make number smaller if more needed(max is 256)
549 0           $config->{ip_last_number} = int(rand(220));
550            
551 0 0         print "'Info: component_dirs' parameter is missing\n" if not defined $config->{component_dirs};
552 0 0         print "'Info: cluster_ip_range' parameter is missing\n" if not defined $config->{cluster_ip_range};
553 0 0         print "'Info: host' parameter is missing\n" if not defined $config->{host};
554              
555 0           $self->_getSecrets($config);
556              
557 0           return $config;
558             }
559              
560             sub _generateYaml{
561 0     0     my ($self, $params) = @_;
562              
563 0           my $dir = $params->{dir};
564 0           my $templateName = $params->{templateName};
565              
566 0 0 0       return if $dir =~ /init/ && $self->{omit} =~ /init/;
567 0 0 0       return if (defined $self->{specificYamlFile}) && ($templateName !~ $self->{specificYamlFile});
568             return if (defined $self->{componentIsAllowed}) &&
569 0 0 0       (not $self->{componentIsAllowed}->($templateName, $dir, $self->{cluster}, $self->{instance}));
570            
571 0           my $yamlText;
572 0           my $templatesYamlFilePath = "$self->{config}->{templates_yaml_dir}/$dir";
573 0 0 0       make_path $templatesYamlFilePath or die("Failed to create path: $templatesYamlFilePath") if !-d $templatesYamlFilePath;
574              
575             # $self->{templatesTTHash}->{"40-api"}->{"40-build-config-api"} = "40-api/clusterPublic/40-build-config-api";
576 0           my $templateTTFilePath = "$self->{templatesTTDir}/$self->{templatesTTHash}->{$dir}->{$templateName}";
577 0           $templateTTFilePath = $templateTTFilePath.".tt";
578            
579 0           eval { $self->{tt}->process($templateTTFilePath, $self->{config}, \$yamlText); };
  0            
580 0 0         if($@){
581 0           print "Error occured during generating yaml in: $dir $templateName\n", Dumper($@), "\n";
582 0           return;
583             }
584            
585 0           my $yamlHash;
586 0           eval { $yamlHash = Load($yamlText);};
  0            
587 0 0         if($@){
588 0           print "Error occured during conversion to yaml in: $dir $templateName\n", Dumper($@), "\n";
589 0           return;
590             }
591            
592 0 0 0       return if (defined $self->{ocResourceKinds}) && ($self->{ocResourceKinds} !~ $yamlHash->{kind});
593              
594 0           write_file("$templatesYamlFilePath/$templateName\.yaml", $yamlText);
595 0 0         $self->{config}->{ip_last_number}++ if $yamlHash->{kind} eq "Service";
596             }
597              
598             sub _getComponentConfigNodes{
599 0     0     my ($self, $config) = @_;
600            
601 0           my @componentConfigNodes;
602             my $componentConfigNodesString;
603 0 0 0       if((defined $config->{oc_config}) && (defined $config->{oc_config}->{instance_specific_data})){
604 0           foreach my $componentConfigNode (keys %{$config->{oc_config}->{instance_specific_data}}){
  0            
605 0           push @componentConfigNodes, $componentConfigNode;
606             }
607             }
608              
609 0           return \@componentConfigNodes;
610             }
611              
612             sub _getComponentFromTemplatesTTDir{
613 0     0     my ($self) = @_;
614              
615 0           my $componentDirs = "";
616 0           foreach my $dirPath (glob "$self->{templatesTTDir}/*") {
617 0 0         next if not -d $dirPath;
618 0           my @dirPathArr = split('/', $dirPath);
619 0           my $componentDir = $dirPathArr[-1];
620 0           $componentDir =~ s/^\d+-//;
621 0           $componentDirs .= $componentDir.";";
622             }
623 0 0         chop($componentDirs) if $componentDirs ne "";
624              
625 0           return $componentDirs;
626             }
627              
628             sub _getCurrentProject{
629 0     0     my ($self) = @_;
630              
631 0           my $projectCmdLine = qx/$self->{cliCommand} config current-context/;
632 0           my @projectCmdLineArr = split('/', $projectCmdLine);
633 0           my $project = $projectCmdLineArr[0];
634 0 0         $project = "unknown" if not defined $project;
635              
636 0           return $project;
637             }
638              
639             sub _getDefaultKinds{
640 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";
641             }
642              
643             sub _getSecrets{
644 0     0     my ($self, $config) = @_;
645              
646 0           my $secretJsonFileName = $self->{secretsJson};
647             # secrets files for all instances and all clusters e.g.: secrets/my-secret.txt
648 0           for my $dirFileName (File::Find::Rule->file()->name("*")->in($self->{secretsDir})) {
649 0           my @dirFileNameArr = split('/', $dirFileName);
650 0 0         next if $dirFileNameArr[-1] eq $secretJsonFileName;
651 0 0         $self->_addSecretsToConfigHash($config, $dirFileName) if scalar @dirFileNameArr == 2;
652             }
653              
654             # instance specific secrets files and for all clusters e.g.: secrets/instance/test/my-secret.txt
655 0           for my $dirFileName (File::Find::Rule->file()->name("*")->in("$self->{secretsDir}/instance/$self->{instance}")) {
656 0           my @dirFileNameArr = split('/', $dirFileName);
657 0 0         next if $dirFileNameArr[-1] eq $secretJsonFileName;
658 0 0         $self->_addSecretsToConfigHash($config, $dirFileName) if scalar @dirFileNameArr == 4;
659             }
660            
661 0           my $clusterSpecificSecretsDirExist = 0;
662 0           foreach my $dirPath (glob "$self->{secretsDir}/*") {
663 0 0         next if not -d $dirPath;
664 0           my @dirPathArr = split('/', $dirPath);
665 0           my $dirName = $dirPathArr[1];
666 0 0         next if $dirName eq "instance";
667 0 0         $clusterSpecificSecretsDirExist = 1 if $dirName eq $self->{cluster};
668             }
669              
670             # secrets files for all instances and for specific cluster e.g.: secrets/clusterIntern/my_secret.txt
671 0 0         if($clusterSpecificSecretsDirExist){
672 0           for my $dirFileName (File::Find::Rule->file()->name("*")->in("$self->{secretsDir}/$self->{cluster}")) {
673 0           my @dirFileNameArr = split('/', $dirFileName);
674 0 0         next if $dirFileNameArr[-1] eq $secretJsonFileName;
675 0 0         $self->_addSecretsToConfigHash($config, $dirFileName) if scalar @dirFileNameArr == 3;
676             }
677             # secret files for specific instance and specific cluster e.g.: secrets/clusterIntern/instance/prod/my_secret.txt
678 0           for my $dirFileName (File::Find::Rule->file()->name("*")->in("$self->{secretsDir}/$self->{cluster}/instance/$self->{instance}")) {
679 0           my @dirFileNameArr = split('/', $dirFileName);
680 0 0         next if $dirFileNameArr[-1] eq $secretJsonFileName;
681 0 0         $self->_addSecretsToConfigHash($config, $dirFileName) if scalar @dirFileNameArr == 5;
682             }
683             }
684              
685             # secrets json for all instances and all clusters
686 0           my $secretJson = read_file("$self->{secretsDir}/$secretJsonFileName");
687 0           $config->{secrets_json} = $self->{json}->utf8->decode($secretJson);
688 0 0         if($clusterSpecificSecretsDirExist){
689             # secrets json for all instances and specific cluster
690 0           my $path = "$self->{secretsDir}/$self->{cluster}/$secretJsonFileName";
691 0           my $secretJsonClusterSpecific = read_file($path);
692 0           my $secretJsonClusterSpecificHash = $self->{json}->utf8->decode($secretJsonClusterSpecific);
693 0           $self->_mergeSecretsJson($config->{secrets_json}, $secretJsonClusterSpecificHash);
694            
695             # secrets json for specific instance and specific cluster
696 0           $path = "$self->{secretsDir}/$self->{cluster}/instance/$self->{instance}/$secretJsonFileName";
697 0           my $secretJsonClusterSpecificInstanceSpecific = read_file($path);
698 0           my $secretJsonClusterSpecificInstanceSpecificHash = $self->{json}->utf8->decode($secretJsonClusterSpecificInstanceSpecific);
699 0           $self->_mergeSecretsJson($config->{secrets_json}, $secretJsonClusterSpecificInstanceSpecificHash);
700             }else{
701             # secrets json for specific instance and all clusters
702 0           my $path = "$self->{secretsDir}/instance/$self->{instance}/$secretJsonFileName";
703 0           my $secretJsonInstanceSpecific = read_file($path);
704 0           my $secretJsonInstanceSpecificHash = $self->{json}->utf8->decode($secretJsonInstanceSpecific);
705 0           $self->_mergeSecretsJson($config->{secrets_json}, $secretJsonInstanceSpecificHash);
706             }
707              
708 0           return;
709             }
710              
711             sub _loopDir {
712 0     0     my ($self, $dirToLoop, $extention, $injectedSubName, $params) = @_;
713              
714 0           my $templates = {};
715 0           for my $dirFileName (File::Find::Rule->file()->name("*.$extention")->in($dirToLoop)) {
716 0           $dirFileName =~ s/$dirToLoop\///;
717 0           my @dirFile = split('\/', $dirFileName);
718 0           my @dir = split('\-', $dirFile[0]);
719 0           my @file = split('\-', $dirFile[1]);
720             # e.g: $templates->{30-solr}->{dirNumber} = 30 # in order so sort as integer
721             # ->{20-build-config-solr.tt} = 20
722 0           $templates->{$dirFile[0]}->{dirNumber} = $dir[0];
723 0           $templates->{$dirFile[0]}->{$dirFile[1]} = $file[0];
724             }
725              
726 0           my @dirArray;
727 0 0 0       if((defined $self->{sortType}) && ($self->{sortType}) eq "alphabetic"){
728 0           foreach my $dir (sort { lc $templates->{$a}->{dirNumber} cmp lc $templates->{$b}->{dirNumber} } keys %{$templates}){
  0            
  0            
729 0           push @dirArray, $dir;
730             }
731             }else{
732             # numeric
733 1     1   6 no warnings 'numeric';
  1         2  
  1         152  
734 0           foreach my $dir (sort { $templates->{$a}->{dirNumber} <=> $templates->{$b}->{dirNumber} } keys %{$templates}){
  0            
  0            
735 0           push @dirArray, $dir;
736             }
737             }
738 0 0         @dirArray = reverse @dirArray if $injectedSubName eq "_deleteOc";
739              
740 0           foreach my $dir (@dirArray){
741 0 0         print "\nInstalling components from: $dir:\n" if $injectedSubName eq "_callOc";
742            
743 0           my @fileArray;
744 0 0 0       if((defined $self->{sortType}) && ($self->{sortType}) eq "alphabetic"){
745 0           foreach my $file (sort { lc $templates->{$dir}->{$a} cmp lc $templates->{$dir}->{$b} } keys %{$templates->{$dir}}){
  0            
  0            
746 0           push @fileArray, $file;
747             }
748             }else{
749             # numeric
750 1     1   15 no warnings 'numeric';
  1         1  
  1         1174  
751 0           foreach my $file (sort { $templates->{$dir}->{$a} <=> $templates->{$dir}->{$b} } keys %{$templates->{$dir}}){
  0            
  0            
752 0           push @fileArray, $file;
753             }
754             }
755 0 0         @fileArray = reverse @fileArray if $injectedSubName eq "_deleteOc";
756 0           foreach my $file (@fileArray){
757 0 0 0       next if $file eq "dirNumber" || $self->_skipComponent($dir);
758 0           my @fileArr = split('\.', $file);
759 0 0 0       if((not defined $fileArr[1]) && ($self->{config}->{allowed_clusters} !~ /$file/)){
760 0           print "Warning : Unknown cluster: $file\n";
761             }
762 0           my $injectedSub = \&$injectedSubName;
763 0           $injectedSub->($self, {dir => $dir, templateName => $fileArr[0], params => $params});
764             }
765             }
766             }
767              
768             sub _mergeSecretsJson{
769 0     0     my ($self, $jsonHashOriginal, $jsonHashAddition) = @_;
770            
771 0           foreach my $key (keys %{$jsonHashAddition}){
  0            
772 0           $jsonHashOriginal->{$key} = $jsonHashAddition->{$key}
773             }
774            
775 0           return;
776             }
777              
778             sub _removeInitFromComponentDirs{
779 0     0     my ($self) = @_;
780            
781 0           my @componentsYamlDirArray = split(';', $self->{config}->{component_dirs});
782 0           @componentsYamlDirArray = (grep {$_ !~ /init/} @componentsYamlDirArray);
  0            
783 0           $self->{config}->{component_dirs} = join( ';', @componentsYamlDirArray);
784             }
785              
786             sub _skipComponent{
787 0     0     my ($self, $dir) = @_;
788              
789             # ignore skiping if called by this flag
790 0 0         return 0 if defined $self->{yamlToTTconvertDir};
791            
792 0           my @installComponents = [];
793 0 0         if($dir =~ /init/){
794 0 0         if(defined $self->{config}->{init_component_dirs}){
795 0           @installComponents = split(';', $self->{config}->{init_component_dirs});
796             }
797 0           foreach my $installComponent (@installComponents){
798 0 0         return 0 if $dir =~ /$installComponent/;
799             }
800             }else{
801 0 0         if(defined $self->{config}->{standard_component_dirs}){
802 0           @installComponents = split(';', $self->{config}->{standard_component_dirs});
803             }
804 0           foreach my $installComponent (@installComponents){
805 0 0         return 0 if $dir =~ /$installComponent/;
806             }
807             }
808              
809 0           return 1;
810             }
811              
812             sub _validateInstance{
813 0     0     my ($self, $params) = @_;
814            
815 0           my @funcName = split /::/, (caller(0))[3];
816 0           my $dir = $params->{dir};
817 0           my $templateName = $params->{templateName};
818              
819 0           my $templateNameYaml = $templateName.".yaml";
820 0           my $yamlData = LoadFile("$self->{config}->{templates_yaml_dir}/$dir/$templateNameYaml");
821 0           my $ocName = $yamlData->{metadata}->{name};
822 0           my $ocKind = $yamlData->{kind};
823 0           my $ocJson = qx/$self->{cliCommand} get $ocKind $ocName -o json/;
824 0           my $ocHash = {};
825 0           eval {
826 0           $ocHash = $self->{json}->utf8->decode($ocJson);
827 0 0 0       if(($ocKind eq "Secret") && (ref($ocHash->{data}) eq 'HASH')){
828 0           foreach my $key (keys %{$ocHash->{data}}){
  0            
829 0           my $secret = decode_base64($ocHash->{data}->{$key});
830 0           $secret =~ s/\n//g;
831 0           $secret =~ s/\r//g;
832 0           $ocHash->{data}->{$key} = $secret;
833             }
834             }
835             };
836 0 0         print Dumper($@) if $@;
837            
838 0           my $templateYamlText = read_file("$self->{config}->{templates_yaml_dir}/$dir/$templateNameYaml");
839 0           my $yamlObj = YAML::Safe->new->boolean("JSON::PP");
840 0           my $templateHash = $yamlObj->Load($templateYamlText);
841 0           my $subParams = {"dir" => $dir, "templateName" => $templateName, "ocKind" => $ocKind, "ocName" => $ocName};
842 0 0         $ocHash = $self->{removeClutter}->($ocHash, $subParams) if defined $self->{removeClutter};
843 0 0         $templateHash = $self->{removeClutter}->($templateHash, $subParams) if defined $self->{removeClutter};
844            
845 0 0         if($ocKind eq "Secret"){
846 0 0 0       if((defined $templateHash->{data}) && (ref($templateHash->{data}) eq 'HASH')){
847 0           foreach my $key (keys %{$templateHash->{data}}){
  0            
848 0           my $dataBase64Encoded = $templateHash->{data}->{$key};
849 0           my $dataBase64Decoded = decode_base64($dataBase64Encoded);
850 0           $dataBase64Decoded =~ s/\n//g;
851 0           $dataBase64Decoded =~ s/\r//g;
852 0           $templateHash->{data}->{$key} = $dataBase64Decoded;
853             }
854             }
855             }
856            
857 0           $ocJson = $self->{json}->utf8->pretty->canonical->encode($ocHash);
858 0           my $templateJson = $self->{json}->utf8->pretty->canonical->encode($templateHash);
859 0           my @ocJsonArr = split /\n/, $ocJson;
860 0           my @templateJsonArr = split /\n/, $templateJson;
861 0           my $diff = diff \@ocJsonArr, \@templateJsonArr, { STYLE => "Table", CONTEXT => 0 };
862 0 0 0       if(($ocKind eq "Secret") && ($diff ne "")){
863 0           $diff = "+---+-----------------------------------------------------+---+---------------------------------+
864             * | different secret, not displayed * | different secret, not displayed |
865             +---+-----------------------------------------------------+---+---------------------------------+
866             ";
867             }
868 0           $diff =~ s/\\ No newline at end of file\s//g;
869            
870 0           my $diffStatus;
871 0 0 0       if((defined $diff) && ($diff ne "")){
872 0           $diffStatus = "MODIFIED";
873             }else{
874 0           $diffStatus = "OK";
875             }
876 0           my $line = "$dir/$templateNameYaml;$ocKind;$ocName;$diffStatus\n";
877 0           print $line;
878 0 0         print $diff if $diff ne "";
879 0           write_file($self->{validationReportFile}, {append => 1}, $line);
880             }
881              
882             1;
883              
884             __END__