invoke yum --verbose when available
[nodeupdate.git] / NodeUpdate.py
1 #!/usr/bin/python2
2
3 import sys, os
4 from random import Random
5 import string
6 from types import StringTypes
7
8 from time import strftime
9 TIMEFORMAT="%Y-%m-%d %H:%M:%S"
10
11 NODEUPDATE_PID_FILE= "/var/run/NodeUpdate.pid"
12
13 # variables for cron file creation
14 TARGET_SCRIPT = '(echo && date && echo && /usr/bin/NodeUpdate.py start) >>/var/log/NodeUpdate.log 2>&1'
15 TARGET_DESC = 'Update node RPMs periodically'
16 TARGET_USER = 'root'
17 TARGET_SHELL = '/bin/bash'
18 CRON_FILE = '/etc/cron.d/NodeUpdate.cron'
19
20 YUM_PATH = "/usr/bin/yum"
21
22 RPM_PATH = "/bin/rpm"
23
24 RPM_GPG_PATH = "/etc/pki/rpm-gpg"
25
26
27 # location of file containing http/https proxy info, if needed
28 PROXY_FILE = '/etc/planetlab/http_proxy'
29
30 # this is the flag that indicates an update needs to restart
31 # the system to take effect. it is created by the rpm that requested
32 # the reboot
33 REBOOT_FLAG = '/etc/planetlab/update-reboot'
34
35 # location of directory containing boot server ssl certs
36 SSL_CERT_DIR='/mnt/cdrom/bootme/cacert/'
37
38 # file containing list of extra groups to attempt to update,
39 # if necessary.
40 EXTRA_GROUPS_FILE= '/etc/planetlab/extra-node-groups'
41
42 # file containing a list of rpms that we should attempt to delete
43 # before updating everything else. This list is not removed with 
44 # 'yum remove', because that could accidently remove dependency rpms
45 # that were not intended to be deleted.
46 DELETE_RPM_LIST_FILE= '/etc/planetlab/delete-rpm-list'
47
48
49 # print out a message only if we are displaying output
50 def Message(message):
51     if displayOutput:
52         if isinstance(message,StringTypes) and len(message) >=2 and message[0]=="\n":
53             print "\n",
54             message=message[1:]
55         print strftime(TIMEFORMAT),
56         print message
57
58 # print out a message only if we are displaying output
59 def Error(Str):
60     print strftime(TIMEFORMAT),
61     print Str
62
63
64 # create an entry in /etc/cron.d so we run periodically.
65 # we will be run once a day at a 0-59 minute random offset
66 # into a 0-23 random hour
67 def UpdateCronFile():
68     try:
69         
70         randomMinute= Random().randrange( 0, 59, 1 );
71         randomHour= Random().randrange( 0, 11, 1 );
72         
73         f = open( CRON_FILE, 'w' );
74         f.write( "# %s\n" % (TARGET_DESC) );
75         f.write( "MAILTO=%s\n" % (TARGET_USER) );
76         f.write( "SHELL=%s\n" % (TARGET_SHELL) );
77         f.write( "%s %s,%s * * * %s %s\n\n" %
78                  (randomMinute, randomHour, randomHour + 12, TARGET_USER, TARGET_SCRIPT) );
79         f.close()
80     
81         print( "Created new cron.d entry." )
82     except:
83         print( "Unable to create cron.d entry." )
84
85
86 # simply remove the cron file we created
87 def RemoveCronFile():
88     try:
89         os.unlink( CRON_FILE )
90         print( "Deleted cron.d entry." )
91     except:
92         print( "Unable to delete cron.d entry." )
93         
94
95
96 class NodeUpdate:
97
98     def __init__( self, doReboot ):
99         if self.CheckProxy():
100             os.environ['http_proxy']= self.HTTP_PROXY
101             os.environ['HTTP_PROXY']= self.HTTP_PROXY
102             
103         self.doReboot= doReboot
104
105     
106
107     def CheckProxy( self ):
108         Message( "Checking existence of proxy config file..." )
109         
110         if os.access(PROXY_FILE, os.R_OK) and os.path.isfile(PROXY_FILE):
111             self.HTTP_PROXY= string.strip(file(PROXY_FILE,'r').readline())
112             Message( "Using proxy %s." % self.HTTP_PROXY )
113             return 1
114         else:
115             Message( "Not using any proxy." )
116             return 0
117
118
119     def InstallKeys( self ):
120         Message( "\nRemoving any existing GPG signing keys from the RPM database" )
121         os.system( "%s --allmatches -e gpg-pubkey" % RPM_PATH )
122
123         Message( "\nInstalling all GPG signing keys in %s" % RPM_GPG_PATH )
124         os.system( "%s --import %s/*" % (RPM_PATH, RPM_GPG_PATH) )
125
126
127     def ClearRebootFlag( self ):
128         os.system( "/bin/rm -rf %s" % REBOOT_FLAG )
129
130
131     def CheckForUpdates( self ):
132
133         Message( "\nRemoving any existing reboot flags" )
134         self.ClearRebootFlag()
135
136         if self.doReboot == 0:
137             Message( "\nIgnoring any reboot flags set by RPMs" );
138
139         Message( "\nChecking if yum supports SSL certificate checks" )
140         if os.system( "%s --help | grep -q sslcertdir" % YUM_PATH ) == 0:
141             Message( "Yes, using --sslcertdir option" )
142             sslcertdir = "--sslcertdir=" + SSL_CERT_DIR
143         else:
144             Message( "No, not using --sslcertdir option" )
145             sslcertdir = ""
146                     
147         yum_options=""
148         Message( "\nChecking if yum supports --verbose" )
149         if os.system( "%s --help | grep -q verbose" % YUM_PATH ) == 0:
150             Message( "Yes, using --verbose option" )
151             yum_options += " --verbose"
152         else:
153             Message( "No, not using --verbose option" )
154                     
155         Message( "\nUpdating PlanetLab group" )
156         os.system( "%s %s %s -y groupupdate \"PlanetLab\"" %
157                    (YUM_PATH, yum_options, sslcertdir) )
158
159         Message( "\nUpdating rest of system" )
160         os.system( "%s %s %s -y update" %
161                    (YUM_PATH, yum_options, sslcertdir) )
162
163         Message( "\nChecking for extra groups to update" )
164         if os.access(EXTRA_GROUPS_FILE, os.R_OK) and \
165            os.path.isfile(EXTRA_GROUPS_FILE):
166             extra_groups_contents= file(EXTRA_GROUPS_FILE).read()
167             extra_groups_contents= string.strip(extra_groups_contents)
168             if extra_groups_contents == "":
169                 Message( "No extra groups found in file." )
170             else:
171                 for group in string.split(extra_groups_contents,"\n"):
172                     Message( "\nUpdating %s group" % group )
173                     os.system( "%s %s %s -y groupupdate \"%s\"" %
174                                (YUM_PATH, yum_options, sslcertdir, group) )
175         else:
176             Message( "No extra groups file found" )
177             
178         if os.access(REBOOT_FLAG, os.R_OK) and os.path.isfile(REBOOT_FLAG) and self.doReboot:
179             Message( "\nAt least one update requested the system be rebooted" )
180             self.ClearRebootFlag()
181             os.system( "/sbin/shutdown -r now" )
182
183     def RebuildRPMdb( self ):
184         Message( "\nRebuilding RPM Database." )
185         try: os.system( "rm /var/lib/rpm/__db.*" )
186         except Exception, err: print "RebuildRPMdb: %s" % err
187         try: os.system( "%s --rebuilddb" % RPM_PATH )
188         except Exception, err: print "RebuildRPMdb: %s" % err
189
190     def RemoveRPMS( self ):
191
192         Message( "\nLooking for RPMs to be deleted." )
193         if os.access(DELETE_RPM_LIST_FILE, os.R_OK) and \
194            os.path.isfile(DELETE_RPM_LIST_FILE):
195             rpm_list_contents= file(DELETE_RPM_LIST_FILE).read()
196             rpm_list_contents= string.strip(rpm_list_contents)
197
198             if rpm_list_contents == "":
199                 Message( "No RPMs listed in file to delete." )
200                 return
201
202             rpm_list= string.join(string.split(rpm_list_contents))
203             
204             Message( "Deleting these RPMs:" )
205             Message( rpm_list_contents )
206             
207             rc= os.system( "%s -ev %s" % (RPM_PATH, rpm_list) )
208
209             if rc != 0:
210                 Error( "Unable to delete RPMs, continuing. rc=%d" % rc )
211             else:
212                 Message( "RPMs deleted successfully." )
213             
214         else:
215             Message( "No RPMs list file found." )
216
217
218
219 if __name__ == "__main__":
220
221     # if we are invoked with 'start', display the output. this
222     # is usefull for running something under cron and as a service
223     # (at startup), so the cron only outputs errors and doesn't
224     # generate mail when it works correctly
225
226     displayOutput= 0
227
228     # if we hit an rpm that requests a reboot, do it if this is
229     # set to 1. can be turned off by adding noreboot to command line
230     # option
231     
232     doReboot= 1
233
234     if "start" in sys.argv:
235         displayOutput= 1
236
237     if "noreboot" in sys.argv:
238         doReboot= 0
239
240     if "updatecron" in sys.argv:
241         # simply update the /etc/cron.d file for us, and exit
242         UpdateCronFile()
243         sys.exit(0)
244
245     if "removecron" in sys.argv:
246         RemoveCronFile()
247         sys.exit(0)     
248
249             
250     # see if we are already running by checking the existance
251     # of a PID file, and if it exists, attempting a test kill
252     # to see if the process really does exist. If both of these
253     # tests pass, exit.
254     
255     if os.access(NODEUPDATE_PID_FILE, os.R_OK):
256         pid= string.strip(file(NODEUPDATE_PID_FILE).readline())
257         if pid <> "":
258             if os.system("/bin/kill -0 %s > /dev/null 2>&1" % pid) == 0:
259                 Message( "It appears we are already running, exiting." )
260                 sys.exit(1)
261                     
262     # write out our process id
263     pidfile= file( NODEUPDATE_PID_FILE, 'w' )
264     pidfile.write( "%d\n" % os.getpid() )
265     pidfile.close()
266
267     
268     nodeupdate= NodeUpdate(doReboot)
269     if not nodeupdate:
270         Error( "Unable to initialize." )
271     else:
272         nodeupdate.RebuildRPMdb()
273         nodeupdate.RemoveRPMS()
274         nodeupdate.InstallKeys()
275         nodeupdate.CheckForUpdates()
276         Message( "\nUpdate complete." )
277
278     # remove the PID file
279     os.unlink( NODEUPDATE_PID_FILE )
280