View Javadoc
1   /*
2    * Copyright (c) 2002-2020, City of Paris
3    * All rights reserved.
4    *
5    * Redistribution and use in source and binary forms, with or without
6    * modification, are permitted provided that the following conditions
7    * are met:
8    *
9    *  1. Redistributions of source code must retain the above copyright notice
10   *     and the following disclaimer.
11   *
12   *  2. Redistributions in binary form must reproduce the above copyright notice
13   *     and the following disclaimer in the documentation and/or other materials
14   *     provided with the distribution.
15   *
16   *  3. Neither the name of 'Mairie de Paris' nor 'Lutece' nor the names of its
17   *     contributors may be used to endorse or promote products derived from
18   *     this software without specific prior written permission.
19   *
20   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
24   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30   * POSSIBILITY OF SUCH DAMAGE.
31   *
32   * License 1.0
33   */
34  package fr.paris.lutece.plugins.appcenter.modules.fastdeployapplication.service.ansible;
35  
36  import java.io.IOException;
37  import java.util.ArrayList;
38  import java.util.HashMap;
39  import java.util.List;
40  import java.util.stream.Collector;
41  import java.util.stream.Collectors;
42  
43  import org.antlr.runtime.tree.DOTTreeGenerator;
44  import org.apache.commons.collections.CollectionUtils;
45  
46  import com.jayway.jsonpath.Configuration;
47  import com.jayway.jsonpath.DocumentContext;
48  import com.jayway.jsonpath.JsonPath;
49  import com.jayway.jsonpath.PathNotFoundException;
50  import com.jayway.jsonpath.TypeRef;
51  import com.jayway.jsonpath.spi.json.JacksonJsonProvider;
52  import com.jayway.jsonpath.spi.mapper.JacksonMappingProvider;
53  
54  import fr.paris.lutece.plugins.appcenter.modules.fastdeployapplication.util.ConstanteUtils;
55  import fr.paris.lutece.plugins.appcenter.modules.fastdeployapplication.util.MapperJsonUtil;
56  import fr.paris.lutece.portal.business.style.Theme;
57  import fr.paris.lutece.portal.service.util.AppLogService;
58  import fr.paris.lutece.portal.service.util.AppPropertiesService;
59  import fr.paris.lutece.util.httpaccess.HttpAccess;
60  import fr.paris.lutece.util.httpaccess.HttpAccessException;
61  import fr.paris.lutece.util.signrequest.BasicAuthorizationAuthenticator;
62  import fr.paris.lutece.util.signrequest.RequestAuthenticator;
63  
64  public class AWXService
65  {
66  
67      private static final String PROPERTY_AWX_BASE_URL = "appcenter.modules.fastdeployapplication.awx.baseurl";
68      private static final String PROPERTY_AWX_ACCOUNT_LOGIN = "appcenter.modules.fastdeployapplication.awx.account.login";
69      private static final String PROPERTY_AWX_ACCOUNT_PWD = "appcenter.modules.fastdeployapplication.awx.account.password";
70      private static final String PROPERTY_AWX_API_MANAGER_HEADER_KEY = "appcenter.modules.fastdeployapplication.awx.headerApiKeyName";
71      private static final String PROPERTY_AWX_API_MANAGER_HEADER_KEY_VALUE = "appcenter.modules.fastdeployapplication.awx.headerApiKeyValue";
72  
73      // private static final String AWX_SEARCH_URL = "/api/v2/groups/?name__exact=";
74      // private static final String AWX_HOSTS_URL = "/api/v2/groups/$id/hosts/?page_size=100";
75      private static final String AWX_HOSTS_URL = "/api/v2/inventories/$id/hosts/?page_size=100&name__istartswith=$code-";
76  
77      private static final String AWX_JOB_TEMPLATES = "/api/v2/job_templates/";
78      private static final String CACHE_KEY_JOB_TEMPLATES = "job_templates";
79      private static final String AWX_INVENTORY_ID_URL = "/api/v2/inventories/fastdeploy++Default/";
80      private static final String AWX_FACTS_URL = "/api/v2/hosts/";
81  
82      private static InventoryAWXCacheService _inventoryCache;
83      private static JobTemplateAWXCacheService _jobTemplateCache;
84  
85      private static AWXService _instance;
86      private static Configuration _conf;
87  
88      public synchronized static AWXService getService( )
89      {
90          if ( _instance == null )
91          {
92              _instance = new AWXService( );
93              _inventoryCache = new InventoryAWXCacheService( );
94              // forced enable cache
95              _inventoryCache.enableCache( true );
96              _jobTemplateCache = new JobTemplateAWXCacheService( );
97              // forced enable cache
98              _jobTemplateCache.enableCache( true );
99              _conf = Configuration.builder( ).mappingProvider( new JacksonMappingProvider( ) ).jsonProvider( new JacksonJsonProvider( ) ).build( );
100 
101         }
102 
103         return _instance;
104     }
105 
106     public List<AWXHost> getHosts( String strCodeApplication )
107     {
108 
109         List<AWXHost> listHosts = null;
110         try
111         {
112             listHosts = getHostsByJsonPath( strCodeApplication, "$.results[*]" );
113 
114         }
115 
116         catch( PathNotFoundException e )
117         {
118             AppLogService.error( "error Parsing Host List for " + strCodeApplication, e );
119         }
120 
121         // return only the hosts presents in fastdeploy inventory
122         List<AWXHost> listHostsAutorized = listHosts != null
123                 ? listHosts.stream( ).filter( h -> hostInFastDeployInventory( h.getName( ) ) ).collect( Collectors.toList( ) )
124                 : null;
125 
126         return listHostsAutorized;
127     }
128 
129     public AWXHost getHost( String strCodeApplication, String strHostName )
130     {
131 
132         List<AWXHost> listHosts = null;
133         try
134         {
135             listHosts = getHostsByJsonPath( strCodeApplication, "$.results[?(@.name == '" + strHostName + "')]" );
136         }
137 
138         catch( PathNotFoundException e )
139         {
140             AppLogService.error( "error Parsing Host List for " + strCodeApplication, e );
141         }
142 
143         // return only the hosts presents in fastdeploy inventory
144         List<AWXHost> listHostsAutorized = listHosts != null
145                 ? listHosts.stream( ).filter( h -> hostInFastDeployInventory( h.getName( ) ) ).collect( Collectors.toList( ) )
146                 : null;
147 
148         return !CollectionUtils.isEmpty( listHostsAutorized ) ? listHostsAutorized.get( 0 ) : null;
149     }
150 
151     /**
152      * return true if the host is in fast deploy inventory
153      * 
154      * @param strHostname
155      *            the hostname
156      * @return true if the hostname is in the fastdeploy inventory
157      */
158     boolean hostInFastDeployInventory( String strHostname )
159     {
160 
161         String strInventorySearchResult = (String) _inventoryCache.getFromCache( strHostname );
162         Integer nCountHost = 0;
163 
164         if ( strInventorySearchResult == null )
165         {
166             try
167             {
168                 String strInventorySearchUrl = AWX_INVENTORY_ID_URL + "hosts/?name__exact=" + strHostname;
169                 strInventorySearchResult = doGetJsonAWX( strInventorySearchUrl );
170                 _inventoryCache.putInCache( strHostname, strInventorySearchResult );
171 
172             }
173             catch( HttpAccessException e )
174             {
175 
176                 AppLogService.error( "error calling AWXService.hostInFastDeployInventory Methods for hostname" + strHostname, e );
177             }
178         }
179         try
180         {
181 
182             String strInventoryNbResult = "$.count";
183             DocumentContext jsonContext = JsonPath.parse( strInventorySearchResult );
184             nCountHost = jsonContext.read( strInventoryNbResult );
185         }
186         catch( PathNotFoundException e )
187         {
188             AppLogService.error( "error calling AWXService.hostInFastDeployInventory no .count found for hostname" + strHostname, e );
189         }
190 
191         return nCountHost != null && nCountHost > 0;
192 
193     }
194 
195     public AWXJobResult getJobInfo( String strJobUri )
196     {
197 
198         AWXJobResult jobResult = null;
199 
200         try
201         {
202             String strJsonResponse = doGetJsonAWX( strJobUri );
203             DocumentContext jsonContext = JsonPath.using( _conf ).parse( strJsonResponse );
204             jobResult = jsonContext.read( "$", AWXJobResult.class );
205 
206         }
207 
208         catch( HttpAccessException e )
209         {
210             AppLogService.error( "error calling AWXService.getJobInfo Method", e );
211         }
212         catch( PathNotFoundException pe )
213         {
214             AppLogService.error( "error calling AWXService.getJobInfo Method", pe );
215         }
216 
217         return jobResult;
218 
219     }
220 
221     @Deprecated
222     public Boolean getStatusInfo( String strJobUri )
223     {
224 
225         Boolean bStatus = null;
226 
227         try
228         {
229             String strStatusJson = doGetJsonAWX( strJobUri + "job_events/?task=fd_return&event=runner_on_ok" );
230             String strJsonpathServiceOn = "$.results[0].event_data.res.msg.service_on";
231             DocumentContext jsonContext = JsonPath.parse( strStatusJson );
232             bStatus = jsonContext.read( strJsonpathServiceOn );
233 
234         }
235 
236         catch( HttpAccessException e )
237         {
238             AppLogService.error( "error calling AWXService.getStatusInfo Method", e );
239         }
240         catch( PathNotFoundException pe )
241         {
242             AppLogService.error( "error calling AWXService.getStatusInfo Method", pe );
243         }
244 
245         return bStatus;
246 
247     }
248 
249     @Deprecated
250     public List<String> getDatabasesInfo( String strJobUri )
251     {
252 
253         List<String> listDabases = null;
254 
255         try
256         {
257 
258             String strJson = doGetJsonAWX( strJobUri + "job_events/?task=fd_return&event=runner_on_ok" );
259             String strJsonpathServiceDb = "$.results[0].event_data.res.msg.databases";
260             DocumentContext jsonContext = JsonPath.parse( strJson );
261 
262             listDabases = jsonContext.read( strJsonpathServiceDb, List.class );
263 
264         }
265 
266         catch( HttpAccessException e )
267         {
268             AppLogService.error( "error calling AWXService.getDatabasesInfo Method", e );
269         }
270         catch( PathNotFoundException pe )
271         {
272             AppLogService.error( "error calling AWXService.getDatabasesInfo Method", pe );
273         }
274 
275         return listDabases;
276 
277     }
278 
279     public List<String> getDatabasesInfoInFacts( AWXHost host )
280     {
281 
282         List<String> listDabases = null;
283 
284         try
285         {
286 
287             String strJson = doGetJsonAWX( AWX_FACTS_URL + host.getId( ) + "/ansible_facts/" );
288             String strJsonpathServiceDb = "$.databases";
289             DocumentContext jsonContext = JsonPath.parse( strJson );
290 
291             listDabases = jsonContext.read( strJsonpathServiceDb, List.class );
292 
293         }
294 
295         catch( HttpAccessException e )
296         {
297             AppLogService.error( "error calling AWXService.getDatabasesInfo Method", e );
298         }
299         catch( PathNotFoundException pe )
300         {
301             AppLogService.error( "error calling AWXService.getDatabasesInfo Method", pe );
302         }
303 
304         return listDabases;
305 
306     }
307 
308     public Boolean getStatusInFacts( AWXHost host )
309     {
310 
311         Boolean bStatus = null;
312 
313         try
314         {
315             String strJson = doGetJsonAWX( AWX_FACTS_URL + host.getId( ) + "/ansible_facts/" );
316             String strJsonpathServiceOn = "$.service_on";
317             DocumentContext jsonContext = JsonPath.parse( strJson );
318             bStatus = jsonContext.read( strJsonpathServiceOn );
319 
320         }
321 
322         catch( HttpAccessException e )
323         {
324             AppLogService.error( "error calling AWXService.getStatusInfo Method", e );
325         }
326         catch( PathNotFoundException pe )
327         {
328             AppLogService.error( "error calling AWXService.getStatusInfo Method", pe );
329         }
330 
331         return bStatus;
332 
333     }
334 
335     @Deprecated
336     public AWXExecuteResult getExecuteInfo( String strJobUri )
337     {
338 
339         AWXExecuteResult strOut = null;
340 
341         try
342         {
343             String strExecuteInfoJson = doGetJsonAWX( strJobUri + "job_events/?task=fd_return&event=runner_on_ok" );
344             String strJsonpathServiceOn = "$.results[0].event_data.res.msg";
345             // String strJsonpathServiceOn = "$.results[0].stdout";
346             DocumentContext jsonContext = JsonPath.using( _conf ).parse( strExecuteInfoJson );
347             strOut = jsonContext.read( strJsonpathServiceOn, AWXExecuteResult.class );
348 
349         }
350 
351         catch( HttpAccessException e )
352         {
353             AppLogService.error( "error calling AWXService.getExecuteInfo Method", e );
354         }
355         catch( PathNotFoundException pe )
356         {
357             AppLogService.error( "error calling AWXService.getExecuteInfo Method", pe );
358         }
359 
360         return strOut;
361 
362     }
363 
364     public static AWXJobResult refreshJobInformations( String jobUri )
365     {
366 
367         AWXJobResult jobResult = AWXService.getService( ).getJobInfo( jobUri );
368         if ( jobResult.isRunning( ) )
369         {
370             try
371             {
372                 Thread.sleep( AppPropertiesService.getPropertyInt( ConstanteUtils.PROPERTY_AWX_JOB_INFO_INTERVAL, 5000 ) );
373             }
374             catch( InterruptedException e )
375             {
376                 AppLogService.error( e );
377             }
378             return refreshJobInformations( jobResult.getUrl( ) );
379         }
380         return jobResult;
381 
382     }
383 
384     public AWXJobResult launchJob( String strJobTemplateCode, String strHost )
385     {
386 
387         return launchJob( strJobTemplateCode, new AWXParameters( strHost, null ) );
388 
389     }
390 
391     public AWXJobResult launchJob( String strJobTemplateCode, AWXParameters awxParameters )
392     {
393 
394         AWXJobResult jobResult = null;
395         // set inventoty id before launch
396         awxParameters.setInventory( getInventoryId( ) );
397         String strJsonPost = null;
398         if ( awxParameters.getExtraVars( ) != null )
399         {
400             try
401             {
402                 strJsonPost = MapperJsonUtil.getJson( awxParameters );
403             }
404             catch( IOException e )
405             {
406                 AppLogService.error( "error parsing Awx Parameters", e );
407             }
408 
409         }
410         else
411         {
412 
413             strJsonPost = "{\"limit\":\"" + awxParameters.getLimit( ) + "\",\"inventory\":" + awxParameters.getInventory( ) + "}";
414         }
415         String strLaunchUri = getJobTemplateLaunchUrl( strJobTemplateCode );
416         try
417         {
418             String strJsonResponse = doPostJsonAWX( strLaunchUri, strJsonPost );
419             DocumentContext jsonContext = JsonPath.using( _conf ).parse( strJsonResponse );
420             jobResult = jsonContext.read( "$", AWXJobResult.class );
421 
422         }
423         catch( HttpAccessException e )
424         {
425             AppLogService.error( "error calling AWXService.launchJob Method", e );
426         }
427 
428         return jobResult;
429 
430     }
431 
432     private List<AWXHost> getHostsByJsonPath( String strCodeApplication, String strJsonPath ) throws PathNotFoundException
433     {
434 
435         List<AWXHost> listHosts = new ArrayList<AWXHost>( );
436         String strJsonHosts = getJsonHosts( strCodeApplication );
437 
438         String strJsonClean = strJsonHosts.replace( "\"{", "{" ).replace( "}\"", "}" ).replace( "\\", "" );
439 
440         DocumentContext jsonContext = JsonPath.using( _conf ).parse( strJsonClean );
441 
442         TypeRef<List<AWXHost>> type = new TypeRef<List<AWXHost>>( )
443         {
444         };
445 
446         listHosts = jsonContext.read( strJsonPath, type );
447 
448         return listHosts;
449     }
450 
451     private String getJsonHosts( String strCodeApplication )
452     {
453 
454         String strValue = (String) _inventoryCache.getFromCache( strCodeApplication );
455 
456         if ( strValue == null )
457         {
458             try
459             {
460                 String strHostUrl = AWX_HOSTS_URL;
461                 strHostUrl = strHostUrl.replace( "$id", Integer.toString( getInventoryId( ) ) );
462                 strHostUrl = strHostUrl.replace( "$code", strCodeApplication.toLowerCase( ) );
463 
464                 strValue = doGetJsonAWX( strHostUrl );
465 
466             }
467             catch( HttpAccessException e )
468             {
469 
470                 AppLogService.error( "error calling AWXService.getHosts Methods", e );
471             }
472             catch( PathNotFoundException e )
473             {
474                 AppLogService.error( "error calling AWXService.getHosts Method - no host  found on applications" + strCodeApplication, e );
475             }
476             _inventoryCache.putInCache( strCodeApplication, strValue );
477 
478         }
479 
480         return strValue;
481 
482     }
483 
484     public Integer getInventoryId( )
485     {
486 
487         Integer strValue = (Integer) _inventoryCache.getFromCache( InventoryAWXCacheService.CACHE_KEY_INVENTORY_ID );
488 
489         if ( strValue == null )
490         {
491             try
492             {
493                 String strInventoryUrl = AWX_INVENTORY_ID_URL;
494                 String strJsonAwxInventory = doGetJsonAWX( strInventoryUrl );
495                 String strInventoryPathId = "$.id";
496                 DocumentContext jsonContext = JsonPath.parse( strJsonAwxInventory );
497                 strValue = jsonContext.read( strInventoryPathId );
498 
499             }
500             catch( HttpAccessException e )
501             {
502 
503                 AppLogService.error( "error calling AWXService.getInventoryId Methods", e );
504             }
505             catch( PathNotFoundException e )
506             {
507                 AppLogService.error( "error calling AWXService.getInventoryId Method - no Id found", e );
508             }
509             _inventoryCache.putInCache( InventoryAWXCacheService.CACHE_KEY_INVENTORY_ID, strValue );
510 
511         }
512 
513         return strValue;
514 
515     }
516 
517     private String getJobTemplateLaunchUrl( String strJobTemplateCode )
518     {
519         List<String> listTemplateLaunchUrl = null;
520         String strJsonJobTemplates = getJsonJobTemplates( );
521         String strLaunchUrlJsonpath = "$.results[?(@.name == '" + strJobTemplateCode + "')].related.launch";
522 
523         DocumentContext jsonContext = JsonPath.parse( strJsonJobTemplates );
524         listTemplateLaunchUrl = jsonContext.read( strLaunchUrlJsonpath, List.class );
525 
526         return listTemplateLaunchUrl.get( 0 );
527 
528     }
529 
530     private String getJsonJobTemplates( )
531     {
532 
533         String strValue = (String) _jobTemplateCache.getFromCache( CACHE_KEY_JOB_TEMPLATES );
534 
535         if ( strValue == null )
536         {
537             try
538             {
539                 String strSearchUrl = AWX_JOB_TEMPLATES;
540                 strValue = doGetJsonAWX( strSearchUrl );
541 
542             }
543             catch( HttpAccessException e )
544             {
545 
546                 AppLogService.error( "error calling AWXService.getJsonJobTemplates Method", e );
547             }
548 
549             _jobTemplateCache.putInCache( CACHE_KEY_JOB_TEMPLATES, strValue );
550 
551         }
552 
553         return strValue;
554 
555     }
556 
557     private String doGetJsonAWX( String strRelativeUri ) throws HttpAccessException
558     {
559         String strJsonResult = null;
560 
561         try
562         {
563             HttpAccess httpAccess = new HttpAccess( );
564             String strHeaderApiKeyName = AppPropertiesService.getProperty( PROPERTY_AWX_API_MANAGER_HEADER_KEY );
565             String strHeaderApiKeyValue = AppPropertiesService.getProperty( PROPERTY_AWX_API_MANAGER_HEADER_KEY_VALUE );
566 
567             HashMap<String, String> mapHeader = new HashMap<>( );
568             mapHeader.put( strHeaderApiKeyName, strHeaderApiKeyValue );
569 
570             RequestAuthenticator requestAuthenticator = new BasicAuthorizationAuthenticator( AppPropertiesService.getProperty( PROPERTY_AWX_ACCOUNT_LOGIN ),
571                     AppPropertiesService.getProperty( PROPERTY_AWX_ACCOUNT_PWD ) );
572             strJsonResult = httpAccess.doGet( AppPropertiesService.getProperty( PROPERTY_AWX_BASE_URL ) + strRelativeUri, requestAuthenticator, null, mapHeader,
573                     null );
574         }
575         catch( HttpAccessException e )
576         {
577             String strError = "AWXService - Error calling '" + strRelativeUri + "' : ";
578             AppLogService.error( strError + e.getMessage( ), e );
579             throw new HttpAccessException( strError, e );
580         }
581 
582         return strJsonResult;
583 
584     }
585 
586     private String doPostJsonAWX( String strRelativeUri, String strJson ) throws HttpAccessException
587     {
588         String strJsonResult = null;
589 
590         try
591         {
592             String strHeaderApiKeyName = AppPropertiesService.getProperty( PROPERTY_AWX_API_MANAGER_HEADER_KEY );
593             String strHeaderApiKeyValue = AppPropertiesService.getProperty( PROPERTY_AWX_API_MANAGER_HEADER_KEY_VALUE );
594 
595             HashMap<String, String> mapHeader = new HashMap<>( );
596             mapHeader.put( strHeaderApiKeyName, strHeaderApiKeyValue );
597 
598             HttpAccess httpAccess = new HttpAccess( );
599 
600             RequestAuthenticator requestAuthenticator = new BasicAuthorizationAuthenticator( AppPropertiesService.getProperty( PROPERTY_AWX_ACCOUNT_LOGIN ),
601                     AppPropertiesService.getProperty( PROPERTY_AWX_ACCOUNT_PWD ) );
602             strJsonResult = httpAccess.doPostJSON( AppPropertiesService.getProperty( PROPERTY_AWX_BASE_URL ) + strRelativeUri, strJson, requestAuthenticator,
603                     null, mapHeader, null );
604         }
605         catch( HttpAccessException e )
606         {
607             String strError = "AWXService - Error calling '" + strRelativeUri + "' : ";
608             AppLogService.error( strError + e.getMessage( ), e );
609             throw new HttpAccessException( strError, e );
610         }
611 
612         return strJsonResult;
613 
614     }
615 
616 }