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