2
2
3
3
4
4
import datetime
5
+ import platform
6
+ import plistlib
5
7
import re
6
8
import subprocess
7
9
import sys
10
+ import xml .parsers .expat
8
11
from distutils .version import StrictVersion
9
12
10
13
sys .path .insert (0 , '/usr/local/sal' )
11
14
import utils
12
15
13
16
14
- __version__ = '1.0.0 '
17
+ __version__ = '1.0.1 '
15
18
16
19
17
20
def main ():
18
21
sus_submission = {}
19
- sus_report = get_sus_install_report ()
20
-
21
22
sus_submission ['facts' ] = get_sus_facts ()
22
23
23
24
# Process managed items and update histories.
24
- sus_submission ['managed_items' ] = {}
25
+ sus_submission ['managed_items' ] = get_sus_install_report ()
25
26
sus_submission ['update_history' ] = []
26
27
27
- for item in sus_report :
28
- name , version , date = item
29
- submission_item = {}
30
- submission_item ['date_managed' ] = date
31
- submission_item ['status' ] = 'PRESENT'
32
- submission_item ['data' ] = {'type' : 'Apple SUS Install' , 'version' : version }
33
- sus_submission ['managed_items' ][name ] = submission_item
34
-
35
28
pending = get_pending ()
36
29
sus_submission ['managed_items' ].update (pending )
37
30
@@ -40,60 +33,19 @@ def main():
40
33
41
34
def get_sus_install_report ():
42
35
"""Return installed apple updates from softwareupdate"""
43
- cmd = ['softwareupdate' , '--history' ]
44
36
try :
45
- output = subprocess .check_output (cmd )
46
- except subprocess .CalledProcessError :
47
- # This is a new argument and not supported on all OS versions
48
- return []
49
-
50
- # Example output:
51
- # macOS Mojave 10.14.1 11/06/2018, 08:41:49
52
- # macOS 10.14.1 Update 11/02/2018, 13:24:17
53
- # Command Line Tools (macOS High Sierra version 10.13) for Xcode 10.1 11/02/2018, 12:36:15
54
-
55
- # Line one is a "normal" line, name, version, and date are separated
56
- # by 2+ spaces.
57
- # Line two has no version number.
58
- # Line three has such a long name that they decided to output only
59
- # one space between the name and the version.
60
-
61
- # Drop the header and do an initial split on 2 or more whitespace
62
- mostly_parsed = [re .split (r' {2,}' , l .strip ()) for l in output .splitlines ()[2 :]]
63
- results = []
64
- for line in mostly_parsed :
65
- # If we have three lines, everything is fine.
66
- if len (line ) == 2 :
67
- # Some long update names are displayed without a minimum 2
68
- # space delimiter, so we have to split them again.
69
- # This time, we split on a single space and then see if the
70
- # second item can be cast to a StrictVersion.
71
- attempt = line [0 ].rsplit (' ' , 1 )
72
- if len (attempt ) == 2 :
73
- try :
74
- # If we got a StrictVersion, then use our split
75
- # results
76
- StrictVersion (attempt [1 ])
77
- name = attempt [0 ]
78
- version = attempt [1 ]
79
- except ValueError :
80
- # Otherwise, there's no versionm, just a name.
81
- name = line [0 ]
82
- version = None
83
-
84
- else :
85
- # I haven't seen examples of this (name with no spaces
86
- # and a date), but it's here just in case.
87
- name = line [0 ]
88
- version = None
89
- else :
90
- name = line [0 ]
91
- version = line [1 ]
92
-
93
- installed = datetime .datetime .strptime (line [- 1 ], '%m/%d/%Y, %H:%M:%S' )
94
- results .append ([name , version , installed ])
95
-
96
- return results
37
+ history = plistlib .readPlist ('/Library/Receipts/InstallHistory.plist' )
38
+ except (IOError , xml .parsers .expat .ExpatError ):
39
+ history = []
40
+ return {
41
+ i ['displayName' ]: {
42
+ 'date_managed' : i ['date' ],
43
+ 'status' : 'PRESENT' ,
44
+ 'data' : {
45
+ 'type' : 'Apple SUS Install' ,
46
+ 'version' : i ['displayVersion' ].strip ()
47
+ }
48
+ } for i in history if i ['processName' ] == 'softwareupdated' }
97
49
98
50
99
51
def get_sus_facts ():
@@ -162,26 +114,71 @@ def get_pending():
162
114
except subprocess .CalledProcessError :
163
115
return pending_items
164
116
165
- # Example output
166
-
167
- # Software Update Tool
168
-
169
- # Software Update found the following new or updated software:
170
- # * macOS High Sierra 10.13.6 Update-
171
- # macOS High Sierra 10.13.6 Update ( ), 1931648K [recommended] [restart]
172
- # * iTunesX-12.8.2
173
- # iTunes (12.8.2), 273564K [recommended]
174
-
175
- for line in output .splitlines ():
176
- if line .strip ().startswith ('*' ):
177
- item = {'date_managed' : datetime .datetime .utcnow ().isoformat () + 'Z' }
178
- item ['status' ] = 'PENDING'
179
- pending_items [line .strip ()[2 :]] = item
180
-
181
- return pending_items
182
-
183
-
184
-
117
+ # The following regex code is from Shea Craig's work on the Salt
118
+ # mac_softwareupdate module. Reference that for future updates.
119
+ if StrictVersion (platform .mac_ver ()[0 ]) >= StrictVersion ('10.15' ):
120
+ # Example output:
121
+ # Software Update Tool
122
+ #
123
+ # Finding available software
124
+ # Software Update found the following new or updated software:
125
+ # * Label: Command Line Tools beta 5 for Xcode-11.0
126
+ # Title: Command Line Tools beta 5 for Xcode, Version: 11.0, Size: 224804K, Recommended: YES,
127
+ # * Label: macOS Catalina Developer Beta-6
128
+ # Title: macOS Catalina Public Beta, Version: 5, Size: 3084292K, Recommended: YES, Action: restart,
129
+ # * Label: BridgeOSUpdateCustomer
130
+ # Title: BridgeOSUpdateCustomer, Version: 10.15.0.1.1.1560926689, Size: 390674K, Recommended: YES, Action: shut down,
131
+ # - Label: iCal-1.0.2
132
+ # Title: iCal, Version: 1.0.2, Size: 6520K,
133
+ rexp = re .compile (
134
+ r'(?m)' # Turn on multiline matching
135
+ r'^\s*[*-] Label: ' # Name lines start with * or - and "Label: "
136
+ r'(?P<name>[^ ].*)[\r\n]' # Capture the rest of that line; this is the update name.
137
+ r'.*Version: (?P<version>[^,]*), ' # Grab the version number.
138
+ r'Size: (?P<size>[^,]*),\s*' # Grab the size; unused at this time.
139
+ r'(?P<recommended>Recommended: YES,)?\s*' # Optionally grab the recommended flag.
140
+ r'(?P<action>Action: (?:restart|shut down),)?' # Optionally grab an action.
141
+ )
142
+ else :
143
+ # Example output:
144
+ # Software Update Tool
145
+ #
146
+ # Finding available software
147
+ # Software Update found the following new or updated software:
148
+ # * Command Line Tools (macOS Mojave version 10.14) for Xcode-10.3
149
+ # Command Line Tools (macOS Mojave version 10.14) for Xcode (10.3), 199140K [recommended]
150
+ # * macOS 10.14.1 Update
151
+ # macOS 10.14.1 Update (10.14.1), 199140K [recommended] [restart]
152
+ # * BridgeOSUpdateCustomer
153
+ # BridgeOSUpdateCustomer (10.14.4.1.1.1555388607), 328394K, [recommended] [shut down]
154
+ # - iCal-1.0.2
155
+ # iCal, (1.0.2), 6520K
156
+ rexp = re .compile (
157
+ r'(?m)' # Turn on multiline matching
158
+ r'^\s+[*-] ' # Name lines start with 3 spaces and either a * or a -.
159
+ r'(?P<name>.*)[\r\n]' # The rest of that line is the name.
160
+ r'.*\((?P<version>[^ \)]*)' # Capture the last parenthesized value on the next line.
161
+ r'[^\r\n\[]*(?P<recommended>\[recommended\])?\s?' # Capture [recommended] if there.
162
+ r'(?P<action>\[(?:restart|shut down)\])?' # Capture an action if present.
163
+ )
164
+
165
+ now = datetime .datetime .utcnow ().isoformat () + 'Z'
166
+ return {
167
+ m .group ('name' ): {
168
+ 'date_managed' : now ,
169
+ 'status' : 'PENDING' ,
170
+ 'data' : {
171
+ 'version' : m .group ('version' ),
172
+ 'recommended' : 'TRUE' if 'recommended' in m .group ('recommended' ) else 'FALSE' ,
173
+ 'action' : _bracket_cleanup (m , 'action' )
174
+ }
175
+ } for m in rexp .finditer (output )
176
+ }
177
+
178
+
179
+ def _bracket_cleanup (match , key ):
180
+ """Strip out [ and ] and uppercase SUS output"""
181
+ return re .sub (r'[\[\]]' , '' , match .group (key ) or '' ).upper ()
185
182
186
183
187
184
if __name__ == "__main__" :
0 commit comments