mirror of
https://github.com/munki/munki.git
synced 2026-04-23 04:59:17 -05:00
e3a7e280e2
git-svn-id: http://munki.googlecode.com/svn/trunk@1033 a4e17f2e-e282-11dd-95e1-755cbddbdd66
247 lines
6.5 KiB
Python
Executable File
247 lines
6.5 KiB
Python
Executable File
#!/usr/bin/python
|
|
# encoding: utf-8
|
|
#
|
|
# Copyright 2009-2011 Greg Neagle.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
"""
|
|
munkistatus.py
|
|
|
|
Created by Greg Neagle on 2009-09-24.
|
|
|
|
Utility functions for using MunkiStatus.app
|
|
to display status and progress.
|
|
"""
|
|
|
|
import os
|
|
import subprocess
|
|
import socket
|
|
import time
|
|
|
|
# module socket variable
|
|
SOCK = None
|
|
|
|
|
|
def launchMunkiStatus():
|
|
'''Uses launchd KeepAlive path so it launches from a launchd agent
|
|
in the correct context.
|
|
This is more complicated to set up, but makes Apple (and launchservices)
|
|
happier.
|
|
There needs to be a launch agent that is triggered when the launchfile
|
|
is created; and that launch agent then runs MunkiStatus.app.'''
|
|
launchfile = "/var/run/com.googlecode.munki.MunkiStatus"
|
|
cmd = ['/usr/bin/touch', launchfile]
|
|
retcode = subprocess.call(cmd)
|
|
time.sleep(0.1)
|
|
if os.path.exists(launchfile):
|
|
os.unlink(launchfile)
|
|
|
|
|
|
def launchAndConnectToMunkiStatus():
|
|
'''Connects to the MunkiStatus socket, launching MunkiStatus if needed'''
|
|
global SOCK
|
|
if not getMunkiStatusPID():
|
|
launchMunkiStatus()
|
|
socketpath = getMunkiStatusSocket()
|
|
if socketpath:
|
|
try:
|
|
SOCK = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
|
SOCK.connect(socketpath)
|
|
except IOError:
|
|
# some problem; kill the socket
|
|
SOCK = None
|
|
#else:
|
|
#raise Exception("Could not open connection to MunkiStatus.app")
|
|
|
|
|
|
def sendCommand(command_text):
|
|
'''Send a command to MunkiStatus'''
|
|
global SOCK
|
|
if SOCK == None:
|
|
launchAndConnectToMunkiStatus()
|
|
if SOCK:
|
|
try:
|
|
# we can send only a single line.
|
|
messagelines = command_text.splitlines(True)
|
|
SOCK.send(messagelines[0].encode('UTF-8'))
|
|
except socket.error, (err, errmsg):
|
|
if err == 32 or err == 9:
|
|
# broken pipe
|
|
SOCK.close()
|
|
SOCK = None
|
|
|
|
|
|
def readResponse():
|
|
'''Read a response from MunkiStatus'''
|
|
global SOCK
|
|
if SOCK:
|
|
try:
|
|
# our responses are really short
|
|
data = SOCK.recv(256)
|
|
return int(data.rstrip('\n'))
|
|
except (ValueError, TypeError):
|
|
# MunkiStatus returned an illegal value
|
|
# ignore for now
|
|
return 0
|
|
except socket.error, (err, errmsg):
|
|
print err, errmsg
|
|
SOCK.close()
|
|
SOCK = None
|
|
|
|
return ''
|
|
|
|
|
|
def getPIDforProcessName(processname):
|
|
'''Returns a process ID for processname'''
|
|
cmd = ['/bin/ps', '-eo', 'pid=,command=']
|
|
proc = subprocess.Popen(cmd, shell=False, bufsize=1,
|
|
stdin=subprocess.PIPE,
|
|
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
|
while True:
|
|
line = proc.stdout.readline().decode('UTF-8')
|
|
if not line and (proc.poll() != None):
|
|
break
|
|
line = line.rstrip('\n')
|
|
if line:
|
|
try:
|
|
(pid, process) = line.split(None, 1)
|
|
except ValueError:
|
|
# funky process line, so we'll skip it
|
|
pass
|
|
else:
|
|
if process.find(processname) != -1:
|
|
return str(pid)
|
|
|
|
return 0
|
|
|
|
|
|
def getMunkiStatusPID():
|
|
'''Gets the process ID for Managed Software Update'''
|
|
return getPIDforProcessName(
|
|
"Managed Software Update.app/Contents/MacOS/Managed Software Update") \
|
|
or getPIDforProcessName("MunkiStatus.app/Contents/MacOS/MunkiStatus")
|
|
|
|
|
|
def getMunkiStatusSocket():
|
|
'''Returns the path to the MunkiStatus socket'''
|
|
pid = None
|
|
for i in range(8):
|
|
pid = getMunkiStatusPID()
|
|
if pid:
|
|
break
|
|
else:
|
|
# sleep and try again
|
|
time.sleep(.25)
|
|
if pid:
|
|
for i in range(12):
|
|
socketpath = "/tmp/com.googlecode.munki.munkistatus.%s" % pid
|
|
if os.path.exists(socketpath):
|
|
return socketpath
|
|
|
|
# sleep and try again
|
|
time.sleep(.25)
|
|
return ""
|
|
|
|
|
|
def activate():
|
|
'''Brings MunkiStatus window to the front.'''
|
|
sendCommand(u"ACTIVATE: \n")
|
|
|
|
|
|
def hide():
|
|
'''Hides MunkiStatus window.'''
|
|
sendCommand(u"HIDE: \n")
|
|
|
|
|
|
def show():
|
|
'''Shows MunkiStatus window.'''
|
|
sendCommand(u"SHOW: \n")
|
|
|
|
|
|
def title(titleText):
|
|
'''Sets the window title.'''
|
|
sendCommand(u"TITLE: %s\n" % titleText)
|
|
|
|
|
|
def message(messageText):
|
|
'''Sets the status message.'''
|
|
sendCommand(u"MESSAGE: %s\n" % messageText)
|
|
|
|
|
|
def detail(detailsText):
|
|
'''Sets the detail text.'''
|
|
sendCommand(u"DETAIL: %s\n" % detailsText)
|
|
|
|
|
|
def percent(percentage):
|
|
'''Sets the progress indicator to 0-100 percent done.
|
|
If you pass a negative number, the progress indicator
|
|
is shown as an indeterminate indicator (barber pole).'''
|
|
sendCommand(u"PERCENT: %s\n" % percentage)
|
|
|
|
|
|
def hideStopButton():
|
|
'''Hides the stop button.'''
|
|
sendCommand(u"HIDESTOPBUTTON: \n")
|
|
|
|
|
|
def showStopButton():
|
|
'''Shows the stop button.'''
|
|
sendCommand(u"SHOWSTOPBUTTON: \n")
|
|
|
|
|
|
def disableStopButton():
|
|
'''Disables (grays out) the stop button.'''
|
|
sendCommand(u"DISABLESTOPBUTTON: \n")
|
|
|
|
|
|
def enableStopButton():
|
|
'''Enables the stop button.'''
|
|
sendCommand(u"ENABLESTOPBUTTON: \n")
|
|
|
|
|
|
def restartAlert():
|
|
'''Tells MunkiStatus to display a restart alert.'''
|
|
try:
|
|
sendCommand(u"ACTIVATE: \n")
|
|
sendCommand(u"RESTARTALERT: \n")
|
|
return readResponse()
|
|
except IOError:
|
|
return 0
|
|
|
|
|
|
def getStopButtonState():
|
|
'''Returns 1 if the stop button has been clicked, 0 otherwise.'''
|
|
if not SOCK:
|
|
return 0
|
|
try:
|
|
SOCK.send(u"GETSTOPBUTTONSTATE: \n")
|
|
state = readResponse()
|
|
if state:
|
|
return state
|
|
else:
|
|
return 0
|
|
except IOError:
|
|
return 0
|
|
|
|
|
|
def quit():
|
|
'''Tells the status app that we're done.'''
|
|
global SOCK
|
|
try:
|
|
SOCK.send(u"QUIT: \n")
|
|
SOCK.close()
|
|
SOCK = None
|
|
except (AttributeError, IOError):
|
|
pass
|