def delete_profiles(profs, dbo, table): ''' deletes all database entries matching user's command line options Args: profs - list of profiles to delete by name dbo - database object table - database table name Returns: True if any errors encountered, False otherwise Exceptions: none ''' # if any serious errors encountered, set exit status has_errors = False queue = dbo.getQueue() # Build a list of criteria for WHERE clause db_cols = [u'rowid'] + [u'file'] # loop through all profiles from command line and delete them for profile_name in profs: query_str = "SELECT " + ", ".join(db_cols) + " FROM " + table + \ " WHERE name=" + AIdb.format_value('name', profile_name) logging.debug("query=" + query_str) query = AIdb.DBrequest(query_str, commit=True) queue.put(query) query.waitAns() # check response, if failure, getResponse prints error rsp = query.getResponse() if rsp is None: has_errors = True continue if len(rsp) == 0: print >> sys.stderr, _("\tProfile %s not found.") % profile_name has_errors = True continue # delete database record and any accompanying internal profile file for response in rsp: deldict = dict() iresponse = iter(response) for crit in db_cols: deldict[crit] = next(iresponse) query_str = "DELETE FROM %s WHERE rowid=%d" % \ (table, deldict['rowid']) delquery = AIdb.DBrequest(query_str, commit=True) queue.put(delquery) delquery.waitAns() # check response, if failure, getResponse prints error if delquery.getResponse() is None: has_errors = True continue print >> sys.stderr, _("\tDeleted profile %s.") % profile_name # delete static (internal) files only if deldict['file'] is None or \ not deldict['file'].startswith(sc.INTERNAL_PROFILE_DIRECTORY): continue try: os.unlink(deldict['file']) except OSError, (errno, errmsg): if errno != ENOENT: # does not exist print >> sys.stderr, _( "Error (%s): Problem deleting %s (%s): %s") \ % (errno, profile_name, deldict['file'], errmsg) has_errors = True continue
def parse_options(cmd_options=None): ''' Parse and validate options Args: options handled by OptionParser Returns: options ''' parser = OptionParser(usage='\n' + get_usage()) parser.add_option("-p", "--profile", dest="profile_name", action="append", default=list(), help=_("Name of profile")) parser.add_option("-n", "--service", dest="service_name", default='', help=_("Name of install service.")) # Get the parsed options using parse_args(). We know we don't have # args, so check to make sure there are none. options, args = parser.parse_args(cmd_options) if not options.profile_name or not options.service_name: parser.error(_("Both -p|--profile and -n|--service are required.")) if len(args): parser.error(_("Unexpected argument(s): %s" % args)) try: validate_service_name(options.service_name) except ValueError as err: parser.error(err) return options
def verifyDBStructure(self): '''Ensures reasonable DB schema and columns or else raises a SystemExit ''' # get the names of each table in the database query = DBrequest("SELECT * FROM SQLITE_MASTER") self._requests.put(query) query.waitAns() # if query fails, getResponse prints error message if query.getResponse() is None: raise SystemExit(1) # iterate over each table in the database for row in iter(query.getResponse()): if "manifests" == row['tbl_name']: break # if we do not break out we do not have a manifest table else: raise SystemExit(_("Error:\tNo manifests table")) # iterate over each column of the manifests table query = DBrequest("PRAGMA table_info(manifests)") self._requests.put(query) query.waitAns() # gather column names in a list columns = list() for col in iter(query.getResponse()): columns.append(col['name']) # ensure we have a name, instance and at least one criteria column if "name" not in columns or "instance" not in columns or \ len(columns) < 3: raise SystemExit(_("Error:\tDatabase columns appear malformed"))
def criteria_to_dict(criteria): """ Convert criteria list into dictionary. This function is intended to be called by a main function, or the options parser, so it can potentially raise the SystemExit exception. Args: criteria in list format: [ criteria=value, criteria=value, ... ] where value can be a: single string value space-separated string value (list of values) range (<lower>-<upper>) Returns: dictionary of criteria { criteria: value, criteria: value, ... } with all keys in lower case, values are case-sensitive. Raises: ValueError on malformed name=value strings in input list. """ cri_dict = dict() for entry in criteria: entries = entry.partition("=") if entries[1]: if not entries[0]: raise ValueError(_("Missing criteria name in " "'%s'\n") % entry) elif entries[0].lower() in cri_dict: raise ValueError(_("Duplicate criteria: '%s'\n") % entries[0]) elif not entries[2]: raise ValueError(_("Missing value for criteria " "'%s'\n") % entries[0]) cri_dict[entries[0].lower()] = entries[2] else: raise ValueError(_("Criteria must be of the form " "<criteria>=<value>\n")) return cri_dict
def check_published_manifest(service, dbn, manifest_name): """ Used for checking that a manifest is already published in the install service specified. Checks to make sure manifest exists in the install service's DB, and that the manifest also exists in the install service's published files area. Args: service - service object for service dbn - dbn object of install service to check against. manifest_name - name of manifest to check. Postconditions: None Returns: True if manifest exists in install service False if manifest does not exist. """ # Check if manifest exists in the service's criteria DB. if AIdb.sanitizeSQL(manifest_name) not in AIdb.getManNames(dbn.getQueue()): print(_("Error: install service does not contain the specified " "manifest: %s") % manifest_name) return False # Check if manifest file exists in the service's published area. published_path = os.path.join(service.manifest_dir, manifest_name) if not os.path.exists(published_path): print(_("Error: manifest missing from published area: %s") % published_path) return False return True
def do_update_manifest(cmd_options=None): ''' Update the contents of an existing manifest. ''' # check that we are root if os.geteuid() != 0: raise SystemExit(_("Error:\tRoot privileges are required for " "this command.")) # load in all the options and file data. Validate proper manifests. data = parse_options(DO_UPDATE, cmd_options) service = AIService(data.service_name) manifest_path = os.path.join(service.manifest_dir, data.manifest_name) if not os.path.exists(manifest_path): raise SystemExit(_("Error:\tNo manifest or script with name " "%s is registered with this service.\n" "\tPlease use installadm " "create-manifest instead.") % data.manifest_name) # move the manifest into place df.place_manifest(data, manifest_path)
def verify_SC_manifest(self, data, name=None): """ Used for verifying and loading SC manifest Args: data - file path, or StringIO object. name - Optionally, takes a name to provide error output, as a StringIO object will not have a file path to provide. Returns: Provide an XML DOM for the SC manifest Raises: SystemExit on validation or file open error. """ if not isinstance(data, StringIO.StringIO): try: data = file(data, 'r') except IOError: if name is None: raise SystemExit(_("Error:\tCan not open: %s") % data) else: raise SystemExit(_("Error:\tCan not open: %s") % name) xml_root, errors = verifyXML.verifyDTDManifest(self.smfDtd, data) if errors: if not isinstance(data, StringIO.StringIO): print >> sys.stderr, (_("Error:\tFile %s failed validation:") % data.name) else: print >> sys.stderr, (_("Error:\tSC Manifest %s failed " "validation:") % name) for err in xml_root: print >> sys.stderr, err raise SystemExit() return(xml_root)
def check_imagepath(imagepath): ''' Check if image path exists. If it exists, check whether it has a valid net image. An empty dir is ok. Raises: ValueError if a problem exists with imagepath ''' # imagepath must be a full path if not os.path.isabs(imagepath): raise ValueError(_("\nA full pathname is required for the " "image path.\n")) # imagepath must not exist, or must be empty if os.path.exists(imagepath): try: dirlist = os.listdir(imagepath) except OSError as err: raise ValueError(err) if dirlist: if com.AI_NETIMAGE_REQUIRED_FILE in dirlist: raise ValueError(_("\nThere is a valid image at (%s)." "\nPlease delete the image and try " "again.\n") % imagepath) else: raise ValueError(_("\nTarget directory is not empty: %s\n") % imagepath)
def validate_file(profile_name, profile, image_dir=None, verbose=True): '''validate a profile file, given the file path and profile name Args: profile_name - reference name of profile profile - file path of profile image_dir - path of service image, used to locate service_bundle verbose - boolean, True if verbosity desired, False otherwise Return: Raw profile in string format if it is valid, None otherwise. ''' if verbose: print >> sys.stderr, (_("Validating static profile %s...") % profile_name) try: with open(profile, 'r') as fip: raw_profile = fip.read() except IOError as strerror: print >> sys.stderr, _("Error opening profile %s: %s") % \ (profile_name, strerror) return errmsg = '' validated_xml = '' tmpl_profile = None try: # do any templating tmpl_profile = sc.perform_templating(raw_profile) # validate validated_xml = sc.validate_profile_string(tmpl_profile, image_dir, dtd_validation=True, warn_if_dtd_missing=True) except lxml.etree.XMLSyntaxError, err: errmsg = _('XML syntax error in profile %s:' % profile_name)
def set_AI_schema(self, manifest_file=None): """ Sets self._AIschema and errors if imagepath not yet set. Args: None Raises: SystemExit if unable to find a valid AI schema Returns: None """ # If manifest is provided, it must have a DOCTYPE string that # references a DTD. if not os.path.exists(manifest_file): raise SystemExit(_("Error: Cannot access Manifest \"%s\"." % manifest_file)) # Try first to get schema basename from DOCINFO in XML schema_basename = None try: manifest_doc = lxml.etree.parse(manifest_file) system_url = manifest_doc.docinfo.system_url if system_url is not None: schema_basename = os.path.basename(system_url) else: raise SystemExit(_("Error: manifest must have a DOCTYPE string" " with DTD reference.")) except lxml.etree.XMLSyntaxError, syntax_error: raise SystemExit(_("Error: There was a syntax error parsing the " "manifest %(mf)s:\n %(error)s") % {'mf': manifest_file, 'error': str(syntax_error)})
def remove_client_dhcp_config(client_id): ''' If a local DHCP server is running, remove any client configuration for this client from its configuration. If not, inform end-user that the client-service binding should no longer be referenced in the DHCP configuration. ''' server = dhcp.DHCPServer() if server.is_configured(): # A local DHCP server is configured. Check for a host entry and remove # it if found. mac_address = client_id[2:] mac_address = AIdb.formatValue('mac', mac_address) if server.host_is_configured(mac_address): print cw(_("Removing host entry '%s' from local DHCP " "configuration.") % mac_address) server.remove_host(mac_address) if server.is_online(): try: server.control('restart') except dhcp.DHCPServerError as err: print >> sys.stderr, cw(_("Unable to restart the DHCP " "SMF service: %s" % err)) return else: # No local DHCP configuration, inform user that it needs to be # unconfigured elsewhere. print cw(_("No local DHCP configuration found. Unless it will be " "reused, the bootfile '%s' may be removed from the DHCP " "configuration\n" % client_id))
def set_imagepath(options): '''Change the location of a service's image''' logging.debug("set %s imagepath to %s", options.svcname, options.value) new_imagepath = options.value.strip() service = svc.AIService(options.svcname) if service.is_alias(): raise SystemExit(cw(_('\nError: Can not change the imagepath of an ' 'alias.'))) if not os.path.isabs(new_imagepath): raise SystemExit(_("\nError: A full pathname is required for the " "imagepath.\n")) if os.path.exists(new_imagepath): raise SystemExit(_("\nError: The imagepath already exists: %s\n") % new_imagepath) if os.path.islink(new_imagepath): raise SystemExit(_("\nError: The imagepath may not be a symlink.\n")) new_imagepath = new_imagepath.rstrip('/') try: service.relocate_imagedir(new_imagepath) except (svc.MountError, aismf.ServicesError) as error: raise SystemExit(error)
def do_publish_manifest(cmd_options=None): ''' Publish a manifest, associating it with an install service. ''' # check that we are root if os.geteuid() != 0: raise SystemExit(_("Error:\tRoot privileges are required for " "this command.")) # load in all the options and file data. Validate proper manifests. data = parse_options(DO_CREATE, cmd_options) service = AIService(data.service_name) # Disallow multiple manifests or scripts with the same mname. manifest_path = os.path.join(service.manifest_dir, data.manifest_name) if os.path.exists(manifest_path): raise SystemExit(_("Error:\tName %s is already registered with " "this service.") % data.manifest_name) # if criteria are provided, make sure they are a unique set. if data.criteria: find_colliding_manifests(data.criteria, data.database, find_colliding_criteria(data.criteria, data.database)) # Add all manifests to the database, whether default or not, and whether # they have criteria or not. df.insert_SQL(data) # move the manifest into place df.place_manifest(data, manifest_path) # if we have a default manifest do default manifest handling if data.set_as_default: service.set_default_manifest(data.manifest_name)
def do_export_manifest(options): ''' Export a manifest. ''' save_errno = 0 for mname in options.mnames: # Get the pathname of the manifest to export. input_mname = os.path.join(options.service.manifest_dir, mname) if options.output_isdir: output_name = "/".join([options.output_name, mname]) else: output_name = options.output_name if output_name == SCREEN and options.file_count > 1: display_file_header(_("manifest: ") + mname) # Find the file in the directory and copy it to screen or file. try: shutil.copyfile(input_mname, output_name) except IOError as err: print >> sys.stderr, _("Error exporting manifest: " "%(error)s: %(file)s") % ( {"error": err.strerror, "file": err.filename}) save_errno = err.errno print return save_errno
def parse_options(cmd_options=None): """ Parse and validate options Args: Optional cmd_options, used for unit testing. Otherwise, cmd line options handled by OptionParser Returns: tuple consisting of svcname and newsvcname """ usage = '\n' + get_usage() parser = OptionParser(usage=usage) # Get the parsed arguments using parse_args() unused, args = parser.parse_args(cmd_options) if len(args) < 2: parser.error(_("Missing one or more required arguments.")) elif len(args) > 2: parser.error(_("Too many arguments: %s" % args)) svcname = args[0] newsvcname = args[1] # validate service names try: validate_service_name(newsvcname) except ValueError as err: parser.error(err) logging.debug("Renaming %s to %s", svcname, newsvcname) return (svcname, newsvcname)
def do_set_service(cmd_options=None): ''' Set a property of a service ''' # check that we are root if os.geteuid() != 0: raise SystemExit(_("Error: Root privileges are required for this " "command.")) options = parse_options(cmd_options) # validate service name try: validate_service_name(options.svcname) except ValueError as err: raise SystemExit(str(err)) logging.debug("options %s", options) if not config.is_service(options.svcname): raise SystemExit(_('\nError: Service does not exist: %s\n') % options.svcname) if options.prop == "default-manifest": do_set_service_default_manifest(options) elif options.prop == "aliasof": return set_aliasof(options) elif options.prop == "imagepath": return set_imagepath(options)
def _register_a_service(self, name, interfaces=None, port=0, comments=None): '''Method: _register_a_service, private to class Description: Register a single service on the interfaces Args interfaces - the interfaces to register the service on instance - the SMF service instance handle name - the service name to be registered port - the port that the service is listening on, if port is 0 then registering a service listed in the AI SMF service instance. comments - comments for the ad hoc registered service Returns list_sdrefs - list of service references Raises AImDNSError - if SMF status property does not exist, OR if SMF txt_record property does not exist, OR if SMF port property does not exist. ''' if not self.register_initialized: self.exclude = libaimdns.getboolean_property(common.SRVINST, common.EXCLPROP) self.networks = libaimdns.getstrings_property(common.SRVINST, common.NETSPROP) self.register_initialized = True smf_port = None # if port is 0 then processing an AI service if port == 0: serv = config.get_service_props(name) if not serv: raise AIMDNSError(cw(_('error: aiMDNSError: no such ' 'installation service "%s"') % name)) # ensure the service is enabled if config.PROP_STATUS not in serv: raise AIMDNSError(cw(_('error: aiMDNSError: installation ' 'service key "status" property does ' 'not exist'))) if serv[config.PROP_STATUS] != config.STATUS_ON: print(cw(_('warning: Installation service "%s" is not enabled ' % name))) return None smf_port = config.get_service_port(name) if not smf_port: try: smf_port = libaimdns.getinteger_property(common.SRVINST, common.PORTPROP) smf_port = str(smf_port) except libaimdns.aiMDNSError, err: raise AIMDNSError(cw(_('error: aiMDNSError: port property ' 'failure (%s)') % err))
def register(self, servicename=None, port=0, interfaces=None, comments=None): '''Method: register Description: Registers an ad hoc service. This method will loop until the the application is killed. Args servicename - the name of the ad hoc service port - the port to use for the ad hoc service interfaces - the interfaces to register the ad hoc service on comments - the service comments for the ad hoc service Returns None Raises SystemError - if the SMF service instance can not be loaded. AImDNSError - if unable to register the service OR if no servicename is present. ''' self._do_lookup = False if servicename is not None: self.servicename = servicename if self.servicename is None: raise ValueError(_('must specify a service to register')) if self.verbose: print _('Registering "%s"...') % self.servicename # get the AI SMF service instance information try: self.instance = smf.AISCF(FMRI="system/install/server") except SystemError: raise SystemError(_("error: the system does not have the " "system/install/server SMF service")) # use the interfaces within the class if none are passed in if interfaces is None: interfaces = self.interfaces sdrefs = self._register_a_service(name=self.servicename, interfaces=interfaces, port=port, comments=comments) if sdrefs is not None: self.sdrefs[servicename] = sdrefs self._handle_events() else: raise AIMDNSError(cw(_('error: aiMDNSError: mDNS ad hoc ' 'registration failed for "%s" service') % self.servicename))
def validate_profile_string(profile_str, image_dir=None, resolve_entities=True, dtd_validation=False, warn_if_dtd_missing=False): ''' Given the profile contained in a string variable, validate Args: profile_str - profile in string format image_dir - path of service image, used to locate service_bundle if None, only unit test against local service_bundle(4) resolve_entities - if True, ask XML parser to resolve all entities dtd_validation - if True, validate against a DTD in the profile warn_if_dtd_missing - if True, raise an exception if the DTD not found Returns: profile as string with any inclusions Exceptions: etree.XMLSyntaxError ''' import lxml.etree as etree from StringIO import StringIO # create an XMLParser object with settings parser = etree.XMLParser( # always read DTD for XInclude namespace xi in service_bundle(4) load_dtd=True, resolve_entities=resolve_entities, dtd_validation=False ) root = etree.parse(StringIO(profile_str), parser) if not resolve_entities: # just check basic XML, no inclusions return profile_str # validate against DTD if provided if dtd_validation and \ (root.docinfo.externalDTD is not None or root.docinfo.internalDTD is not None): # check for service_bundle(4) if root.docinfo.system_url is not None and \ root.docinfo.system_url.find('/service_bundle.dtd.') == -1: print >> sys.stderr, _( "Warning: DOCTYPE %s specified instead of service_bundle(4). " "The file might not be a profile.") % root.docinfo.system_url if image_dir is None: # unit testing only err = validate_profile_external_dtd(profile_str) if err: raise etree.XMLSyntaxError(err, '', '', '') return profile_str dtd_file = os.path.join(image_dir, 'auto_install', 'service_bundle.dtd.1') # if warning only on DTD missing, and DTD is indeed missing if root.docinfo.system_url is not None and warn_if_dtd_missing and \ not os.path.exists(dtd_file): print >> sys.stderr, _( "Warning: DTD %s not found. Cannot validate completely.") % \ dtd_file return etree.tostring(root) # parse, validating against external DTD err = validate_profile_external_dtd(profile_str, dtd_file) if err: raise etree.XMLSyntaxError(err, '', '', '') return profile_str
def send_needed_criteria(port): '''Replies to the old client with the needed criteria Args port - the originating port for the old client Returns None Raises None ''' # Establish the service SQL database based upon the # port number for the service path = os.path.join(com.AI_SERVICE_DIR_PATH, str(port), 'AI.db') if os.path.exists(path): try: aisql = AIdb.DB(path) aisql.verifyDBStructure() except StandardError as err: # internal error, record the error in the server error_log sys.stderr.write(_('error:AI database access error\n%s\n') % err) # report the error to the requesting client print "Content-Type: text/html" # HTML is following print # blank line, end of headers sys.stdout.write(_("error:AI database access error\n%s\n") % err) sys.exit(1) else: # not an internal error, report to the requesting client only print "Content-Type: text/html" # HTML is following print # blank line, end of headers print _("Error:unable to determine criteria " "for service associated with port"), port return # build the required criteria list xml = lxml.etree.Element("CriteriaList") # old version number version_value = lxml.etree.Element("Version") version_value.attrib["Number"] = COMPATIBILITY_VERSION xml.append(version_value) # pull the required criteria from the SQL database for crit in AIdb.getCriteria(aisql.getQueue(), strip=True): tag = lxml.etree.Element("Criteria") tag.attrib["Name"] = crit xml.append(tag) xmlstr = lxml.etree.tostring(xml, pretty_print=True) # report the results print "Content-Length:", len(xmlstr) # Length of XML reply print "Content-Type: text/xml" # XML is following print # blank line, end of headers print xmlstr
def do_alias_service(options): ''' Create an alias of a service ''' # Ensure that the base service is a service if not config.is_service(options.aliasof): raise SystemExit(_("\nService does not exist: %s\n") % options.aliasof) basesvc = AIService(options.aliasof) # Ensure that the base service is not an alias if basesvc.is_alias(): raise SystemExit(_("\nError: Cannot create alias of another alias.\n")) image = basesvc.image if options.svcname in DEFAULT_ARCH: if ((image.arch == 'sparc' and 'sparc' not in options.svcname) or (image.arch == 'i386' and 'i386' not in options.svcname)): raise SystemExit(cw(_("\nError: %s can not be an alias of a " "service with a different architecture.\n") % options.svcname)) logging.debug("Creating alias of service %s", options.aliasof) print _("\nCreating %(arch)s alias: %(name)s\n") % \ {'arch': image.arch, 'name': options.svcname} logging.debug("Creating AIService aliasname %s base svc=%s, bootargs=%s", options.svcname, options.aliasof, options.bootargs) try: service = AIService.create(options.svcname, image, alias=options.aliasof, bootargs=options.bootargs) except AIServiceError as err: raise SystemExit(err) # if recreating default-sparc alias, recreate symlinks if service.is_default_arch_service() and image.arch == 'sparc': logging.debug("Recreating default-sparc symlinks") service.do_default_sparc_symlinks(options.svcname) # Register & enable service # (Also enables system/install/server, as needed) try: service.enable() except (config.ServiceCfgError, MountError) as err: raise SystemExit(err) except aismf.ServicesError as err: # don't error out if the service is successfully created # but the services fail to start - just print out the error # and proceed print err
def set_aliasof(options): '''Change a service's base service''' logging.debug("set alias %s's basesvc to %s", options.svcname, options.value) basesvcname = options.value aliasname = options.svcname if not config.is_service(basesvcname): raise SystemExit(_('\nError: Service does not exist: %s\n') % basesvcname) if aliasname == basesvcname: raise SystemExit(_('\nError: Alias name same as service name: %s\n') % aliasname) aliassvc = svc.AIService(aliasname) if not aliassvc.is_alias(): raise SystemExit(_('\nError: Service exists, but is not an ' 'alias: %s\n') % aliasname) basesvc_arch = svc.AIService(basesvcname).arch aliassvc_arch = aliassvc.arch if basesvc_arch != aliassvc_arch: raise SystemExit(_("\nError: Architectures of service and alias " "are different.\n")) if aliassvc.is_aliasof(basesvcname): raise SystemExit(_("\nError: %s is already an alias of %s\n") % (aliasname, basesvcname)) if svc.AIService(basesvcname).is_alias(): raise SystemExit(_("\nError: Cannot alias to another alias.\n")) # Make sure we aren't creating inter dependencies all_aliases = config.get_aliased_services(aliasname, recurse=True) if basesvcname in all_aliases: raise SystemExit(cw(_("\nError: %s can not be made an alias of %s " "because %s is dependent on %s\n") % (aliasname, basesvcname, basesvcname, aliasname))) try: aliassvc.update_basesvc(basesvcname) except (OSError, config.ServiceCfgError) as err: raise SystemExit(_("Failed to set 'aliasof' property of : %s") % aliasname) except svc.MultipleUnmountError as err: print >> sys.stderr, _("Failed to disable alias") raise SystemExit(err) except svc.MountError as err: print >> sys.stderr, _("Failed to enable alias") raise SystemExit(err) except svc.UnsupportedAliasError as err: raise SystemExit(err)
def _register_callback(self, sdref, flags, errorcode, name, regtype, domain): '''Method: _register_callback, private to class Description: DNS Callback for the registration process Args sdref - service reference standard argument for callback, not used flags - flag to determine what action is taking place standard argument for callback, not used errorcode - flag to determine if a registration error occurred name - name of the service regtype - registration type, should be _OSInstall._tcp. domain - DNS domain, either local or remote Returns None Raises None ''' # note: DNSService Errors are ignored here and handled elsewhere. if errorcode == pyb.kDNSServiceErr_NoError and \ self.verbose: print _('Registered service:') print _('\tname = %s') % name print _('\tregtype = %s') % regtype print _('\tdomain = %s') % domain
def list_local_services(services, name=None): """ Lists the local services for a host. If name is not None then it prints only the named service. Args services = config.get_all_service_props() name = service name Returns None Raises None """ sdict, width, awidth = get_local_services(services, sname=name) width = max(width, len(_('Service Name'))) awidth = max(awidth, len(_('Alias Of'))) fields = [[_('Service Name'), width]] fields.extend([[_('Alias Of'), awidth]]) fields.extend([[_('Status'), FDICT['status']]]) fields.extend([[_('Arch'), FDICT['arch']]]) fields.extend([[_('Image Path'), len(_('Image Path'))]]) do_header(fields) print_local_services(sdict, width, awidth)
def list_local_profiles(linst, name=None): """ list the local profiles. If name is not passed in then print all the local profiles. Otherwise list the named service's profiles' criteria. Args inst = smf.AISCF() name = service name Returns None Raises None """ # list -p if not name: sdict, swidth, mwidth, cwidth = \ get_manifest_or_profile_names(linst, AIdb.PROFILES_TABLE) if not sdict: output = _('There are no profiles configured for local ' 'services.\n') sys.stdout.write(output) return swidth = max(swidth, len(_('Service Name'))) + 1 fields = [[_('Service Name'), swidth]] mwidth = max(mwidth, len(_('Profile'))) fields.extend([[_('Profile'), mwidth]]) do_header(fields) print_local_profiles(sdict, swidth) # list -p -n <service> else: sdict, mwidth, cwidth = \ get_mfest_or_profile_criteria(name, linst, AIdb.PROFILES_TABLE) if not sdict: output = _('There are no profiles configured for local service, ' '"%s".\n') % name sys.stdout.write(output) return mwidth = max(mwidth, len(_('Profile'))) + 1 fields = [[_('Profile'), mwidth]] fields.extend([[_('Criteria'), len(_('Criteria'))]]) do_header(fields) print_service_profiles(sdict, mwidth, cwidth)
def register_all(self, interfaces=None): '''Method: register_all Description: Registers all AI services. This method will loop until the the application is killed. It responds to SIGHUP signals, re-checking all registered services for additions and removals of a service from the AI SMF service. Args interfaces - the interfaces to register the AI services on Returns None Raises SystemError - if the SMF service instance can not be loaded. AIMDNSError - if the Instance keys are not loaded. ''' self._do_lookup = False if self.verbose: print _('Registering all Auto Install services...') # get the AI SMF service instance information try: self.instance = smf.AISCF(FMRI="system/install/server") except SystemError: raise SystemError(_("error: the system does not have the " "system/install/server SMF service")) self.instance_services = config.get_all_service_names() # use interfaces within the class if none are passed if interfaces is None: interfaces = self.interfaces # iterate through each service and register it for servicename in self.instance_services: sdrefs = self._register_a_service(name=servicename, interfaces=interfaces) # save the service reference within the class if sdrefs: if servicename in self.sdrefs: self.sdrefs[servicename].extend(sdrefs) else: self.sdrefs[servicename] = sdrefs signal.signal(signal.SIGHUP, self._signal_hup) self._handle_events()
def browse(self): '''Method: browse Description: browse all available _OSInstall._tcp services. Args None Returns True -- if a service is found -- OR -- False -- if a service is not found -- OR -- sdref -- if actually in find mode Raises AImDNSError - if there are no service references available ''' self.sdrefs = dict() self._found = False self._resolved = list() # only browse over the number of interfaces available self.count = len(self.interfaces) if self.verbose: print _('Browsing for services...') # pybonjour bug -- can not Browse on a specific interfaceIndex # thus only Browsing on all interfaces (0). If and when this # bug gets fixed up stream then the iteration over the interfaces # would be appropriate. The code should look like what is in # the find() method. # Resolve the DNS service sdref = pyb.DNSServiceBrowse(flags=0, regtype=common.REGTYPE, domain=common.DOMAIN, interfaceIndex=0, callBack=self._browse_callback) # save the service reference if sdref: self.sdrefs['browse'] = [sdref] else: raise AIMDNSError(_('error: aiMDNSError: mDNS browse failed')) # cause the event loop to loop only 5 times self._do_lookup = True self._handle_events() return self._found
def remove_client_from_config(service_name, client_id): ''' Remove client entry from .config file Input: service name client_id of entry to remove Raises: ServiceCfgError if service missing .config file ''' logging.log(com.XDEBUG, "**** START service_config.remove_client_from_config: %s " "%s ****", service_name, client_id) cfg = _read_config_file(service_name) if cfg is None: raise ServiceCfgError(_("\nMissing configuration file for " "service: %s\n" % service_name)) if CLIENTS not in cfg.sections(): return clients = cfg.options(CLIENTS) if client_id.lower() in clients: cfg.remove_option(CLIENTS, client_id.lower()) # if last client deleted, remove section if not cfg.options(CLIENTS): cfg.remove_section(CLIENTS) _write_config_file(service_name, cfg)
def is_client(client_id): ''' Find out if client exists Input: clientid ('01aabbccaabbcc') Returns: True if client exists False otherwise Raises: ServiceCfgError if service missing .config file ''' logging.log(com.XDEBUG, "**** START service_config.is_client: %s ****", client_id) exists = False all_svc_names = get_all_service_names() for svc in all_svc_names: cfg = _read_config_file(svc) if cfg is None: raise ServiceCfgError(_("\nMissing configuration file for " "service: %s\n" % svc)) if CLIENTS not in cfg.sections(): continue clients = dict(cfg.items(CLIENTS)) # cfgparser changes client_id to lower if client_id.lower() in clients: exists = True logging.log(com.XDEBUG, 'client exists: %s', exists) return exists
def find_client(client_id): ''' Get info on a particular client Input: clientid ('01aabbccaabbcc') Returns: tuple consisting of service_name of client and dict of client data, both None if client does not exist. Client data can include: FILES: [list of files to remove when deleting client] BOOTARGS: comma separated string of client specific boot args <property>=<value>, Raises: ServiceCfgError if service missing .config file ''' logging.log(com.XDEBUG, "**** START service_config.find_client: %s ****", client_id) service = None files = None for svc in get_all_service_names(): cfg = _read_config_file(svc) if cfg is None: raise ServiceCfgError(_("\nMissing configuration file for " "service: %s\n" % svc)) if CLIENTS not in cfg.sections(): continue clients = dict(cfg.items(CLIENTS)) # cfgparser changes client_id to lower if client_id.lower() in clients: data = clients[client_id.lower()] files = ast.literal_eval(data) service = svc break logging.log(com.XDEBUG, 'service is %s, files are %s', service, files) return (service, files)
class InstalladmPkgImage(InstalladmImage): '''Handles creation of a pkg(5)-based InstalladmImage''' _PKG_CLIENT_NAME = "installadm" DEFAULT_PKG_NAME = 'install-image/solaris-auto-install' ARCH_VARIANT = u'variant.arch' SVC_NAME_ATTR = 'com.oracle.install.service-name' INVALID_AI_IMAGE = _( "\nError:\tThe pkg image is not an Automated Installer image.\n") def __init__(self, image_path, pkg_image=None): super(InstalladmPkgImage, self).__init__(image_path) self._pkgimg = pkg_image @classmethod def image_create(cls, fmri_or_p5i, targetdir, arch=None, publisher=None): logging.debug("image_create, install from=%s", fmri_or_p5i) tracker = pkg.client.progress.CommandLineProgressTracker() root_img = pkg.client.api.ImageInterface( "/", PKG5_API_VERSION, tracker, None, cls._PKG_CLIENT_NAME) # In the future, handle: # * SSL repos (keys/certs may need explicit flags from user) if publisher is not None: prefix = publisher[0] order = [prefix] repo = pkg.client.publisher.Repository(origins=[publisher[1]]) pub = pkg.client.publisher.Publisher(prefix, repository=repo) publishers = {prefix: pub} else: publishers = dict() order = list() for pub in root_img.get_publishers(duplicate=True): if pub.disabled: logging.debug("skipping disabled publisher '%s'", pub.prefix) continue publishers[pub.prefix] = pub order.append(pub.prefix) if not publishers: raise ImageError(_("\nError:\tThere are no enabled " "publishers.\n")) if arch is None: arch = root_img.img.get_variants()[cls.ARCH_VARIANT] variants = {cls.ARCH_VARIANT: arch} props = {pkg.client.imageconfig.FLUSH_CONTENT_CACHE: True} pkgimg = pkg.client.api.image_create( cls._PKG_CLIENT_NAME, PKG5_API_VERSION, targetdir, pkg.client.imagetypes.IMG_USER, is_zone=False, progtrack=tracker, props=props, variants=variants ) # Add publishers to the new image, preserving the original # search order search_after = None for pub_prefix in order: add_pub = publishers[pub_prefix] pkgimg.add_publisher(add_pub, search_after=search_after) logging.debug("adding publisher '%s' after '%s'", add_pub.prefix, search_after) search_after = pub_prefix ai_img = cls(targetdir, pkg_image=pkgimg) ai_img._install_package(fmri_or_p5i) ai_img.verify() ai_img._prep_ai_webserver() return ai_img @property def pkg_image(self): if self._pkgimg is None: tracker = pkg.client.progress.CommandLineProgressTracker() # installadm is non-interactive, so we don't need to track # the "cancel_state" like, for example, packagemanager cancel_state_callable = None self._pkgimg = pkg.client.api.ImageInterface( self.path, PKG5_API_VERSION, tracker, cancel_state_callable, self._PKG_CLIENT_NAME) return self._pkgimg def is_pkg_based(self): '''Returns True if image is pkg(5) based, False otherwise''' try: self.pkg_image except pkg.client.api_errors.ImageNotFoundException: return False return True def _install_package(self, fmri_or_p5i): try: p5i_data = self.pkg_image.parse_p5i(location=fmri_or_p5i) # Returns a list of tuples; should only be one publisher with # one package if len(p5i_data) != 1: raise ImageError(_("\nError:\tMore than one publisher " "in p5i file.\n")) pub, pkgs = p5i_data[0] if len(pkgs) != 1: raise ImageError(_("\nError:\tMore than one package " "in p5i file.\n")) if pub and self.pkg_image.has_publisher(prefix=pub.prefix): img_pub = self.pkg_image.get_publisher(prefix=pub.prefix, duplicate=True) for origin in pub.repository.origins: if not img_pub.repository.has_origin(origin): img_pub.repository.add_origin(origin) for mirror in pub.repository.mirrors: if not img_pub.repository.has_mirror(mirror): img_pub.repository.add_mirror(mirror) self.pkg_image.update_publisher(img_pub) elif pub: self.pkg_image.add_publisher(pub) except (pkg.client.api_errors.InvalidP5IFile, pkg.client.api_errors.RetrievalError): pkgs = [fmri_or_p5i] self.pkg_image.plan_install(pkgs) self.accept_licenses() self.pkg_image.prepare() self.pkg_image.execute_plan() self.pkg_image.reset() def accept_licenses(self): '''Accept licenses''' plan = self.pkg_image.describe() for pfmri, src, dest, accepted, displayed in plan.get_licenses(): self.pkg_image.set_plan_license_status(pfmri, dest.license, displayed=True if dest.must_display else None, accepted=True if dest.must_accept else None) def check_fmri(self, fmri): '''Calls pkg.client.api.ImageInterface.parse_fmri_patterns() to check if fmri is valid Input: fmri to check Returns: PkgFmri object Raises: ValueError if there is a problem with the fmri ''' for pattern, err, pfmri, matcher in \ self.pkg_image.parse_fmri_patterns(fmri): if err: if isinstance(err, pkg.version.VersionError): # For version errors, include the pattern so # that the user understands why it failed. print >> sys.stderr, \ cw(_("Illegal FMRI '%(patt)s': %(error)s" % {'patt': pattern, 'error': err})) raise ValueError(err) else: # Including the pattern is redundant for other # exceptions. raise ValueError(err) return pfmri def check_update(self, fmri=None, publisher=None): '''Checks to see if any updates are available for this image. If so, self.pkg_image will be left "ready" to complete the update. Input: fmri - pkg to which to potentially update publisher - tuple (prefix, origin) to use for update. If that publisher already exists in the image, its origins/mirrors are reset to the passed in origin. Otherwise, the new publisher is added. All other publishers in the image are removed. Returns: True if update available; False if not ''' logging.debug('check_update fmri=%s, publisher=%s', fmri, publisher) logging.debug("currently installed pfmri is: %s", self.get_installed_pfmri()) if fmri is not None: # validate fmri specified by user pkgfmri = self.check_fmri(fmri) fmri = [str(pkgfmri)] if publisher is not None: new_repo = pkg.client.publisher.Repository(origins=[publisher[1]]) new_repo_uri = new_repo.origins[0].uri new_pub = pkg.client.publisher.Publisher(publisher[0], repository=new_repo) if fmri: # ensure that user didn't specify conflicting publisher names if pkgfmri.publisher and pkgfmri.publisher != new_pub.prefix: raise ValueError(cw( _('\nError: FMRI publisher, "%(pub1)s", does not ' 'match specified --publisher, "%(pub2)s".\n' % {'pub1': pkgfmri.publisher, 'pub2': new_pub.prefix}))) # Replace existing publisher(s) with that specified by user same_pub = None if self.pkg_image.has_publisher(new_pub.prefix): # Specified publisher already exists same_pub = self.pkg_image.get_publisher(new_pub.prefix, duplicate=True) logging.debug('basesvc has same pub %s', same_pub.prefix) logging.debug('origins are:\n%s', '\n'.join(orig.uri for orig in same_pub.repository.origins)) logging.debug('replacing origins with new uri, %s', new_repo_uri) same_pub.repository.reset_origins() same_pub.repository.reset_mirrors() same_pub.repository.add_origin(new_repo_uri) self.pkg_image.update_publisher(same_pub, search_first=True) else: # create a new publisher logging.debug('adding pub %s, origin %s', new_pub.prefix, new_repo_uri) self.pkg_image.add_publisher(new_pub, search_first=True) # Remove any other publishers for pub in self.pkg_image.get_publishers(duplicate=True)[1:]: logging.debug('removing pub %s', pub.prefix) self.pkg_image.remove_publisher(prefix=pub.prefix) for plan_desc in self.pkg_image.gen_plan_update(pkgs_update=fmri): continue return (not self.pkg_image.planned_nothingtodo()) def update(self, fmri=None, publisher=None): '''Check to see if update is needed for image and, if so, do the update. Input: fmri - pkg to which to potentially update publisher - tuple (prefix, origin) to use for update. This replaces any publishers in the image. Returns: True if update was needed; False if not ''' logging.debug('in update, fmri=%s, publisher=%s', fmri, publisher) update_needed = self.check_update(fmri=fmri, publisher=publisher) logging.debug('update_needed=%s', update_needed) if update_needed: self.accept_licenses() self.pkg_image.prepare() self.pkg_image.execute_plan() self.pkg_image.reset() return update_needed def get_basename(self): '''Get pkg service basename ''' basename = "solarisx" try: pkg_list = self.pkg_image.get_pkg_list( pkg.client.api.ImageInterface.LIST_INSTALLED, raise_unmatched=True, return_fmris=True) for pfmri, summ, cats, states, attrs in pkg_list: manifest = self.pkg_image.get_manifest(pfmri) for action in manifest.gen_actions_by_type("set"): for attrval in action.attrlist("name"): if (attrval == self.SVC_NAME_ATTR and action.attrs.get("variant.arch", self.arch) == self.arch): basename = action.attrs["value"].strip() except pkg.client.api_errors.ApiException: pass logging.debug("get_basename returning %s", basename) return basename def get_installed_pfmri(self): '''Get installed pkg fmri''' try: pkg_list = self.pkg_image.get_pkg_list( pkg.client.api.ImageInterface.LIST_INSTALLED, raise_unmatched=True, return_fmris=True) try: pfmri = pkg_list.next()[0] except StopIteration: raise ImageError(_("\nError:\tUnable to obtain pkg name from " "image.\n")) except pkg.client.api_errors.ApiException: raise return pfmri
parser.print_help() sys.exit() else: # make sure we have the installadm smf service try: aismf.get_smf_instance() except aismf.ServicesError as err: raise SystemExit(err) # Invoke the function which implements the specified subcommand func = sub_cmds[sub_cmd][0] logging.debug("Invoking subcommand: %s %s", func.func_name, sys.argv[index + 1:]) try: return func(sys.argv[index + 1:]) except VersionError as err: print >> sys.stderr, err return 4 except Exception: sys.stderr.write( _("%s:\n" "\tUnhandled error encountered:\n") % sub_cmd) traceback.print_exc(file=sys.stderr) return 1 if __name__ == '__main__': sys.exit(main())
def main(): ''' installadm main Parse the command line arguments and invoke sub-command. Returns: The return from the invoked sub-command. 4 if VersionError encountered ''' # sub_cmds is a dictionary. The value for each subcommand key # is a tuple consisting of the method to call to invoke the # subcommand and the method to call to get usage for the subcommand. sub_cmds = { 'create-service': (create_service.do_create_service, create_service.get_usage()), 'delete-service': (delete_service.do_delete_service, delete_service.get_usage()), 'rename-service': (rename_service.do_rename_service, rename_service.get_usage()), 'set-service': (set_service.do_set_service, set_service.get_usage()), 'list': (ai_list.do_list, ai_list.get_usage()), 'enable': (do_enable_service, get_enable_usage()), 'disable': (do_disable_service, get_disable_usage()), 'create-client': (create_client.do_create_client, create_client.get_usage()), 'create-profile': (create_profile.do_create_profile, create_profile.get_create_usage()), 'update-profile': (create_profile.do_update_profile, create_profile.get_update_usage()), 'delete-client': (delete_client.do_delete_client, delete_client.get_usage()), 'create-manifest': (publish_manifest.do_publish_manifest, publish_manifest.get_create_usage()), 'add-manifest': ( publish_manifest.do_publish_manifest, # alias publish_manifest.get_create_usage()), 'update-manifest': (publish_manifest.do_update_manifest, publish_manifest.get_update_usage()), 'delete-manifest': (delete_manifest.do_delete_manifest, delete_manifest.get_usage()), 'delete-profile': (delete_profile.do_delete_profile, delete_profile.get_usage()), 'export': (export.do_export, export.get_usage()), 'remove': ( delete_manifest.do_delete_manifest, # alias delete_manifest.get_usage()), 'set-criteria': (set_criteria.do_set_criteria, set_criteria.get_usage()), 'validate': (validate_profile.do_validate_profile, validate_profile.get_usage()), 'help': (None, get_help_usage()) } # cmds is a list of subcommands used to dictate the order of # the commands listed in the usage output cmds = [ "create-service", "delete-service", "rename-service", "set-service", "list", "enable", "disable", "create-client", "delete-client", "create-manifest", "update-manifest", "delete-manifest", "create-profile", "update-profile", "delete-profile", "export", "validate", "set-criteria", "help", ] usage_str = "Usage: installadm [options] <subcommand> <args> ..." for entry in cmds: usage_str += '\n' + sub_cmds[entry][1] parser = OptionParser(usage=usage_str) # add private debug option, which provides console output that might # be useful during development or bug fixing. parser.add_option("-d", "--debug", action="count", dest="debug", default=0, help=SUPPRESS_HELP) # Find subcommand in sys.argv and save index to know which # options/args to pass to installadm and which options to # pass to subcommand sub_cmd = None index = 0 for index, arg in enumerate(sys.argv): if arg in sub_cmds: sub_cmd = arg break # Exit if no subcommand was provided. if not sub_cmd: parser.print_help(file=sys.stderr) sys.exit(2) # Pass arguments up to subcommand to installadm parser # The rest of the arguments will be passed to the # subcommand later. # (options, args) = parser.parse_args(sys.argv[1:index]) if args: parser.error(_("Unexpected argument(s): %s" % args)) # Set up logging to the specified level of detail if options.debug == 0: options.log_level = DEFAULT_LOG_LEVEL elif options.debug == 1: options.log_level = DEBUG_LOG_LEVEL elif options.debug >= 2: options.log_level = XDEBUG try: setup_logging(options.log_level) except IOError, err: parser.error("%s '%s'" % (err.strerror, err.filename))
def get_disable_usage(): ''' get usage for disable''' usage = _('disable\t<svcname>') return (usage)
def parse_options(): '''Parses and validate options Args None Globals None Returns a dictionary of the valid options { 'verbose':Bool, 'interface':interface_name (i.e., iwh0), 'comment':string (comment for the server), 'timeout':time (length of time to wait per request), 'service':SName (service name to find), 'browse':Bool (browse mode), 'register':SName (service name to register), 'port':port (port number for the service), } Raises None ''' desc = _("Multicast DNS (mDNS) & DNS Service Directory Automated " "Installations utility. " "Or with -f option, Find a service. " "Or with -b option, Browse the services. " "Or with -r option, Register a service. " "Or with -i option, Browse, Find, Register on a " "specific interface. " "Or with -c option, Comment for a mDNS record(s) being " "registered. " "Or with -t option, set the timeout for the operation. " "Or with -p option, set the port number for the registration. " "Or with -v option, Verbose output.") usage = _("usage: %prog [[-v][-i <interface>][-t <timeout>]]\n" "\t[-f <servicename>] |\n" "\t[-b] |\n" "\t[-r <servicename> -p <port>] [[-c comment]] |\n") parser = OptionParser(usage=usage, description=desc) parser.add_option('-v', '--verbose', dest='verbose', default=False, action='store_true', help=_('turn on verbose mode')) parser.add_option('-i', '--interface', dest='interface', default=None, type='string', help=_('interface to browse, find or register on')) parser.add_option('-c', '--comment', dest='comment', default=None, type='string', help=_('comment used in service registration')) parser.add_option('-t', '--timeout', dest='timeout', default=None, type='int', help=_('set the timeout for the operation')) parser.add_option('-p', '--port', dest='port', default=None, type='int', help=_('set the port for the ad hoc registration')) parser.add_option("-f", "--find", dest="service", default=None, type="string", help=_("find a named service")) parser.add_option("-b", "--browse", dest="browse", default=False, action="store_true", help=_("browse the services")) parser.add_option("-r", "--register", dest="register", default=None, type="string", help=_("register a service, root privileges required")) (loptions, args) = parser.parse_args() if args: parser.error(_('unknown argument(s): %s') % args) if loptions.register is not None and os.geteuid() != 0: parser.error(_('root privileges required with the "-r" operation.')) if [ bool(loptions.browse), bool(loptions.register), bool(loptions.service) ].count(True) > 1: parser.error( _('"-f", "-b", and "-r" operations are mutually ' 'exclusive.')) if not loptions.browse and not loptions.register and not loptions.service: parser.error(_('must specify an operation of "-f", "-b", or "-r".')) if loptions.register and not loptions.port: parser.error(_('must specify a "port" for the "-r" operation.')) if not loptions.register and loptions.port: parser.error(_('"-p" option only valid for the "-r" operation.')) if not loptions.register and loptions.comment: parser.error(_('"-c" option only valid for the "-r" operation.')) return loptions
def delete_manifest_from_db(db, manifest_instance, service_name, data_loc): """ Remove manifest from DB """ instance = manifest_instance[1] # check to see that the manifest is found in the database (as entered) if manifest_instance[0] not in AIdb.getManNames(db.getQueue()): # since all manifest names have to have .xml appended try adding that if manifest_instance[0] + '.xml' in AIdb.getManNames(db.getQueue()): man_name = manifest_instance[0] + '.xml' else: raise SystemExit( _("Error:\tManifest %s not found in database!" % manifest_instance[0])) else: man_name = manifest_instance[0] service = AIService(service_name) # Do not delete if this manifest is set up as the default. if man_name == service.get_default_manifest(): raise ValueError( _("Error:\tCannot delete default manifest %s.") % man_name) # if we do not have an instance remove the entire manifest if instance is None: # remove manifest from database query = AIdb.DBrequest("DELETE FROM manifests WHERE name = '%s'" % AIdb.sanitizeSQL(man_name), commit=True) db.getQueue().put(query) query.waitAns() # run getResponse to handle and errors query.getResponse() # clean up file on file system try: os.remove(os.path.join(service.manifest_dir, man_name)) except OSError: print >> sys.stderr, _("Warning:\tUnable to find file %s for " + "removal!") % man_name # we are removing a specific instance else: # check that the instance number is within bounds for that manifest # (0..numInstances) if instance > AIdb.numInstances(man_name, db.getQueue()) or \ instance < 0: raise SystemExit( _("Error:\tManifest %s has %i instances" % (man_name, AIdb.numInstances(man_name, db.getQueue())))) # remove instance from database query = ("DELETE FROM manifests WHERE name = '%s' AND " "instance = '%i'") % (AIdb.sanitizeSQL(man_name), instance) query = AIdb.DBrequest(query, commit=True) db.getQueue().put(query) query.waitAns() # run getResponse to handle and errors query.getResponse() # We may need to reshuffle manifests to prevent gaps in instance # numbering as the DB routines expect instances to be contiguous and # increasing. We may have removed an instance with instances numbered # above thus leaving a gap. # get the number of instances with a larger instance for num in range(instance, AIdb.numInstances(man_name, db.getQueue()) + 1): # now decrement the instance number query = ("UPDATE manifests SET instance = '%i' WHERE " "name = '%s' ") % (num - 1, AIdb.sanitizeSQL(man_name)) query += "AND instance = '%i'" % num query = AIdb.DBrequest(query, commit=True) db.getQueue().put(query) query.waitAns() # run getResponse to handle and errors query.getResponse() # remove file if manifest is no longer in database if man_name not in AIdb.getManNames(db.getQueue()): try: os.remove(os.path.join(service.manifest_dir, man_name)) except OSError: print >> sys.stderr, _("Warning: Unable to find file %s for " + "removal!") % man_name
# they exist. We validate those separately later. try: crit = lxml.etree.parse(criteria_path) except lxml.etree.XMLSyntaxError, err: raise ValueError(_("Error: %s") % err.error_log.last_error) ai_sc_list = list() ai_sc_paths = (".//ai_manifest_file", ".//ai_embedded_manifest", ".//sc_manifest_file", ".//sc_embedded_manifest") for path in ai_sc_paths: elements = crit.iterfind(path) for elem in elements: if is_dtd: raise ValueError( _("Error:\tCriteria file should not contain " "AI or SC manifest tags: %s") % criteria_path) ai_sc_list.append(elem) elem.getparent().remove(elem) # Verify the remaing DOM, which should only contain criteria root, errors = (verifyXML.verifyRelaxNGManifest( schema, StringIO.StringIO(lxml.etree.tostring(crit.getroot())))) logging.debug('criteria file passed RNG validation') if errors: raise ValueError( _("Error:\tFile %s failed validation:\n" "\tline %s: %s") % (criteria_path, errors.line, errors.message)) try: verifyXML.prepValuesAndRanges(root, db, table) except ValueError, err:
def do_create_baseservice(options): ''' This method sets up the install service by: - creating the target image directory from an iso or pkg - creating the /var/ai service structure - enabling tftp service or configuring wanboot - configuring dhcp if desired ''' tempdir = None print _("\nCreating service from: %s") % options.srcimage if is_iso(options.srcimage): have_iso = True # get default service name, if needed logging.debug("Creating ISO based service") else: have_iso = False logging.debug("Creating pkg(5) based service") # If imagepath specified by user, use that. # If imagepath not specified by user: # a) if svcname specified by user, set up image in # <default image path>/<svcname> # b) if svcname not specified by user, set up image in # <tmp location> and move to <default image path>/<svcname> # once svcname is determined. # If imagepath not specified, verify that default image path is # ok with user if not options.imagepath: if options.svcname: imagepath = os.path.join(BASE_IMAGE_DIR, options.svcname) prompt = (_("OK to use default image path: %s? [y/N]: " % imagepath)) else: prompt = (_("OK to use subdir of %s to store image? [y/N]: " % BASE_IMAGE_DIR)) try: if not options.noprompt: if not com.ask_yes_or_no(prompt): raise SystemExit( _('\nPlease re-enter command with ' 'desired --imagepath\n')) except KeyboardInterrupt: raise SystemExit(1) # If we know the svcname, we know where to put the image. # Otherwise, put the image into a temp directory and move # it to correct location when we know it later if options.svcname: options.imagepath = os.path.join(BASE_IMAGE_DIR, options.svcname) try: check_imagepath(options.imagepath) except ValueError as error: raise SystemExit(error) else: try: os.makedirs(BASE_IMAGE_DIR) except OSError as err: if err.errno != errno.EEXIST: raise if not os.path.isdir(BASE_IMAGE_DIR): raise SystemExit( cw( _('\nThe default image base ' 'directory, %(dir)s, is not a directory. Check the ' 'SMF setting for property %(prop)s in servce ' '%(svc)s.') % { 'dir': BASE_IMAGE_DIR, 'prop': com.BASEDIR_PROP, 'svc': com.SRVINST })) tempdir = tempfile.mkdtemp(dir=BASE_IMAGE_DIR) options.imagepath = tempdir logging.debug('Using default image path: %s', options.imagepath) # create the image area if have_iso: try: image = InstalladmIsoImage.unpack(options.srcimage, options.imagepath) except CalledProcessError as err: raise SystemExit(err.popen.stderr) except ImageError as err: print >> sys.stderr, str(err) shutil.rmtree(options.imagepath, ignore_errors=True) raise SystemExit( cw( _('Please re-enter command and specify ' 'a valid Automated Installer ISO file'))) else: try: image = InstalladmPkgImage.image_create( options.srcimage, options.imagepath, arch=options.arch, publisher=options.publisher) except (ImageError, pkg.client.api_errors.ApiException) as err: print >> sys.stderr, cw(_("The specified data source, %s, " "for the service is not a path to an existing ISO image.") % \ options.srcimage) print >> sys.stderr, cw(_("Attempting to create the service from" " pkg(5) package, %s, failed for the following reasons:") % \ options.srcimage) if isinstance(err, pkg.client.api_errors.VersionException): print >> sys.stderr, cw( _("The IPS API version specified, " + str(err.received_version) + ", is incompatible with the expected version, " + str(err.expected_version) + ".")) elif isinstance(err, pkg.client.api_errors.CatalogRefreshException): for pub, error in err.failed: print >> sys.stderr, " " print >> sys.stderr, str(error) if err.errmessage: print >> sys.stderr, err.errmessage shutil.rmtree(options.imagepath, ignore_errors=True) raise SystemExit(err) # get default service name, if needed if not options.svcname: if tempdir and options.imagepath == tempdir: specified_path = None else: specified_path = options.imagepath options.svcname = get_default_service_name(specified_path, image=image, iso=have_iso) print _("\nCreating %(arch)s service: %(name)s\n") % \ {'arch': image.arch, 'name': options.svcname} # If image was created in temporary location, move to correct # location now that we know the svcname. if tempdir is not None: new_imagepath = os.path.join(BASE_IMAGE_DIR, options.svcname) try: check_imagepath(new_imagepath) except ValueError as error: # leave image in temp location so that service can be created logging.debug('unable to move image to %s: %s', new_imagepath, error) else: options.imagepath = image.move(new_imagepath) logging.debug('image moved to %s', options.imagepath) set_permissions(options.imagepath) print _("Image path: %s\n") % options.imagepath try: if options.dhcp_ip_start: service = AIService.create(options.svcname, image, options.dhcp_ip_start, options.dhcp_ip_count, options.dhcp_bootserver, bootargs=options.bootargs) else: service = AIService.create(options.svcname, image, bootargs=options.bootargs) except AIServiceError as err: raise SystemExit(err) # Register & enable service # (Also enables system/install/server, as needed) got_services_error = False try: service.enable() except (config.ServiceCfgError, MountError) as err: raise SystemExit(err) except aismf.ServicesError as svc_err: # Don't print the error now. It will either get printed out # upon exit or when services are enabled after creating the # alias got_services_error = True # create default-<arch> alias if this is the first aliasable # service of this architecture if should_be_default_for_arch(service): defaultarch = 'default-' + image.arch print(_("\nCreating %s alias.\n") % defaultarch) try: defaultarchsvc = AIService.create(defaultarch, image, bootargs=options.bootargs, alias=options.svcname) except AIServiceError as err: raise SystemExit(err) except UnsupportedAliasError as err: if got_services_error: # Print the services error string before printing the # unsupported alias error print svc_err, '\n' # Print the error, but have installadm exit successfully. # Since the user did not explicitly request this alias, # it's not a problem if an alias can't be made for this service print err return 0 # For sparc, create symlinks for default sparc service if image.arch == 'sparc': logging.debug("Creating default-sparc symlinks") defaultarchsvc.do_default_sparc_symlinks(defaultarch) # Register & enable default-<arch> service try: defaultarchsvc.enable() except (config.ServiceCfgError, MountError) as err: raise SystemExit(err) except aismf.ServicesError as err: print err elif got_services_error: # Print the services start error generated when creating the service print svc_err
def parse_options(cmd_options=None): '''Parse commandline options for export command''' parser = OptionParser(usage=get_usage(), prog="export") parser.add_option('-p', '--profile', dest='pnames', action="append", default=list(), help=_("Name of profile to export.")) parser.add_option('-m', '--manifest', dest='mnames', action="append", default=list(), help=_("Name of manifest to export.")) parser.add_option('-n', '--service', dest='service_name', default=None, help=_("Name of install service.")) parser.add_option('-o', '--output', dest='output_name', default=None, help=_("Name of output file.")) (options, args) = parser.parse_args(cmd_options) if args: parser.error(_("Extra args given.")) if not options.service_name: parser.error(_("Service name is required.")) if not config.is_service(options.service_name): raise SystemExit(_("No such service: %s") % options.service_name) service = AIService(options.service_name) options.service = service if not len(options.mnames) and not len(options.pnames): parser.error(_("A manifest or profile name is required.")) options.file_count = len(options.mnames) + len(options.pnames) if not options.output_name: options.output_name = SCREEN options.output_isdir = None else: # Non-stdout -o processing: # if output_name is an existing directory: write all files out to it. # if output_name is an existing file and output one file: # overwrite the existing file. # if file exists with output_name and mult output files desired: # error # if file or dir doesn't exist w/output name and mult files desired: # create new directory with output name and write files there. # if file or dir doesn't exist with output name and one file desired: # write the one file to that output name options.output_isdir = False if os.path.isdir(options.output_name): options.output_isdir = True elif os.path.exists(options.output_name): if (options.file_count > 1): parser.error( _("-o must specify a directory when multiple " "files are requested.")) else: if (options.file_count > 1): os.mkdir(options.output_name) options.output_isdir = True return options
def list_manifests(service): '''Replies to the client with criteria list for a service. The output should be similar to installadm list. Args service - the name of the service being listed Returns None Raises None ''' print 'Content-Type: text/html' # HTML is following print # blank line, end of headers print '<html>' print '<head>' sys.stdout.write('<title>%s %s</title>' % (_('Manifest list for'), service)) print '</head><body>' port = 0 try: smf.AISCF(FMRI="system/install/server") except KeyError: # report the internal error to error_log and requesting client sys.stderr.write( _("error:The system does not have the " "system/install/server SMF service.")) sys.stdout.write( _("error:The system does not have the " "system/install/server SMF service.")) return services = config.get_all_service_names() if not services: # report the error to the requesting client only sys.stdout.write(_('error:no services on this server.\n')) return found = False if config.is_service(service): service_ctrl = AIService(service) found = True # assume new service setup path = service_ctrl.database_path if os.path.exists(path): try: aisql = AIdb.DB(path) aisql.verifyDBStructure() except StandardError as err: # report the internal error to error_log and # requesting client sys.stderr.write( _('error:AI database access ' 'error\n%s\n') % err) sys.stdout.write( _('error:AI database access ' 'error\n%s\n') % err) return # generate the list of criteria for the criteria table header criteria_header = E.TR() for crit in AIdb.getCriteria(aisql.getQueue(), strip=False): criteria_header.append(E.TH(crit)) # generate the manifest rows for the criteria table body names = AIdb.getManNames(aisql.getQueue()) table_body = E.TR() allcrit = AIdb.getCriteria(aisql.getQueue(), strip=False) colspan = str(max(len(list(allcrit)), 1)) for manifest in names: # iterate through each manifest (and instance) for instance in range( 0, AIdb.numInstances(manifest, aisql.getQueue())): table_body.append(E.TR()) # print the manifest name only once (from instance 0) if instance == 0: href = '../' + service + '/' + manifest row = str(AIdb.numInstances(manifest, aisql.getQueue())) table_body.append( E.TD(E.A(manifest, href=href, rowspan=row))) else: table_body.append(E.TD()) crit_pairs = AIdb.getManifestCriteria(manifest, instance, aisql.getQueue(), onlyUsed=True, humanOutput=True) # crit_pairs is an SQLite3 row object which doesn't # support iteritems(), etc. for crit in crit_pairs.keys(): formatted_val = AIdb.formatValue( crit, crit_pairs[crit]) # if we do not get back a valid value ensure a # hyphen is printed (prevents "" from printing) if formatted_val and crit_pairs[crit]: table_body.append( E.TD(formatted_val, align="center")) else: table_body.append( E.TD(lxml.etree.Entity("nbsp"), align="center")) # print the default manifest at the end of the table, # which has the same colspan as the Criteria List label else: href = '../' + service + '/default.xml' table_body.append( E.TR( E.TD(E.A("Default", href=href)), E.TD(lxml.etree.Entity("nbsp"), colspan=colspan, align="center"))) web_page = E.HTML( E.HEAD( E.TITLE( _("Syneto StorageOS Automated " "Installation Webserver"))), E.BODY( E.H1( _("Welcome to the Syneto StorageOS " "Automated Installation webserver!")), E.P( _("Service '%s' has the following " "manifests available, served to clients " "matching required criteria.") % service), E.TABLE(E.TR(E.TH(_("Manifest"), rowspan="2"), E.TH(_("Criteria List"), colspan=colspan)), criteria_header, table_body, border="1", align="center"), )) print lxml.etree.tostring(web_page, pretty_print=True) # service is not found, provide available services on host if not found: sys.stdout.write(_('Service <i>%s</i> not found. ') % service) sys.stdout.write(_('Available services are:<p><ol><i>')) host = socket.gethostname() for service_name in config.get_all_service_names(): # assume new service setup port = config.get_service_port(service_name) sys.stdout.write( '<a href="http://%s:%d/cgi-bin/' 'cgi_get_manifest.py?version=%s&service=%s">%s</a><br>\n' % (host, port, VERSION, service_name, service_name)) sys.stdout.write('</i></ol>%s' % _('Please select a service ' 'from the above list.')) print '</body></html>'
def send_manifest(form_data, port=0, servicename=None, protocolversion=COMPATIBILITY_VERSION, no_default=False): '''Replies to the client with matching service for a service. Args form_data - the postData passed in from the client request port - the port of the old client servicename - the name of the service being used protocolversion - the version of the AI service RE: handshake no_default - boolean flag to signify whether or not we should hand back the default manifest and profiles if one cannot be matched based on the client criteria. Returns None Raises None ''' # figure out the appropriate path for the AI database, # and get service name if necessary. # currently service information is stored in a port directory. # When the cherrypy webserver new service directories should be # separated via service-name only. Old services will still use # port numbers as the separation mechanism. path = None found_servicename = None service = None port = str(port) if servicename: service = AIService(servicename) path = service.database_path else: for name in config.get_all_service_names(): if config.get_service_port(name) == port: found_servicename = name service = AIService(name) path = service.database_path break # Check to insure that a valid path was found if not path or not os.path.exists(path): print 'Content-Type: text/html' # HTML is following print # blank line, end of headers if servicename: print '<pre><b>Error</b>:unable to find<i>', servicename + '</i>.' else: print '<pre><b>Error</b>:unable to find<i>', port + '</i>.' print 'Available services are:<p><ol><i>' hostname = socket.gethostname() for name in config.get_all_service_names(): port = config.get_service_port(name) sys.stdout.write( '<a href="http://%s:%d/cgi-bin/' 'cgi_get_manifest.py?version=%s&service=%s">%s</a><br>\n' % (hostname, port, VERSION, name, name)) print '</i></ol>Please select a service from the above list.' return if found_servicename: servicename = found_servicename # load to the AI database aisql = AIdb.DB(path) aisql.verifyDBStructure() # convert the form data into a criteria dictionary criteria = dict() orig_data = form_data while form_data: try: [key_value, form_data] = form_data.split(';', 1) except (ValueError, NameError, TypeError, KeyError): key_value = form_data form_data = '' try: [key, value] = key_value.split('=') criteria[key] = value except (ValueError, NameError, TypeError, KeyError): criteria = dict() # Generate templating dictionary from criteria template_dict = dict() for crit in criteria: template_dict["AI_" + crit.upper()] = \ AIdb.formatValue(crit, criteria[crit], units=False) # find the appropriate manifest try: manifest = AIdb.findManifest(criteria, aisql) except StandardError as err: print 'Content-Type: text/html' # HTML is following print # blank line, end of headers print '<pre><b>Error</b>:findManifest criteria<br>' print err, '<br>' print '<ol>servicename =', servicename print 'port =', port print 'path =', path print 'form_data =', orig_data print 'criteria =', criteria print 'servicename found by port =', found_servicename, '</ol>' print '</pre>' return # check if findManifest() returned a number equal to 0 # (means we got no manifests back -- thus we serve the default if desired) if manifest is None and not no_default: manifest = service.get_default_manifest() # if we have a manifest to return, prepare its return if manifest is not None: try: # construct the fully qualified filename filename = os.path.abspath( os.path.join(service.manifest_dir, manifest)) # open and read the manifest with open(filename, 'rb') as mfp: manifest_str = mfp.read() # maintain compability with older AI client if servicename is None or \ float(protocolversion) < float(PROFILES_VERSION): content_type = mimetypes.types_map.get('.xml', 'text/plain') print 'Content-Length:', len( manifest_str) # Length of the file print 'Content-Type:', content_type # XML is following print # blank line, end of headers print manifest_str logging.info('Manifest sent from %s.' % filename) return except OSError as err: print 'Content-Type: text/html' # HTML is following print # blank line, end of headers print '<pre>' # report the internal error to error_log and requesting client sys.stderr.write(_('error:manifest (%s) %s\n') % \ (str(manifest), err)) sys.stdout.write(_('error:manifest (%s) %s\n') % \ (str(manifest), err)) print '</pre>' return # get AI service image path service = AIService(servicename) image_dir = service.image.path # construct object to contain MIME multipart message outermime = MIMEMultipart() client_msg = list() # accumulate message output for AI client # If we have a manifest, attach it to the return message if manifest is not None: # add manifest as attachment msg = MIMEText(manifest_str, 'xml') # indicate manifest using special name msg.add_header('Content-Disposition', 'attachment', filename=sc.AI_MANIFEST_ATTACHMENT_NAME) outermime.attach(msg) # add manifest as an attachment # search for any profiles matching client criteria # formulate database query to profiles table q_str = "SELECT DISTINCT name, file FROM " + \ AIdb.PROFILES_TABLE + " WHERE " nvpairs = list() # accumulate criteria values from post-data # for all AI client criteria for crit in AIdb.getCriteria(aisql.getQueue(), table=AIdb.PROFILES_TABLE, onlyUsed=False): if crit not in criteria: msgtxt = _("Warning: client criteria \"%s\" not provided in " "request. Setting value to NULL for profile lookup.") \ % crit client_msg += [msgtxt] logging.warn(msgtxt) # fetch only global profiles destined for all clients if AIdb.isRangeCriteria(aisql.getQueue(), crit, AIdb.PROFILES_TABLE): nvpairs += ["MIN" + crit + " IS NULL"] nvpairs += ["MAX" + crit + " IS NULL"] else: nvpairs += [crit + " IS NULL"] continue # prepare criteria value to add to query envval = AIdb.sanitizeSQL(criteria[crit]) if AIdb.isRangeCriteria(aisql.getQueue(), crit, AIdb.PROFILES_TABLE): # If no default profiles are requested, then we mustn't allow # this criteria to be NULL. It must match the client's given # value for this criteria. if no_default: if crit == "mac": nvpairs += ["(HEX(MIN" + crit + ")<=HEX(X'" + envval + \ "'))"] nvpairs += ["(HEX(MAX" + crit + ")>=HEX(X'" + envval + \ "'))"] else: nvpairs += ["(MIN" + crit + "<='" + envval + "')"] nvpairs += ["(MAX" + crit + ">='" + envval + "')"] else: if crit == "mac": nvpairs += [ "(MIN" + crit + " IS NULL OR " "HEX(MIN" + crit + ")<=HEX(X'" + envval + "'))" ] nvpairs += [ "(MAX" + crit + " IS NULL OR HEX(MAX" + crit + ")>=HEX(X'" + envval + "'))" ] else: nvpairs += [ "(MIN" + crit + " IS NULL OR MIN" + crit + "<='" + envval + "')" ] nvpairs += [ "(MAX" + crit + " IS NULL OR MAX" + crit + ">='" + envval + "')" ] else: # If no default profiles are requested, then we mustn't allow # this criteria to be NULL. It must match the client's given # value for this criteria. # # Also, since this is a non-range criteria, the value stored # in the DB may be a whitespace separated list of single # values. We use a special user-defined function in the # determine if the given criteria is in that textual list. if no_default: nvpairs += ["(is_in_list('" + crit + "', '" + envval + \ "', " + crit + ", 'None') == 1)"] else: nvpairs += ["(" + crit + " IS NULL OR is_in_list('" + crit + \ "', '" + envval + "', " + crit + ", 'None') == 1)"] if len(nvpairs) > 0: q_str += " AND ".join(nvpairs) # issue database query logging.info("Profile query: " + q_str) query = AIdb.DBrequest(q_str) aisql.getQueue().put(query) query.waitAns() if query.getResponse() is None or len(query.getResponse()) == 0: msgtxt = _("No profiles found.") client_msg += [msgtxt] logging.info(msgtxt) else: for row in query.getResponse(): profpath = row['file'] profname = row['name'] if profname is None: # should not happen profname = 'unnamed' try: if profpath is None: msgtxt = "Database record error - profile path is " \ "empty." client_msg += [msgtxt] logging.error(msgtxt) continue msgtxt = _('Processing profile %s') % profname client_msg += [msgtxt] logging.info(msgtxt) with open(profpath, 'r') as pfp: raw_profile = pfp.read() # do any template variable replacement {{AI_xxx}} tmpl_profile = sc.perform_templating( raw_profile, template_dict) # precautionary validation of profile, logging only sc.validate_profile_string(tmpl_profile, image_dir, dtd_validation=True, warn_if_dtd_missing=True) except IOError as err: msgtxt = _("Error: I/O error: ") + str(err) client_msg += [msgtxt] logging.error(msgtxt) continue except OSError: msgtxt = _("Error: OS error on profile ") + profpath client_msg += [msgtxt] logging.error(msgtxt) continue except KeyError: msgtxt = _('Error: could not find criteria to substitute ' 'in template: ') + profpath client_msg += [msgtxt] logging.error(msgtxt) logging.error('Profile with template substitution error:' + raw_profile) continue except lxml.etree.XMLSyntaxError as err: # log validation error and proceed msgtxt = _( 'Warning: syntax error found in profile: ') \ + profpath client_msg += [msgtxt] logging.error(msgtxt) for error in err.error_log: msgtxt = _('Error: ') + error.message client_msg += [msgtxt] logging.error(msgtxt) logging.info([ _('Profile failing validation: ') + lxml.etree.tostring(root) ]) # build MIME message and attach to outer MIME message msg = MIMEText(tmpl_profile, 'xml') # indicate in header that this is an attachment msg.add_header('Content-Disposition', 'attachment', filename=profname) # attach this profile to the manifest and any other profiles outermime.attach(msg) msgtxt = _('Parsed and loaded profile: ') + profname client_msg += [msgtxt] logging.info(msgtxt) # any profiles and AI manifest have been attached to MIME message # specially format list of messages for display on AI client console if client_msg: outtxt = '' for msgtxt in client_msg: msgtxt = _('SC profile locator:') + msgtxt outtxt += str(msgtxt) + '\n' # add AI client console messages as single plain text attachment msg = MIMEText(outtxt, 'plain') # create MIME message outermime.attach(msg) # attach MIME message to response print outermime.as_string() # send MIME-formatted message
if __name__ == '__main__': gettext.install("ai", "/usr/lib/locale") DEFAULT_PORT = libaimdns.getinteger_property(com.SRVINST, com.PORTPROP) (PARAM_VERSION, SERVICE, NO_DEFAULT, FORM_DATA) = \ get_parameters(cgi.FieldStorage()) print >> sys.stderr, PARAM_VERSION, SERVICE, NO_DEFAULT, FORM_DATA if PARAM_VERSION == COMPATIBILITY_VERSION or SERVICE is None: # Old client (REQUEST_METHOD, REQUEST_PORT) = get_environment_information() if REQUEST_PORT == DEFAULT_PORT: # only new clients use default port HOST = socket.gethostname() print 'Content-Type: text/html' # HTML is following print # blank line, end of headers print '<pre>' sys.stdout.write(_('error:must supply a service name\n')) sys.stdout.write(_('The request should look like:\n')) sys.stdout.write('<ol>http://%s:%d/cgi_get_manifest.py?' 'version=%s&service=<i>servicename</i></ol>' % (HOST, DEFAULT_PORT, VERSION)) print '</pre>' sys.exit(0) if REQUEST_METHOD == 'GET': send_needed_criteria(REQUEST_PORT) else: send_manifest(FORM_DATA, port=REQUEST_PORT) elif FORM_DATA is None: # do manifest table list list_manifests(SERVICE) else: # do manifest criteria match
def get_usage(): ''' get usage for delete-service''' return(_('delete-service [-r|--autoremove] [-y|--noprompt] <svcname>]'))
def do_update_profile(cmd_options=None): ''' Updates exisiting profile Arg: cmd_options - command line options Effect: update existing profile Raises SystemExit if condition cannot be handled ''' options = parse_options(DO_UPDATE, cmd_options) # verify the file profile_file = options.profile_file[0] if not os.path.exists(profile_file): raise SystemExit(_("Error:\tFile does not exist: %s\n") % profile_file) # get profile name if not options.profile_name: profile_name = os.path.basename(profile_file) else: profile_name = options.profile_name # get AI service image path and database name service = AIService(options.service_name) dbname = service.database_path image_dir = service.image.path # open database dbn = AIdb.DB(dbname, commit=True) dbn.verifyDBStructure() queue = dbn.getQueue() # Handle old DB versions which did not store a profile. if not AIdb.tableExists(queue, AIdb.PROFILES_TABLE): raise SystemExit( _("Error:\tService %s does not support profiles") % options.service_name) # check for the existence of profile missing_profile_error = _("Error:\tService {service} has no profile " "named {profile}.") if not sc.is_name_in_table(profile_name, queue, AIdb.PROFILES_TABLE): raise SystemExit( missing_profile_error.format(service=options.service_name, profile=profile_name)) # validates the profile and report the errors if found raw_profile = df.validate_file(profile_name, profile_file, image_dir, verbose=False) if not raw_profile: raise SystemExit(1) # create file from string and report failures tmp_profile_path = copy_profile_internally(raw_profile) if not tmp_profile_path: raise SystemExit(1) # get the path of profile in db q_str = "SELECT file FROM " + AIdb.PROFILES_TABLE + " WHERE name=" \ + AIdb.format_value('name', profile_name) query = AIdb.DBrequest(q_str) queue.put(query) query.waitAns() response = query.getResponse() # database error if response is None: raise SystemExit( missing_profile_error.format(service=options.service_name, profile=profile_name)) db_profile_path = response[0][0] # replace the file try: shutil.copyfile(tmp_profile_path, db_profile_path) except IOError as err: raise SystemExit( _("Error:\t writing profile %s: %s") % (profile_name, err)) finally: os.unlink(tmp_profile_path) print >> sys.stderr, _("Profile updated successfully.")
def get_usage(): ''' get usage for rename-service''' return(_('rename-service\t<svcname> <newsvcname>'))
def verify_AI_manifest(self): """ Used for verifying and loading AI manifest as defined by DataFiles._AIschema. Args: None. Preconditions: Expects its is_dtd variable to be set to determine how to validate the AI manifest. Postconditions: Sets AI_root on success to a XML DOM of the AI manifest. Raises: IOError on file open error. ValueError on validation error. """ schema = file(self.AI_schema, 'r') try: xml_data = file(self.manifest_path, 'r') except AssertionError: # manifest path will be unset if we're not using a separate file # for A/I manifest so we must emulate a file xml_data = StringIO.StringIO(lxml.etree.tostring(self.AI_root)) if self.is_dtd: self.AI_root, errors = verifyXML.verifyDTDManifest( self.AI_schema, xml_data) if errors: err = '\n'.join(errors) raise ValueError( _("Error: AI manifest failed validation:\n%s") % err) ai_instance = self.AI_root.find(".//ai_instance") else: self.AI_root, errors = verifyXML.verifyRelaxNGManifest( schema, xml_data) if errors: # catch if we are not using a manifest we can name with # manifest_path try: # manifest_path is a property that may raise an # AssertionError man_path = self.manifest_path raise ValueError( _("Error:\tFile %s failed validation:" "\n\t%s") % (os.path.basename(man_path), errors.message)) # manifest_path will throw an AssertionError if it does not # have a path use a different error message except AssertionError: raise ValueError( _("Error: AI manifest failed validation:" "\n\t%s") % errors.message) # Replace the <ai_manifest_file> element (if one exists) with an # <ai_embedded_manifest> element, using content from its referenced # file which was just loaded into the AI_root XML DOM ai_manifest_file = self.criteria_root.find(".//ai_manifest_file") if ai_manifest_file is not None: new_ai = lxml.etree.Element("ai_embedded_manifest") # add newlines to separate ai_embedded_manifest # from children new_ai.text = "\n\t" new_ai.tail = "\n" self.AI_root.getroot().tail = "\n" new_ai.append(self.AI_root.getroot()) ai_manifest_file.getparent().replace(ai_manifest_file, new_ai) ai_instance = self.criteria_root.find(".//ai_manifest") # Set/update the name inside the DOM ai_instance.set("name", self.manifest_name)
def get_usage(): ''' get usage for delete-manifest''' return (_('delete-manifest\t-m|--manifest <manifest/script name> \n' '\t\t-n|--service <svcname>'))
def verifyCriteriaDict(schema, criteria_dict, db, table=AIdb.MANIFESTS_TABLE): """ Used for verifying and loading criteria from a dictionary of criteria. Args: schema - path to schema file for criteria manifest. criteria_dict - dictionary of criteria to verify, in the form of { criteria: value, criteria: value, ... } db - database object for install service table - database table, distinguishing manifests from profiles Raises IOError: * if the schema does not open ValueError: * if the criteria_dict dictionary is empty * if the XML is invalid according to the schema AssertionError: * if a value in the dictionary is empty Returns: A valid XML DOM of the criteria and all MAC and IPV4 values are formatted according to verifyXML.prepValuesAndRanges(). """ schema = open(schema, 'r') if not criteria_dict: raise ValueError("Error:\tCriteria dictionary empty: %s\n" % criteria_dict) root = lxml.etree.Element("ai_criteria_manifest") for name, value_or_range in criteria_dict.iteritems(): if value_or_range is None: raise AssertionError( _("Error: Missing value for criteria " "'%s'") % name) crit = lxml.etree.SubElement(root, "ai_criteria") crit.set("name", name) # If criteria is a range, split on "-" and add to # XML DOM as a range element. if AIdb.isRangeCriteria(db.getQueue(), name, table): # Split on "-" range_value = value_or_range.split('-', 1) # If there was only a single value, means user specified # this range criteria as a single value, add it as a single # value if len(range_value) == 1: value_elem = lxml.etree.SubElement(crit, "value") value_elem.text = value_or_range else: range_elem = lxml.etree.SubElement(crit, "range") range_elem.text = " ".join(range_value) else: value_elem = lxml.etree.SubElement(crit, "value") value_elem.text = value_or_range # Verify the generated criteria DOM root, errors = verifyXML.verifyRelaxNGManifest( schema, StringIO.StringIO(lxml.etree.tostring(root))) if errors: raise ValueError( _("Error: Criteria failed validation:\n\t%s") % errors.message) try: verifyXML.prepValuesAndRanges(root, db, table) except ValueError, err: raise ValueError(_("Error:\tCriteria error: %s") % err)
def get_help_usage(): ''' get usage for help''' usage = _('help\t[<subcommand>]') return (usage)
def parse_options(cmd_options=None): ''' Parse and validate options Returns: An options record containing arch aliasof bootargs dhcp_ip_count dhcp_ip_start dhcp_bootserver noprompt publisher srcimage svcname imagepath ''' logging.log(com.XDEBUG, '**** START installadm.create_service.' 'parse_options ****\n') usage = '\n' + get_usage() description = _('Establishes an Automated Install network service.') parser = OptionParser(usage=usage, prog="create-service", description=description) parser.add_option('-b', '--boot-args', dest='bootargs', action='append', default=list(), help=_('Comma separated list of <property>=<value>' ' pairs to add to the x86 Grub menu entry')) parser.add_option('-a', '--arch', dest='arch', default=None, choices=("i386", "sparc"), help=_("ARCHITECTURE (sparc or i386), desired " "architecture of resulting service when creating " "from a pkg.")) parser.add_option('-d', '--imagepath', dest='imagepath', default=None, help=_("Path at which to create the net image")) parser.add_option('-t', '--aliasof', dest='aliasof', default=None, help=_("Service being created is alias of this serivce")) parser.add_option('-n', '--service', dest='svcname', help=_('service name')) parser.add_option('-i', '--ip-start', dest='dhcp_ip_start', type='string', help=_('DHCP Starting IP Address'), action="callback", callback=check_ip_address) parser.add_option('-c', '--ip-count', dest='dhcp_ip_count', type='int', help=_('DHCP Count of IP Addresses')) parser.add_option('-B', '--bootfile-server', dest='dhcp_bootserver', type='string', help=_('DHCP Boot Server Address'), action="callback", callback=check_ip_address) parser.add_option('-s', '--source', dest='srcimage', type='string', help=_('FMRI or Auto Install ISO')) parser.add_option('-p', '--publisher', help=_( "A pkg(5) publisher, in the" " form '<prefix>=<uri>', from which to install the " "client image")) parser.add_option('-y', "--noprompt", action="store_true", dest="noprompt", default=False, help=_('Suppress confirmation prompts and proceed with ' 'service creation using default values')) options, args = parser.parse_args(cmd_options) if args: parser.error(_('Unexpected argument(s): %s') % args) # if service name provided, validate it if options.svcname: try: com.validate_service_name(options.svcname) except ValueError as err: parser.error(err) # Give error if service already exists if config.is_service(options.svcname): parser.error(_('\nService already exists: %s\n') % options.svcname) # If creating an alias, only allow additional options -n, -b, # and -y if options.aliasof: if (options.dhcp_ip_start or options.dhcp_ip_count or options.imagepath or options.srcimage): parser.error( _('\nOnly options -n|--service, -b|--boot-args, ' 'and -y|--noprompt\nmay be specified with ' '-t|--aliasof.')) if not options.svcname: parser.error( _('\nOption -n|--service is required with the ' '-t|--aliasof option')) else: name = options.svcname if name in DEFAULT_ARCH: raise SystemExit( _('\nDefault services must be created as ' 'aliases. Use -t|--aliasof.\n')) # provide default for srcimage, now that we're done option checking if options.srcimage is None: options.srcimage = "pkg:/install-image/solaris-auto-install" # check dhcp related options if options.dhcp_ip_start or options.dhcp_ip_count: if com.is_multihomed(): # don't allow DHCP setup if multihomed parser.error( cw( _('\nDHCP server configuration is unavailable on ' 'hosts with multiple network interfaces (-i and ' '-c options are disallowed).\n'))) # Confirm options -i and -c are both provided if options.dhcp_ip_count is None: parser.error( _('\nIf -i option is provided, -c option must ' 'also be provided\n')) if not options.dhcp_ip_start: parser.error( _('\nIf -c option is provided, -i option must ' 'also be provided\n')) # Confirm count of ip addresses is positive if options.dhcp_ip_count < 1: parser.error( _('\n"-c <count_of_ipaddr>" must be greater than ' 'zero.\n')) if options.dhcp_bootserver: # Confirm if the -B is provided, that -i/-c are also if options.dhcp_ip_count is None: parser.error( _('\nIf -B option is provided, -i option must ' 'also be provided\n')) if is_iso(options.srcimage): if options.arch is not None: parser.error( _("The --arch option is invalid for ISO-based " "services")) if options.publisher is not None: parser.error( _("The --publisher option is invalid for " "ISO-based services")) if options.publisher: # Convert options.publisher from a string of form 'prefix=uri' to a # tuple (prefix, uri) publisher = options.publisher.split("=") if len(publisher) != 2: parser.error( _('Publisher information must match the form: ' '"<prefix>=<URI>"')) options.publisher = publisher # Make sure imagepath meets requirements if options.imagepath: options.imagepath = options.imagepath.strip() if options.imagepath: if not options.imagepath == '/': options.imagepath = options.imagepath.rstrip('/') try: check_imagepath(options.imagepath) except ValueError as error: raise SystemExit(error) return options
def get_enable_usage(): ''' get usage for enable''' usage = _('enable\t<svcname>') return (usage)
class InstalladmImage(object): '''Represents an AI client image on the installadm server''' INVALID_AI_IMAGE = _("\nError:\tThe image at %(path)s is not a valid " "Automated Installer image.") def __init__(self, image_path): self._path = image_path self._arch = None self._version = None def verify(self): ''' Check that the image directory exists, appears to be a valid net boot image (has a solaris.zlib file), and is a valid Automated Installer image (has an auto_install/ai.dtd file). Raises: ImageError if path checks fail Pre-conditions: Expects self.path to return a valid image_path Returns: None ''' # check image_path exists if not os.path.isdir(self.path): raise ImageError( cw( _("\nError:\tThe image path (%s) is not " "a directory. Please provide a " "different image path.\n") % self.path)) # check that the image_path has solaris.zlib and # auto_install/ai.dtd files if not (os.path.exists(os.path.join(self.path, "solaris.zlib")) and os.path.exists( os.path.join(self.path, "auto_install/ai.dtd"))): raise ImageError(cw(self.INVALID_AI_IMAGE % {"path": self.path})) @property def version(self): '''Returns the AI client image version. See also the module docstring. ''' if self._version is None: version = self.read_image_info().get("image_version", "0.0") try: version = float(version) except (ValueError, TypeError): version = 0.0 self._version = version return self._version def read_image_info(self): '''Reads the .image_info file for this image, returning a dictionary of its contents. The keys are set to lower-case. ''' image_info = dict() with open(os.path.join(self.path, ".image_info"), "r") as info: for line in info: key, valid, value = line.strip().partition("=") if valid: image_info[key.lower()] = value return image_info def move(self, new_path): '''Move image area to new location and update webserver symlinks. To rename self._path, caller should ensure new_path does not exist. Return new image path ''' self._remove_ai_webserver_symlink() try: os.makedirs(os.path.dirname(new_path)) except OSError as err: if err.errno != errno.EEXIST: raise # Use shutil.move rather than os.rename to allow move across # filesystems. shutil.move(self._path, new_path) self._path = new_path self._prep_ai_webserver() return self._path @property def path(self): ''' Returns the image path ''' return self._path @property def arch(self): ''' Provide the image's architecture (and caches the answer) Raises: AssertionError if the image does not have a /platform [sun4u, sun4v, i86pc, amd64] Pre-conditions: Expects self.path to return a valid image path Returns: "sparc" or "i386" as appropriate ''' if self._arch is None: platform = os.path.join(self.path, "platform") for root, dirs, files in os.walk(platform): if "i86pc" in dirs or "amd64" in dirs: self._arch = "i386" elif "sun4v" in dirs or "sun4u" in dirs: self._arch = "sparc" else: raise ImageError( _("\nError:\tUnable to determine " "architecture of image.\n")) break return self._arch def _remove_ai_webserver_symlink(self): '''Remove the ai webserver symlink for this image''' dest = os.path.join(com.WEBSERVER_DOCROOT, self.path.lstrip("/")) if os.path.islink(dest) or os.path.exists(dest): os.remove(dest) # remove empty parent directories up until com.WEBSERVER_DOCROOT parent = os.path.dirname(dest) while parent != com.WEBSERVER_DOCROOT: try: os.rmdir(parent) parent = os.path.dirname(parent) except OSError: # break if directory is non-empty break def _prep_ai_webserver(self): '''Enable the AI webserver to access the image path''' target_path = os.path.dirname(self.path).lstrip("/") try: os.makedirs(os.path.join(com.WEBSERVER_DOCROOT, target_path)) except OSError as err: if err.errno != errno.EEXIST: raise dest = os.path.join(com.WEBSERVER_DOCROOT, self.path.lstrip("/")) if os.path.islink(dest) or os.path.exists(dest): os.remove(dest) os.symlink(self.path, dest)
def do_disable_service(cmd_options=None): ''' Disable a service Disable the specified service and optionally update the service's properties to reflect the new status. Input: List of command line options Return: None Raises: SystemExit if missing permissions, invalid service name, or if attempt to place smf service in maintenance fails. ''' logging.debug('**** START do_disable_service ****') usage = '\n' + get_disable_usage() parser = OptionParser(usage=usage) (options, args) = parser.parse_args(cmd_options) # Check for privileges if os.geteuid() != 0: raise SystemExit( _("Error: Root privileges are required for " "this command.")) # Check for correct number of args if len(args) != 1: if len(args) == 0: parser.error(_("Missing required argument, <svcname>")) else: parser.error(_("Too many arguments: %s") % args) svcname = args[0] # validate service name try: validate_service_name(svcname) except ValueError as err: raise SystemExit(err) if not config.is_service(svcname): err_msg = _("The service does not exist: %s\n") % svcname parser.error(err_msg) prop_data = config.get_service_props(svcname) if prop_data and config.PROP_STATUS not in prop_data: err_msg = _("The property, status, is missing for %s.\n") % svcname parser.error(err_msg) if prop_data[config.PROP_STATUS] == config.STATUS_OFF: err_msg = _("The service is not running: %s\n") % svcname parser.error(err_msg) try: logging.debug("Disabling install service %s", svcname) service = AIService(svcname) service.disable(force=True) except (config.ServiceCfgError, aismf.ServicesError, MountError) as err: raise SystemExit(err) except CalledProcessError: return 1
def image_create(cls, fmri_or_p5i, targetdir, arch=None, publisher=None): logging.debug("image_create, install from=%s", fmri_or_p5i) tracker = pkg.client.progress.CommandLineProgressTracker() root_img = pkg.client.api.ImageInterface("/", PKG5_API_VERSION, tracker, None, cls._PKG_CLIENT_NAME) # In the future, handle: # * SSL repos (keys/certs may need explicit flags from user) if publisher is not None: prefix = publisher[0] order = [prefix] repo = pkg.client.publisher.Repository(origins=[publisher[1]]) pub = pkg.client.publisher.Publisher(prefix, repository=repo) publishers = {prefix: pub} else: publishers = dict() order = list() for pub in root_img.get_publishers(duplicate=True): if pub.disabled: logging.debug("skipping disabled publisher '%s'", pub.prefix) continue publishers[pub.prefix] = pub order.append(pub.prefix) if not publishers: raise ImageError( _("\nError:\tThere are no enabled " "publishers.\n")) if arch is None: arch = root_img.img.get_variants()[cls.ARCH_VARIANT] variants = {cls.ARCH_VARIANT: arch} props = {pkg.client.imageconfig.FLUSH_CONTENT_CACHE: True} pkgimg = pkg.client.api.image_create(cls._PKG_CLIENT_NAME, PKG5_API_VERSION, targetdir, pkg.client.imagetypes.IMG_USER, is_zone=False, progtrack=tracker, props=props, variants=variants) # Add publishers to the new image, preserving the original # search order search_after = None for pub_prefix in order: add_pub = publishers[pub_prefix] pkgimg.add_publisher(add_pub, search_after=search_after) logging.debug("adding publisher '%s' after '%s'", add_pub.prefix, search_after) search_after = pub_prefix ai_img = cls(targetdir, pkg_image=pkgimg) ai_img._install_package(fmri_or_p5i) ai_img.verify() ai_img._prep_ai_webserver() return ai_img
def check_update(self, fmri=None, publisher=None): '''Checks to see if any updates are available for this image. If so, self.pkg_image will be left "ready" to complete the update. Input: fmri - pkg to which to potentially update publisher - tuple (prefix, origin) to use for update. If that publisher already exists in the image, its origins/mirrors are reset to the passed in origin. Otherwise, the new publisher is added. All other publishers in the image are removed. Returns: True if update available; False if not ''' logging.debug('check_update fmri=%s, publisher=%s', fmri, publisher) logging.debug("currently installed pfmri is: %s", self.get_installed_pfmri()) if fmri is not None: # validate fmri specified by user pkgfmri = self.check_fmri(fmri) fmri = [str(pkgfmri)] if publisher is not None: new_repo = pkg.client.publisher.Repository(origins=[publisher[1]]) new_repo_uri = new_repo.origins[0].uri new_pub = pkg.client.publisher.Publisher(publisher[0], repository=new_repo) if fmri: # ensure that user didn't specify conflicting publisher names if pkgfmri.publisher and pkgfmri.publisher != new_pub.prefix: raise ValueError(cw( _('\nError: FMRI publisher, "%(pub1)s", does not ' 'match specified --publisher, "%(pub2)s".\n' % {'pub1': pkgfmri.publisher, 'pub2': new_pub.prefix}))) # Replace existing publisher(s) with that specified by user same_pub = None if self.pkg_image.has_publisher(new_pub.prefix): # Specified publisher already exists same_pub = self.pkg_image.get_publisher(new_pub.prefix, duplicate=True) logging.debug('basesvc has same pub %s', same_pub.prefix) logging.debug('origins are:\n%s', '\n'.join(orig.uri for orig in same_pub.repository.origins)) logging.debug('replacing origins with new uri, %s', new_repo_uri) same_pub.repository.reset_origins() same_pub.repository.reset_mirrors() same_pub.repository.add_origin(new_repo_uri) self.pkg_image.update_publisher(same_pub, search_first=True) else: # create a new publisher logging.debug('adding pub %s, origin %s', new_pub.prefix, new_repo_uri) self.pkg_image.add_publisher(new_pub, search_first=True) # Remove any other publishers for pub in self.pkg_image.get_publishers(duplicate=True)[1:]: logging.debug('removing pub %s', pub.prefix) self.pkg_image.remove_publisher(prefix=pub.prefix) for plan_desc in self.pkg_image.gen_plan_update(pkgs_update=fmri): continue return (not self.pkg_image.planned_nothingtodo())
class InstalladmPkgImage(InstalladmImage): '''Handles creation of a pkg(5)-based InstalladmImage''' _PKG_CLIENT_NAME = "installadm" DEFAULT_PKG_NAME = 'install-image/solaris-auto-install' ARCH_VARIANT = u'variant.arch' SVC_NAME_ATTR = 'com.oracle.install.service-name' INVALID_AI_IMAGE = _( "\nError:\tThe pkg image is not an Automated Installer image.\n") def __init__(self, image_path, pkg_image=None): super(InstalladmPkgImage, self).__init__(image_path) self._pkgimg = pkg_image @classmethod def image_create(cls, fmri_or_p5i, targetdir, arch=None, publisher=None): logging.debug("image_create, install from=%s", fmri_or_p5i) tracker = pkg.client.progress.CommandLineProgressTracker() root_img = pkg.client.api.ImageInterface("/", PKG5_API_VERSION, tracker, None, cls._PKG_CLIENT_NAME) # In the future, handle: # * SSL repos (keys/certs may need explicit flags from user) if publisher is not None: prefix = publisher[0] order = [prefix] repo = pkg.client.publisher.Repository(origins=[publisher[1]]) pub = pkg.client.publisher.Publisher(prefix, repository=repo) publishers = {prefix: pub} else: publishers = dict() order = list() for pub in root_img.get_publishers(duplicate=True): if pub.disabled: logging.debug("skipping disabled publisher '%s'", pub.prefix) continue publishers[pub.prefix] = pub order.append(pub.prefix) if not publishers: raise ImageError( _("\nError:\tThere are no enabled " "publishers.\n")) if arch is None: arch = root_img.img.get_variants()[cls.ARCH_VARIANT] variants = {cls.ARCH_VARIANT: arch} props = {pkg.client.imageconfig.FLUSH_CONTENT_CACHE: True} pkgimg = pkg.client.api.image_create(cls._PKG_CLIENT_NAME, PKG5_API_VERSION, targetdir, pkg.client.imagetypes.IMG_USER, is_zone=False, progtrack=tracker, props=props, variants=variants) # Add publishers to the new image, preserving the original # search order search_after = None for pub_prefix in order: add_pub = publishers[pub_prefix] pkgimg.add_publisher(add_pub, search_after=search_after) logging.debug("adding publisher '%s' after '%s'", add_pub.prefix, search_after) search_after = pub_prefix ai_img = cls(targetdir, pkg_image=pkgimg) ai_img._install_package(fmri_or_p5i) ai_img.verify() ai_img._prep_ai_webserver() return ai_img @property def pkg_image(self): if self._pkgimg is None: tracker = pkg.client.progress.CommandLineProgressTracker() # installadm is non-interactive, so we don't need to track # the "cancel_state" like, for example, packagemanager cancel_state_callable = None self._pkgimg = pkg.client.api.ImageInterface( self.path, PKG5_API_VERSION, tracker, cancel_state_callable, self._PKG_CLIENT_NAME) return self._pkgimg def _install_package(self, fmri_or_p5i): try: p5i_data = self.pkg_image.parse_p5i(location=fmri_or_p5i) # Returns a list of tuples; should only be one publisher with # one package if len(p5i_data) != 1: raise ImageError( _("\nError:\tMore than one publisher " "in p5i file.\n")) pub, pkgs = p5i_data[0] if len(pkgs) != 1: raise ImageError( _("\nError:\tMore than one package " "in p5i file.\n")) if pub and self.pkg_image.has_publisher(prefix=pub.prefix): img_pub = self.pkg_image.get_publisher(prefix=pub.prefix, duplicate=True) for origin in pub.repository.origins: if not img_pub.repository.has_origin(origin): img_pub.repository.add_origin(origin) for mirror in pub.repository.mirrors: if not img_pub.repository.has_mirror(mirror): img_pub.repository.add_mirror(mirror) self.pkg_image.update_publisher(img_pub) elif pub: self.pkg_image.add_publisher(pub) except (pkg.client.api_errors.InvalidP5IFile, pkg.client.api_errors.RetrievalError): pkgs = [fmri_or_p5i] self.pkg_image.plan_install(pkgs) # accept licenses plan = self.pkg_image.describe() for pfmri, src, dest, accepted, displayed in plan.get_licenses(): if not dest.must_accept: continue self.pkg_image.set_plan_license_status(pfmri, dest.license, accepted=True) self.pkg_image.prepare() self.pkg_image.execute_plan() self.pkg_image.reset() def get_basename(self): '''Get pkg service basename ''' basename = "solarisx" try: pkg_list = self.pkg_image.get_pkg_list( pkg.client.api.ImageInterface.LIST_INSTALLED, raise_unmatched=True, return_fmris=True) for pfmri, summ, cats, states, attrs in pkg_list: manifest = self.pkg_image.get_manifest(pfmri) for action in manifest.gen_actions_by_type("set"): for attrval in action.attrlist("name"): if (attrval == self.SVC_NAME_ATTR and action.attrs.get( "variant.arch", self.arch) == self.arch): basename = action.attrs["value"].strip() except pkg.client.api_errors.ApiException: pass logging.debug("get_basename returning %s", basename) return basename
def get_usage(): ''' get usage for delete-profile''' return _("delete-profile\t-p|--profile <profile_name> ... " "-n|--service <svcname>")
def do_rename_service(cmd_options=None): '''Rename a service. Note: Errors that occur during the various rename stages are printed, but the other stages will continue, with the hopes of leaving the final product as close to functional as possible ''' # check that we are root if os.geteuid() != 0: raise SystemExit(_("Error: Root privileges are required for this " "command.\n")) (svcname, newsvcname) = parse_options(cmd_options) # Ensure the service to rename is a valid service if not config.is_service(svcname): raise SystemExit(_("\nFailed to find service %s\n") % svcname) # Ensure the new name is not already a service if config.is_service(newsvcname): raise SystemExit(_("\nService or alias already exists: %s\n") % newsvcname) # Don't allow renaming to/from the 'default-<arch>' aliases if svcname in DEFAULT_ARCH: raise SystemExit(_('\nYou may not rename the "%s" service.\n') % svcname) if newsvcname in DEFAULT_ARCH: raise SystemExit(cw(_('\nYou may not rename a service to be the ' 'default service for an architecture. To create ' 'the default-sparc or default-i386 service ' 'aliases, use "installadm create-service ' '-t|--aliasof."\n'))) # Unmount old service was_mounted = False try: oldservice = AIService(svcname) if oldservice.mounted(): was_mounted = True logging.debug("disabling %s", svcname) oldservice.disable(force=True) except (MountError, ImageError) as err: raise SystemExit(err) # remove old mountpoint try: os.rmdir(oldservice.mountpoint) except OSError as err: # Just make a note if unable to cleanup mountpoint logging.debug(err) # Remove clients whose base service has been renamed clients = config.get_clients(svcname) for clientid in clients.keys(): clientctrl.remove_client(clientid) oldservice.rename(newsvcname) # Update aliases whose base service has been renamed aliases = config.get_aliased_services(svcname) failures = list() for alias in aliases: props = {config.PROP_ALIAS_OF: newsvcname} config.set_service_props(alias, props) # Mount the renamed service if it was mounted newservice = AIService(newsvcname) if was_mounted: try: logging.debug("enabling %s", newsvcname) newservice.enable() except (MountError, ImageError) as err: failures.append(err) print >> sys.stderr, err # Re-add clients whose base service has been renamed arch = newservice.arch for clientid in clients.keys(): # strip off leading '01' client = clientid[2:] bootargs = None if config.BOOTARGS in clients[clientid]: bootargs = clients[clientid][config.BOOTARGS] create_client.create_new_client(arch, newservice, client, bootargs=bootargs) if failures: return 1 else: return 0
def parse_options(do_create, cmd_options=None): """ Parse and validate options Args: - do_create (True) or do_update (False) - cmd_options - command line handled by OptionParser Returns: options """ if do_create: usage = '\n' + get_create_usage() else: usage = '\n' + get_update_usage() parser = OptionParser(usage=usage) if do_create: parser.add_option("-C", "--criteria-file", dest="criteria_file", default='', help=_("Name of criteria XML file.")) parser.add_option("-c", "--criteria", dest="criteria_c", action="append", default=list(), metavar="CRITERIA", help=_("Criteria: <-c criteria=value|range> ...")) parser.add_option("-f", "--file", dest="profile_file", action="append", default=list(), help=_("Path to profile file")) parser.add_option("-p", "--profile", dest="profile_name", default='', help=_("Name of profile")) parser.add_option("-n", "--service", dest="service_name", default="", help=_("Name of install service.")) options, args = parser.parse_args(cmd_options) if len(args): parser.error(_("Unexpected arguments: %s" % args)) if not do_create: options.criteria_file = None options.criteria_c = None if len(options.profile_file) > 1: parser.error(_("Provide only one file name (-f).")) if not options.service_name: parser.error(_("Service name is required (-n <service name>).")) if not options.profile_file: parser.error(_("Profile file is required (-f <profile file>).")) if options.profile_name and len(options.profile_file) > 1: parser.error( _("If a profile name is specified (-p), only one file " "name may be specified (-f).")) if not config.is_service(options.service_name): raise SystemExit(_("No such service: %s") % options.service_name) return options
def do_create_profile(cmd_options=None): ''' external entry point for installadm Arg: cmd_options - command line options Effect: add profiles to database per command line Raises SystemExit if condition cannot be handled ''' options = parse_options(DO_CREATE, cmd_options) # get AI service image path and database name service = AIService(options.service_name) image_dir = service.image.path dbname = service.database_path # open database dbn = AIdb.DB(dbname, commit=True) dbn.verifyDBStructure() queue = dbn.getQueue() root = None criteria_dict = dict() # Handle old DB versions which did not store a profile. if not AIdb.tableExists(queue, AIdb.PROFILES_TABLE): raise SystemExit( _("Error:\tService %s does not support profiles") % options.service_name) try: if options.criteria_file: # extract criteria from file root = df.verifyCriteria(df.DataFiles.criteriaSchema, options.criteria_file, dbn, AIdb.PROFILES_TABLE) elif options.criteria_c: # if we have criteria from cmd line, convert into dictionary criteria_dict = pub_man.criteria_to_dict(options.criteria_c) root = df.verifyCriteriaDict(df.DataFiles.criteriaSchema, criteria_dict, dbn, AIdb.PROFILES_TABLE) except ValueError as err: raise SystemExit(_("Error:\tcriteria error: %s") % err) # Instantiate a Criteria object with the XML DOM of the criteria. criteria = df.Criteria(root) sc.validate_criteria_from_user(criteria, dbn, AIdb.PROFILES_TABLE) # track exit status for all profiles, assuming no errors has_errors = False # loop through each profile on command line for profile_file in options.profile_file: # take option name either from command line or from basename of profile if options.profile_name: profile_name = options.profile_name else: profile_name = os.path.basename(profile_file) # check for any scope violations if sc.is_name_in_table(profile_name, queue, AIdb.PROFILES_TABLE): print >> sys.stderr, \ _("Error: A profile named %s is already in the database " "for service %s.") % (profile_name, options.service_name) has_errors = True continue # open profile file specified by user on command line if not os.path.exists(profile_file): print >> sys.stderr, _("File %s does not exist") % profile_file has_errors = True continue # validates the profile and report errors if found raw_profile = df.validate_file(profile_name, profile_file, image_dir, verbose=False) if not raw_profile: has_errors = True continue # create file from profile string and report failures full_profile_path = copy_profile_internally(raw_profile) if not full_profile_path: has_errors = True continue # add new profile to database if not add_profile(criteria, profile_name, full_profile_path, queue, AIdb.PROFILES_TABLE): os.unlink(full_profile_path) # failure, back out internal profile has_errors = True # exit with status if any errors in any profiles if has_errors: sys.exit(1)