def NotifyAdminsOfPackageUnlock(self, pkginfo): """Notifies admins of package being unlocked.""" subject_line = 'MSU Package Unlocked by %s - %s' % ( users.get_current_user(), pkginfo.filename) main_body = 'That package has been removed from all catalogs and manifests.' if mail: mail.SendMail(settings.EMAIL_ADMIN_LIST, subject_line, main_body)
def SendNotificationEmail(recipients, c, server_fqdn): """Sends a log upload notification email to passed recipients. Args: recipients: list, str email addresses to email. c: models.Computer entity. server_fqdn: str, fully qualified domain name of the server. """ body = [] body.append('https://%s/admin/host/%s\n' % (server_fqdn, c.uuid)) body.append('Owner: %s' % c.owner) body.append('Hostname: %s' % c.hostname) body.append('Client Version: %s' % c.client_version) body.append('OS Version: %s' % c.os_version) body.append('Site: %s' % c.site) body.append('Track / Config Track: %s / %s' % (c.track, c.config_track)) body.append('Serial Number: %s' % c.serial) body.append('Last preflight: %s' % c.preflight_datetime) body.append('Last postflight: %s' % c.postflight_datetime) body.append(('Preflight count since postflight: %s' % c.preflight_count_since_postflight)) body = '\n'.join(body) subject = 'Logs you requested have been uploaded for %s' % c.hostname mail.SendMail(recipients, subject, body)
def NotifyAdminsOfPackageDeletion(self, pkginfo): """Notifies admins of packages deletions.""" subject_line = 'MSU Package Deleted by %s - %s' % ( users.get_current_user(), pkginfo.filename) main_body = 'That package has been deleted, hope you didn\'t need it.' if mail: mail.SendMail(settings.EMAIL_ADMIN_LIST, subject_line, main_body)
def NotifyAdminsOfChange(self, setting, value): """Notify Admins of changes to Settings.""" subject_line = 'Simian Settings Change by %s' % ( users.get_current_user()) main_body = '%s set to: %s' % (setting, value) mail.SendMail(settings_module.EMAIL_ADMIN_LIST, subject_line, main_body)
def ProposalMailer(self, action): """Notifies admins of proposed changes and changed proposals. Args: action: string, defines what message will be sent. """ current_user = users.get_current_user() current_user_nick = current_user.nickname() body = self._BuildProposalBody(os.environ.get('DEFAULT_VERSION_HOSTNAME'), self.filename) if action == 'proposal': subject = 'Proposal for %s by %s' % (self.filename, current_user_nick) elif action == 'approval': subject = 'Proposal Approved for %s by %s' % ( self.filename, current_user_nick) elif action == 'rejection': subject = 'Proposal Rejected for %s by %s' % ( self.filename, current_user_nick) else: logging.warning('Unknown action in ProposalMailer: %s', action) return recipient_list = [self.user] recipient, _ = settings.Settings.GetItem('email_admin_list') if recipient: recipient_list.append(recipient) mail_tool.SendMail(recipient_list, subject, body)
def post(self): """POST handler.""" if not self.IsAdminUser(): return mode = self.request.get('mode') enabled = self.request.get('enabled') verify = self.request.get('verify') if not verify: self.Render( 'panic_set_verify.html', {'mode': {'name': mode, 'enabled': enabled}, 'report_type': 'panic'}) else: if enabled == 'disable': enabled = False elif enabled == 'enable': enabled = True else: enabled = None if enabled is None: self.error(400) else: try: common.SetPanicMode(mode, enabled) if mail: user = users.get_current_user() subject = 'Panic Mode Update by %s' % user body = '%s has set \'%s\' for Panic Mode.\n' % (user, enabled) mail.SendMail(settings.EMAIL_ADMIN_LIST, subject, body) self.redirect('/admin/panic') except ValueError: self.error(400)
def NotifyAdminsOfPackageChange(self, pkginfo, **kwargs): """Notifies admins of changes to packages.""" subject_line = 'MSU Package Update by %s - %s' % (users.get_current_user(), pkginfo.filename) main_body = ['New configuration:\n'] for key, value in kwargs.iteritems(): if key == 'manifests': if pkginfo.manifests != value: main_body.append('Manifests: %s --> %s' % ( ', '.join(pkginfo.manifests), ', '.join(value))) elif key == 'catalogs': if pkginfo.catalogs != value: main_body.append('Catalogs: %s --> %s' % ( ', '.join(pkginfo.catalogs), ', '.join(value))) elif key == 'install_types': if pkginfo.install_types != value: main_body.append('Install Types: %s --> %s' % ( ', '.join(pkginfo.install_types), ', '.join(value))) elif key == 'munki_name': if pkginfo.munki_name != value: main_body.append('Munki Name: %s --> %s' % ( pkginfo.munki_name, value)) elif (key == 'force_install_after_date' and pkginfo.plist.get(key, '') != value): main_body.append('%s: %s' % (key, value)) elif type(value) is list: if pkginfo.plist.get(key, []) != value: main_body.append('%s: %s --> %s' % ( key, ', '.join(pkginfo.plist.get(key, [])), ', '.join(value))) else: if pkginfo.plist.get(key, '') != value: main_body.append( '%s: %s --> %s' % (key, pkginfo.plist.get(key, ''), value)) mail.SendMail(settings.EMAIL_ADMIN_LIST, subject_line, '\n'.join(main_body))
def NotifyAdminsOfPackageChangeFromPlist(self, log, defer=True): """Notifies admins of changes to packages.""" subject_line = 'MSU Package Update by %s - %s' % ( users.get_current_user(), log.filename) plist_diff = log.plist_diff main_body = 'Diff:\n' + '\n'.join([x['line'] for x in plist_diff]) if mail: mail.SendMail( settings.EMAIL_ADMIN_LIST, subject_line, main_body, defer=defer)
def _NotifyAboutPkgInfosWithoutFile(self): """Verify that entities older than a week have a file in Blobstore.""" for p in models.PackageInfo.all(): if p.blobstore_key and blobstore.BlobInfo.get(p.blobstore_key): continue elif p.mtime < (datetime.datetime.utcnow() - datetime.timedelta(days=7)): subject = 'Package is lacking a file: %s' % p.filename body = ( 'The following package is lacking a DMG file: \n' 'https://%s/admin/package/%s' % ( settings.SERVER_HOSTNAME, p.filename)) mail.SendMail(settings.EMAIL_ADMIN_LIST, subject, body, defer=False)
def NotifyAdminsOfChange(self, setting, value): """Notify Admins of changes to Settings.""" try: recipients = settings_module.EMAIL_ADMIN_LIST except AttributeError: logging.info( 'email_admin_list unset, skipping change notification: %s', setting) return subject_line = 'Simian Settings Change by %s' % ( users.get_current_user()) main_body = '%s set to: %s' % (setting, value) mail.SendMail(recipients, subject_line, main_body)
def get(self): """Handle GET.""" # Verify that all PackageInfo entities older than a week have a file in # Blobstore. for p in models.PackageInfo.all(): if p.blobstore_key and blobstore.BlobInfo.get(p.blobstore_key): continue elif p.mtime < (datetime.datetime.utcnow() - datetime.timedelta(days=7)): subject = 'Package is lacking a file: %s' % p.filename body = ('The following package is lacking a DMG file: \n' 'https://%s/admin/package/%s' % (settings.SERVER_HOSTNAME, p.filename)) mail.SendMail([settings.EMAIL_ADMIN_LIST], subject, body) # Verify all Blobstore Blobs have associated PackageInfo entities. for b in blobstore.BlobInfo.all(): # Filter by blobstore_key as duplicate filenames are allowed in Blobstore. key = b.key() max_attempts = 5 for i in xrange(1, max_attempts + 1): p = models.PackageInfo.all().filter('blobstore_key =', key).get() if p: break elif i == max_attempts: if not b.filename and not b.size: b.delete() break subject = 'Orphaned Blob in Blobstore: %s' % b.filename body = ( 'An orphaned Blob exists in Blobstore. Use App Engine Admin ' 'Console\'s "Blob Viewer" to locate and delete this Blob.\n\n' 'Filename: %s\nBlobstore Key: %s' % (b.filename, key)) mail.SendMail([settings.EMAIL_ADMIN_LIST], subject, body) break time.sleep(1)
def NotifyAdminsOfPackageChangeFromPlist(self, plist_xml): """Notifies admins of changes to packages.""" plist = plist_lib.MunkiPackageInfoPlist(plist_xml) plist.EncodeXml() try: plist.Parse() except plist_lib.PlistError as e: raise models.PackageInfoUpdateError( 'plist_lib.PlistError parsing plist XML: %s', str(e)) subject_line = 'MSU Package Update by %s - %s' % ( users.get_current_user(), plist['installer_item_location']) main_body = str(plist.GetXml(indent_num=2)) if mail: mail.SendMail(settings.EMAIL_ADMIN_LIST, subject_line, main_body)
def _NotifyAdminsOfAutoPromotions(self, promotions): """Notifies Simian admins that a new Apple Updates were auto-promoted. Args: promotions: a dict of track keys with lists of update product entities. """ msg = [] restart_required = False for track, updates in promotions.iteritems(): msg.append('\n%s:' % track) for u in updates: msg.append('\t%s: %s %s %s' % (u.product_id, u.name, u.version, '*' if u.restart_required else '')) if u.restart_required: restart_required = True if restart_required: msg.append('\n\n%s' % RESTART_REQUIRED_FOOTER) msg = '\n'.join(msg) body = 'The following Apple Updates were promoted:\n%s' % msg subject = 'Apple Updates Auto-Promotion' mail.SendMail([settings.EMAIL_ADMIN_LIST], subject, body)
def _NotifyAdminsOfCatalogSync(cls, catalog, new_products, deprecated_products): """Notifies Simian admins that a new Apple Updates catalog was synced. Args: catalog: models.AppleSUSCatalog entity. new_products: a list of models.AppleSUSProduct objects. deprecated_products: a list of models.AppleSUSProduct objects. """ if not new_products and not deprecated_products: return # don't notify if the new catalog has no product changes. new_products_strs = [ '%s: %s %s %s' % (p.product_id, p.name, p.version, '*' if p.restart_required else '') for p in new_products ] deprecated_products_strs = [ '%s: %s %s' % (p.product_id, p.name, p.version) for p in deprecated_products ] catalog_name = catalog.key().name() new_msg = '\n\nNew Products:\n%s' % '\n'.join(new_products_strs) if deprecated_products_strs: dep_msg = '\n\nDeprecated Products:\n%s' % '\n'.join( deprecated_products_strs) else: dep_msg = '' if any(p.restart_required for p in new_products): restart_note = '\n\n%s' % RESTART_REQUIRED_FOOTER else: restart_note = '' body = '%s Apple Updates catalog synced to Simian on %s UTC.%s%s%s' % ( catalog_name, catalog.mtime, new_msg, dep_msg, restart_note) subject = '%s Apple Updates catalog synced to Simian.' % catalog_name mail.SendMail([settings.EMAIL_ADMIN_LIST], subject, body)
def _ChangeProduct(self, product_id): """Method to change properties of a given Apple SUS product.""" user = users.get_current_user() track = self.request.get('track') enabled = self.request.get('enabled', None) manual_override = self.request.get('manual_override', None) unattended = self.request.get('unattended', None) force_install_after_date = self.request.get('force_install_after_date', None) product = models.AppleSUSProduct.get_by_key_name(product_id) if not product: self.response.set_status(httplib.NOT_FOUND) return data = { 'product_id': product_id, } changed_tracks = set() # set/unset manual_override property if manual_override is not None: manual_override = bool(int(manual_override)) product.manual_override = manual_override product.put() log_action = 'manual_override=%s' % manual_override for track in common.TRACKS: if track not in product.tracks: prom_date = applesus.GetAutoPromoteDate(track, product) if prom_date: data['%s_promote_date' % track] = prom_date.strftime('%b. %d, %Y') data['manual_override'] = manual_override # set/unset force_install_after_date property elif force_install_after_date is not None: if force_install_after_date: try: tomorrow = datetime.datetime.utcnow() + datetime.timedelta( hours=12) if datetime.datetime.strptime( # only allow future force install date force_install_after_date, '%Y-%m-%d %H:%M') > tomorrow: product.force_install_after_date_str = force_install_after_date else: self.error(httplib.BAD_REQUEST) return except ValueError: self.error(httplib.BAD_REQUEST) return else: product.force_install_after_date = None product.put() data['force_install_after_date'] = force_install_after_date log_action = 'force_install_after_date=%s' % force_install_after_date changed_tracks.update(product.tracks) # set/unset unattended property elif unattended is not None: unattended = bool(int(unattended)) product.unattended = unattended product.put() data['unattended'] = unattended log_action = 'unattended=%s' % unattended changed_tracks.update(product.tracks) # add/remove track to product elif enabled is not None: enabled = bool(int(enabled)) if enabled: if track not in product.tracks: product.tracks.append(track) product.put() else: if track in product.tracks: product.tracks.remove(track) product.put() log_action = '%s=%s' % (track, enabled) data.update({'track': track, 'enabled': enabled}) changed_tracks.add(track) log = models.AdminAppleSUSProductLog(product_id=product_id, action=log_action, tracks=product.tracks, user=user.email()) log.put() # Send email notification to admins if mail and settings.EMAIL_ON_EVERY_CHANGE: display_name = '%s - %s' % (product.name, product.version) subject = 'Apple SUS Update by %s - %s (%s)' % (user, display_name, product_id) body = '%s has set \'%s\' on %s.\n' % (user, log_action, display_name) body += '%s is now in %s track(s).\n' % (product_id, ', '.join( map(str, product.tracks))) mail.SendMail(settings.EMAIL_ADMIN_LIST, subject, body) # Regenerate catalogs for any changed tracks, if a task isn't already # queued to do so. for track in changed_tracks: if gae_util.ObtainLock(applesus.CATALOG_REGENERATION_LOCK_NAME % track): deferred.defer(applesus.GenerateAppleSUSCatalogs, track=track, delay=180) # TODO(user): add a visual cue to UI so admins know a generation is pending. self.response.headers['Content-Type'] = 'application/json' self.response.out.write(json.dumps(data))
class Package(admin.AdminHandler): """Handler for /admin/package.""" XSRF_PROTECT = True def get(self, filename=None): """GET handler.""" if not self.IsAdminUser() or not filename: self.error(404) return filename = urllib.unquote(filename) p = models.PackageInfo.get_by_key_name(filename) if not p: self.error(404) self.response.out.write('PackageInfo not found: %s' % filename) return p.name = p.plist['name'] p.display_name = p.plist.get('display_name', '') p.unattended = p.plist.get('unattended_install') p.version = p.plist['version'] force_install_after_date = p.plist.get('force_install_after_date', None) if force_install_after_date: p.force_install_after_date = datetime.datetime.strftime( force_install_after_date, '%Y-%m-%d') p.force_install_after_date_time = datetime.datetime.strftime( force_install_after_date, '%H:%M') if self.request.get('plist_xml'): self.Render( 'plist.html', { 'report_type': 'packages', 'plist_type': 'package_plist', 'xml': admin.XmlToHtml(p.plist.GetXml()), 'title': 'Plist for %s' % p.name, 'raw_xml_link': '/pkgsinfo/%s' % filename, }) else: manifests_and_catalogs_unlocked = ( p.blob_info or p.plist.get('PackageCompleteURL')) data = { 'pkg': p, 'report_type': 'package', 'tracks': common.TRACKS, 'install_types': common.INSTALL_TYPES, 'manifest_mod_groups': common.MANIFEST_MOD_GROUPS, 'pkg_safe_to_modify': p.IsSafeToModify(), 'editxml': self.request.get('editxml'), 'manifests_and_catalogs_unlocked': manifests_and_catalogs_unlocked } self.Render('package.html', data) def post(self, filename=None): """POST handler.""" if not self.IsAdminUser(): self.error(403) self.response.out.write('Access Denied for current user') return xsrf_token = self.request.get('xsrf_token', None) report_type = filename and 'package' or 'packages' if not xsrf.XsrfTokenValidate(xsrf_token, report_type): self.error(400) self.response.out.write( 'Invalid XSRF token. Please refresh and retry.') return if filename: filename = urllib.unquote(filename) # If we're updating from new plist xml, perform the update and return. if self.request.get('new_pkginfo_plist'): self.UpdatePackageInfoFromPlist() return # All non-plist updates require an existing PackageInfo entity. p = models.PackageInfo.get_by_key_name(filename) if not p: self.error(404) self.response.out.write('Filename not found: %s' % filename) return if self.request.get('delete') == '1': if settings.EMAIL_ON_EVERY_CHANGE: self.NotifyAdminsOfPackageDeletion(p) p.delete() self.redirect('/admin/packages?msg=%s successfully deleted' % filename) return elif self.request.get('submit', None) == 'save': self.UpdatePackageInfo(p) elif self.request.get('unlock') == '1': if settings.EMAIL_ON_EVERY_CHANGE: self.NotifyAdminsOfPackageUnlock(p) p.MakeSafeToModify() self.redirect('/admin/package/%s?msg=%s is safe to modify' % (filename, filename)) else: self.error(400) self.response.out.write( 'No action specified or unknown action.') elif self.request.get('new_pkginfo_plist'): # No filename was specified, so we're creating a new PackageInfo. self.UpdatePackageInfoFromPlist(create_new=True) else: self.error(404) def NotifyAdminsOfPackageChange(self, pkginfo, **kwargs): """Notifies admins of changes to packages.""" subject_line = 'MSU Package Update by %s - %s' % ( users.get_current_user(), pkginfo.filename) main_body = ['New configuration:\n'] for key, value in kwargs.iteritems(): # TODO(user) : This should do better checking of value. if value: if key == 'manifests': if pkginfo.manifests != value: main_body.append( 'Manifests: %s --> %s' % (', '.join(pkginfo.manifests), ', '.join(value))) elif key == 'catalogs': if pkginfo.catalogs != value: main_body.append( 'Catalogs: %s --> %s' % (', '.join(pkginfo.catalogs), ', '.join(value))) elif key == 'install_types': if pkginfo.install_types != value: main_body.append( 'Install Types: %s --> %s' % (', '.join( pkginfo.install_types), ', '.join(value))) elif key == 'munki_name': if pkginfo.munki_name != value: main_body.append('Munki Name: %s --> %s' % (pkginfo.munki_name, value)) elif (key == 'force_install_after_date' and pkginfo.plist[key] != value): main_body.append('%s: %s' % (key, value)) elif type(value) is list: if pkginfo.plist[key] != value: main_body.append('%s: %s --> %s' % (key, ', '.join( pkginfo.plist[key]), ', '.join(value))) else: if pkginfo.plist[key] != value: main_body.append('%s: %s --> %s' % (key, pkginfo.plist[key], value)) mail.SendMail(settings.EMAIL_ADMIN_LIST, subject_line, '\n'.join(main_body)) def NotifyAdminsOfPackageChangeFromPlist(self, plist_xml): """Notifies admins of changes to packages.""" plist = plist_lib.MunkiPackageInfoPlist(plist_xml) plist.EncodeXml() try: plist.Parse() except plist_lib.PlistError, e: raise models.PackageInfoUpdateError( 'plist_lib.PlistError parsing plist XML: %s', str(e)) subject_line = 'MSU Package Update by %s - %s' % ( users.get_current_user(), plist['installer_item_location']) main_body = str(plist.GetXml(2)) mail.SendMail(settings.EMAIL_ADMIN_LIST, subject_line, main_body)