def should_register_cse(client, ext_install): """Decides if CSE installation should register CSE to vCD. Returns False if CSE is already registered, or if the user declines registration. :param pyvcloud.vcd.client.Client client: :param str ext_install: 'skip' skips registration, 'config' allows registration without prompting user, 'prompt' asks user before registration. :return: boolean that signals whether we should register CSE to vCD. :rtype: bool """ if ext_install == 'skip': return False ext = APIExtension(client) try: cse_info = ext.get_extension_info(CSE_NAME, namespace=CSE_NAMESPACE) msg = f"Found 'cse' extension on vCD, enabled={cse_info['enabled']}" click.secho(msg, fg='green') LOGGER.info(msg) return False except MissingRecordException: prompt_msg = "Register 'cse' as an API extension in vCD?" if ext_install == 'prompt' and not click.confirm(prompt_msg): msg = f"Skipping CSE registration." click.secho(msg, fg='yellow') LOGGER.warning(msg) return False return True
def configure_vcd_amqp(client, exchange_name, host, port, prefix, ssl_accept_all, use_ssl, vhost, username, password, quiet=False): """Configures vCD AMQP settings/exchange using parameter values. :param pyvcloud.vcd.client.Client client: :param str exchange_name: name of exchange. :param str host: AMQP host name. :param int port: AMQP port. :param str prefix: :param bool ssl_accept_all: :param bool use_ssl: Enable ssl. :param str vhost: AMQP vhost. :param str username: AMQP username. :param str password: AMQP password. :param bool quiet: if True, disables all console and log output :raises Exception: if could not set AMQP configuration. """ amqp_service = AmqpService(client) amqp = { 'AmqpExchange': exchange_name, 'AmqpHost': host, 'AmqpPort': port, 'AmqpPrefix': prefix, 'AmqpSslAcceptAll': ssl_accept_all, 'AmqpUseSSL': use_ssl, 'AmqpUsername': username, 'AmqpVHost': vhost } # This block sets the AMQP setting values on the # vCD "System Administration Extensibility page" result = amqp_service.test_config(amqp, password) if not quiet: msg = f"AMQP test settings, result: {result['Valid'].text}" click.secho(msg, fg='yellow') LOGGER.info(msg) if result['Valid'].text == 'true': amqp_service.set_config(amqp, password) if not quiet: msg = "Updated vCD AMQP configuration" click.secho(msg, fg='green') LOGGER.info(msg) else: msg = "Couldn't set vCD AMQP configuration" if not quiet: click.secho(msg, fg='red') LOGGER.error(msg, exc_info=True) # TODO replace raw exception with specific raise Exception(msg)
def _create_amqp_exchange(exchange_name, host, port, vhost, use_ssl, username, password, msg_update_callback=None): """Create the specified AMQP exchange if it does not exist. If specified AMQP exchange exists already, does nothing. :param str exchange_name: The AMQP exchange name to check for or create. :param str host: AMQP host name. :param str password: AMQP password. :param int port: AMQP port number. :param bool use_ssl: Enable ssl. :param str username: AMQP username. :param str vhost: AMQP vhost. :param utils.ConsoleMessagePrinter msg_update_callback: Callback object that writes messages onto console. :raises AmqpError: if AMQP exchange could not be created. """ msg = f"Checking for AMQP exchange '{exchange_name}'" if msg_update_callback: msg_update_callback.info(msg) LOGGER.info(msg) credentials = pika.PlainCredentials(username, password) parameters = pika.ConnectionParameters(host, port, vhost, credentials, ssl=use_ssl, connection_attempts=3, retry_delay=2, socket_timeout=5) connection = None try: connection = pika.BlockingConnection(parameters) channel = connection.channel() channel.exchange_declare(exchange=exchange_name, exchange_type=EXCHANGE_TYPE, durable=True, auto_delete=False) except Exception as err: msg = f"Cannot create AMQP exchange '{exchange_name}'" if msg_update_callback: msg_update_callback.error(msg) LOGGER.error(msg, exc_info=True) raise AmqpError(msg, str(err)) finally: if connection is not None: connection.close() msg = f"AMQP exchange '{exchange_name}' is ready" if msg_update_callback: msg_update_callback.general(msg) LOGGER.info(msg)
def _register_cse(client, routing_key, exchange, msg_update_callback=utils.NullPrinter()): """Register or update CSE on vCD. :param pyvcloud.vcd.client.Client client: :param pyvcloud.vcd.client.Client client: :param str routing_key: :param str exchange: :param utils.ConsoleMessagePrinter msg_update_callback: Callback object. """ ext = APIExtension(client) patterns = [ f'/api/{server_constants.CSE_SERVICE_NAME}', f'/api/{server_constants.CSE_SERVICE_NAME}/.*', f'/api/{server_constants.CSE_SERVICE_NAME}/.*/.*', f'/api/{server_constants.PKS_SERVICE_NAME}', f'/api/{server_constants.PKS_SERVICE_NAME}/.*', f'/api/{server_constants.PKS_SERVICE_NAME}/.*/.*' ] cse_info = None try: cse_info = ext.get_extension_info( server_constants.CSE_SERVICE_NAME, namespace=server_constants.CSE_SERVICE_NAMESPACE) # noqa: E501 except MissingRecordException: pass if cse_info is None: ext.add_extension( server_constants.CSE_SERVICE_NAME, server_constants.CSE_SERVICE_NAMESPACE, routing_key, # noqa: E501 exchange, patterns) msg = f"Registered {server_constants.CSE_SERVICE_NAME} as an API extension in vCD" # noqa: E501 else: ext.update_extension( server_constants.CSE_SERVICE_NAME, namespace=server_constants.CSE_SERVICE_NAMESPACE, # noqa: E501 routing_key=routing_key, exchange=exchange) msg = f"Updated {server_constants.CSE_SERVICE_NAME} API Extension in vCD" # noqa: E501 msg_update_callback.general(msg) INSTALL_LOGGER.info(msg)
def _register_right(client, right_name, description, category, bundle_key, msg_update_callback=utils.NullPrinter()): """Register a right for CSE. :param pyvcloud.vcd.client.Client client: :param str right_name: the name of the new right to be registered. :param str description: brief description about the new right. :param str category: add the right in existing categories in vCD Roles and Rights or specify a new category name. :param str bundle_key: is used to identify the right name and change its value to different languages using localization bundle. :param utils.ConsoleMessagePrinter msg_update_callback: Callback object. :raises BadRequestException: if a right with given name already exists in vCD. """ ext = APIExtension(client) # Since the client is a sys admin, org will hold a reference to System org system_org = Org(client, resource=client.get_org()) try: right_name_in_vcd = f"{{{server_constants.CSE_SERVICE_NAME}}}:{right_name}" # noqa: E501 # TODO(): When org.get_right_record() is moved outside the org scope in # pyvcloud, update the code below to adhere to the new method names. system_org.get_right_record(right_name_in_vcd) msg = f"Right: {right_name} already exists in vCD" msg_update_callback.general(msg) INSTALL_LOGGER.info(msg) # Presence of the right in vCD is not a guarantee that the right will # be assigned to system org too. rights_in_system = system_org.list_rights_of_org() for dikt in rights_in_system: # TODO(): When localization support comes in, this check should be # ditched for a better one. if dikt['name'] == right_name_in_vcd: msg = f"Right: {right_name} already assigned to System " \ f"organization." msg_update_callback.general(msg) INSTALL_LOGGER.info(msg) return # Since the right is not assigned to system org, we need to add it. msg = f"Assigning Right: {right_name} to System organization." msg_update_callback.general(msg) INSTALL_LOGGER.info(msg) system_org.add_rights([right_name_in_vcd]) except EntityNotFoundException: # Registering a right via api extension end point, auto assigns it to # System org. msg = f"Registering Right: {right_name} in vCD" msg_update_callback.general(msg) INSTALL_LOGGER.info(msg) ext.add_service_right(right_name, server_constants.CSE_SERVICE_NAME, server_constants.CSE_SERVICE_NAMESPACE, description, category, bundle_key)
def register_cse(client, amqp_routing_key, exchange_name): """Registers CSE to vCD. :param pyvcloud.vcd.client.Client client: :param str amqp_routing_key: :param str exchange_name: AMQP exchange name. """ ext = APIExtension(client) patterns = [ f'/api/{CSE_NAME}', f'/api/{CSE_NAME}/.*', f'/api/{CSE_NAME}/.*/.*' ] ext.add_extension(CSE_NAME, CSE_NAMESPACE, amqp_routing_key, exchange_name, patterns) msg = f"Registered {CSE_NAME} as an API extension in vCD" click.secho(msg, fg='green') LOGGER.info(msg)
def _create_temp_vapp(ctx, client, vdc, config, template_config, ssh_key): """Handles temporary VApp creation and customization step of CSE install. Initializes and customizes VApp. :param click.core.Context ctx: click context object. :param pyvcloud.vcd.client.Client client: :param dict config: CSE config. :param dict template_config: specific template config section of @config. :param str ssh_key: ssh key to use in temporary VApp's VM. Can be None. :return: VApp object for temporary VApp. :rtype: pyvcloud.vcd.vapp.VApp :raises FileNotFoundError: if init/customization scripts are not found. :raises Exception: if VM customization fails. """ vapp_name = template_config['temp_vapp'] init_script = get_data_file(f"init-{template_config['name']}.sh", logger=LOGGER) if ssh_key is not None: init_script += \ f""" mkdir -p /root/.ssh echo '{ssh_key}' >> /root/.ssh/authorized_keys chmod -R go-rwx /root/.ssh """ msg = f"Creating vApp '{vapp_name}'" click.secho(msg, fg='yellow') LOGGER.info(msg) vapp = _create_vapp_from_config(client, vdc, config, template_config, init_script) msg = f"Created vApp '{vapp_name}'" click.secho(msg, fg='green') LOGGER.info(msg) msg = f"Customizing vApp '{vapp_name}'" click.secho(msg, fg='yellow') LOGGER.info(msg) cust_script = get_data_file(f"cust-{template_config['name']}.sh", logger=LOGGER) ova_name = template_config['source_ova_name'] is_photon = True if 'photon' in ova_name else False _customize_vm(ctx, config, vapp, vapp.name, cust_script, is_photon=is_photon) msg = f"Customized vApp '{vapp_name}'" click.secho(msg, fg='green') LOGGER.info(msg) return vapp
def _register_cse(client, routing_key, exchange, msg_update_callback=None): """Register or update CSE on vCD. :param pyvcloud.vcd.client.Client client: :param pyvcloud.vcd.client.Client client: :param str routing_key: :param str exchange: :param utils.ConsoleMessagePrinter msg_update_callback: Callback object that writes messages onto console. """ ext = APIExtension(client) patterns = [ f'/api/{CSE_SERVICE_NAME}', f'/api/{CSE_SERVICE_NAME}/.*', f'/api/{CSE_SERVICE_NAME}/.*/.*' ] cse_info = None try: cse_info = ext.get_extension_info(CSE_SERVICE_NAME, namespace=CSE_SERVICE_NAMESPACE) except MissingRecordException: pass if cse_info is None: ext.add_extension(CSE_SERVICE_NAME, CSE_SERVICE_NAMESPACE, routing_key, exchange, patterns) msg = f"Registered {CSE_SERVICE_NAME} as an API extension in vCD" else: ext.update_extension(CSE_SERVICE_NAME, namespace=CSE_SERVICE_NAMESPACE, routing_key=routing_key, exchange=exchange) msg = f"Updated {CSE_SERVICE_NAME} API Extension in vCD" if msg_update_callback: msg_update_callback.general(msg) LOGGER.info(msg)
def install_template(template_name, template_revision, config_file_name, force_create, retain_temp_vapp, ssh_key, skip_config_decryption=False, decryption_password=None, msg_update_callback=None): """Install a particular template in CSE. If template_name and revision are wild carded to *, all templates defined in remote template cookbook will be installed. :param str template_name: :param str template_revision: :param str config_file_name: config file name. :param bool force_create: if True and template already exists in vCD, overwrites existing template. :param str ssh_key: public ssh key to place into template vApp(s). :param bool retain_temp_vapp: if True, temporary vApp will not destroyed, so the user can ssh into and debug the vm. :param bool skip_config_decryption: do not decrypt the config file. :param str decryption_password: password to decrypt the config file. :param utils.ConsoleMessagePrinter msg_update_callback: Callback object that writes messages onto console. """ configure_install_logger() config = get_validated_config( config_file_name, skip_config_decryption=skip_config_decryption, decryption_password=decryption_password, msg_update_callback=msg_update_callback) populate_vsphere_list(config['vcs']) msg = f"Installing template '{template_name}' at revision " \ f"'{template_revision}' on vCloud Director using config file " \ f"'{config_file_name}'" if msg_update_callback: msg_update_callback.info(msg) LOGGER.info(msg) client = None try: log_filename = None log_wire = str_to_bool(config['service'].get('log_wire')) if log_wire: log_filename = INSTALL_WIRELOG_FILEPATH client = Client(config['vcd']['host'], api_version=config['vcd']['api_version'], verify_ssl_certs=config['vcd']['verify'], log_file=log_filename, log_requests=log_wire, log_headers=log_wire, log_bodies=log_wire) credentials = BasicLoginCredentials(config['vcd']['username'], SYSTEM_ORG_NAME, config['vcd']['password']) client.set_credentials(credentials) msg = f"Connected to vCD as system administrator: " \ f"{config['vcd']['host']}:{config['vcd']['port']}" if msg_update_callback: msg_update_callback.general(msg) LOGGER.info(msg) # read remote template cookbook rtm = RemoteTemplateManager( remote_template_cookbook_url=config['broker'] ['remote_template_cookbook_url'], # noqa: E501 logger=LOGGER, msg_update_callback=msg_update_callback) remote_template_cookbook = rtm.get_remote_template_cookbook() found_template = False for template in remote_template_cookbook['templates']: template_name_matched = template_name in ( template[RemoteTemplateKey.NAME], '*') # noqa: E501 template_revision_matched = str(template_revision) in (str( template[RemoteTemplateKey.REVISION]), '*') # noqa: E501 if template_name_matched and template_revision_matched: found_template = True _install_template( client=client, remote_template_manager=rtm, template=template, org_name=config['broker']['org'], vdc_name=config['broker']['vdc'], catalog_name=config['broker']['catalog'], network_name=config['broker']['network'], ip_allocation_mode=config['broker']['ip_allocation_mode'], storage_profile=config['broker']['storage_profile'], force_update=force_create, retain_temp_vapp=retain_temp_vapp, ssh_key=ssh_key, msg_update_callback=msg_update_callback) if not found_template: msg = f"Template '{template_name}' at revision " \ f"'{template_revision}' not found in remote template " \ "cookbook." if msg_update_callback: msg_update_callback.error(msg) LOGGER.error(msg, exc_info=True) except Exception: if msg_update_callback: msg_update_callback.error( "Template Installation Error. Check CSE install logs") LOGGER.error("Template Installation Error", exc_info=True) finally: if client is not None: client.logout()
def install_cse(config_file_name, skip_template_creation, force_update, ssh_key, retain_temp_vapp, pks_config_file_name=None, skip_config_decryption=False, decryption_password=None, msg_update_callback=None): """Handle logistics for CSE installation. Handles decision making for configuring AMQP exchange/settings, extension registration, catalog setup, and template creation. :param str config_file_name: config file name. :param bool skip_template_creation: If True, skip creating the templates. :param bool force_update: if True and templates already exist in vCD, overwrites existing templates. :param str ssh_key: public ssh key to place into template vApp(s). :param bool retain_temp_vapp: if True, temporary vApp will not destroyed, so the user can ssh into and debug the vm. :param str pks_config_file_name: pks config file name. :param bool skip_config_decryption: do not decrypt the config file. :param str decryption_password: password to decrypt the config file. :param utils.ConsoleMessagePrinter msg_update_callback: Callback object that writes messages onto console. :raises AmqpError: if AMQP exchange could not be created. """ configure_install_logger() config = get_validated_config( config_file_name, pks_config_file_name=pks_config_file_name, skip_config_decryption=skip_config_decryption, decryption_password=decryption_password, msg_update_callback=msg_update_callback) populate_vsphere_list(config['vcs']) msg = f"Installing CSE on vCloud Director using config file " \ f"'{config_file_name}'" if msg_update_callback: msg_update_callback.info(msg) LOGGER.info(msg) client = None try: log_filename = None log_wire = str_to_bool(config['service'].get('log_wire')) if log_wire: log_filename = INSTALL_WIRELOG_FILEPATH client = Client(config['vcd']['host'], api_version=config['vcd']['api_version'], verify_ssl_certs=config['vcd']['verify'], log_file=log_filename, log_requests=log_wire, log_headers=log_wire, log_bodies=log_wire) credentials = BasicLoginCredentials(config['vcd']['username'], SYSTEM_ORG_NAME, config['vcd']['password']) client.set_credentials(credentials) msg = f"Connected to vCD as system administrator: " \ f"{config['vcd']['host']}:{config['vcd']['port']}" if msg_update_callback: msg_update_callback.general(msg) LOGGER.info(msg) # create amqp exchange if it doesn't exist amqp = config['amqp'] _create_amqp_exchange(amqp['exchange'], amqp['host'], amqp['port'], amqp['vhost'], amqp['ssl'], amqp['username'], amqp['password'], msg_update_callback=msg_update_callback) # register or update cse on vCD _register_cse(client, amqp['routing_key'], amqp['exchange'], msg_update_callback=msg_update_callback) # register rights to vCD # TODO() should also remove rights when unregistering CSE _register_right(client, right_name=CSE_NATIVE_DEPLOY_RIGHT_NAME, description=CSE_NATIVE_DEPLOY_RIGHT_DESCRIPTION, category=CSE_NATIVE_DEPLOY_RIGHT_CATEGORY, bundle_key=CSE_NATIVE_DEPLOY_RIGHT_BUNDLE_KEY, msg_update_callback=msg_update_callback) _register_right(client, right_name=CSE_PKS_DEPLOY_RIGHT_NAME, description=CSE_PKS_DEPLOY_RIGHT_DESCRIPTION, category=CSE_PKS_DEPLOY_RIGHT_CATEGORY, bundle_key=CSE_PKS_DEPLOY_RIGHT_BUNDLE_KEY, msg_update_callback=msg_update_callback) # set up cse catalog org = get_org(client, org_name=config['broker']['org']) create_and_share_catalog(org, config['broker']['catalog'], catalog_desc='CSE templates', msg_update_callback=msg_update_callback) if skip_template_creation: msg = "Skipping creation of templates." if msg_update_callback: msg_update_callback.info(msg) LOGGER.warning(msg) else: # read remote template cookbook, download all scripts rtm = RemoteTemplateManager( remote_template_cookbook_url=config['broker'] ['remote_template_cookbook_url'], # noqa: E501 logger=LOGGER, msg_update_callback=msg_update_callback) remote_template_cookbook = rtm.get_remote_template_cookbook() # create all templates defined in cookbook for template in remote_template_cookbook['templates']: _install_template( client=client, remote_template_manager=rtm, template=template, org_name=config['broker']['org'], vdc_name=config['broker']['vdc'], catalog_name=config['broker']['catalog'], network_name=config['broker']['network'], ip_allocation_mode=config['broker']['ip_allocation_mode'], storage_profile=config['broker']['storage_profile'], force_update=force_update, retain_temp_vapp=retain_temp_vapp, ssh_key=ssh_key, msg_update_callback=msg_update_callback) # if it's a PKS setup, setup NSX-T constructs if config.get('pks_config'): nsxt_servers = config.get('pks_config')['nsxt_servers'] for nsxt_server in nsxt_servers: msg = f"Configuring NSX-T server ({nsxt_server.get('name')})" \ " for CSE. Please check install logs for details." if msg_update_callback: msg_update_callback.general(msg) LOGGER.info(msg) nsxt_client = NSXTClient(host=nsxt_server.get('host'), username=nsxt_server.get('username'), password=nsxt_server.get('password'), http_proxy=nsxt_server.get('proxy'), https_proxy=nsxt_server.get('proxy'), verify_ssl=nsxt_server.get('verify'), logger_instance=LOGGER, log_requests=True, log_headers=True, log_body=True) setup_nsxt_constructs( nsxt_client=nsxt_client, nodes_ip_block_id=nsxt_server.get('nodes_ip_block_ids'), pods_ip_block_id=nsxt_server.get('pods_ip_block_ids'), ncp_boundary_firewall_section_anchor_id=nsxt_server.get( 'distributed_firewall_section_anchor_id') ) # noqa: E501 except Exception: if msg_update_callback: msg_update_callback.error( "CSE Installation Error. Check CSE install logs") LOGGER.error("CSE Installation Error", exc_info=True) raise # TODO() need installation relevant exceptions for rollback finally: if client is not None: client.logout()
def install_template(template_name, template_revision, config_file_name, force_create, retain_temp_vapp, ssh_key, skip_config_decryption=False, decryption_password=None, msg_update_callback=utils.NullPrinter()): """Install a particular template in CSE. If template_name and revision are wild carded to *, all templates defined in remote template cookbook will be installed. :param str template_name: :param str template_revision: :param str config_file_name: config file name. :param bool force_create: if True and template already exists in vCD, overwrites existing template. :param str ssh_key: public ssh key to place into template vApp(s). :param bool retain_temp_vapp: if True, temporary vApp will not destroyed, so the user can ssh into and debug the vm. :param bool skip_config_decryption: do not decrypt the config file. :param str decryption_password: password to decrypt the config file. :param utils.ConsoleMessagePrinter msg_update_callback: Callback object. """ config = get_validated_config( config_file_name, skip_config_decryption=skip_config_decryption, decryption_password=decryption_password, log_wire_file=INSTALL_WIRELOG_FILEPATH, logger_debug=INSTALL_LOGGER, msg_update_callback=msg_update_callback) populate_vsphere_list(config['vcs']) msg = f"Installing template '{template_name}' at revision " \ f"'{template_revision}' on vCloud Director using config file " \ f"'{config_file_name}'" msg_update_callback.info(msg) INSTALL_LOGGER.info(msg) client = None try: # Telemetry data construction cse_params = { PayloadKey.TEMPLATE_NAME: template_name, PayloadKey.TEMPLATE_REVISION: template_revision, PayloadKey.WAS_DECRYPTION_SKIPPED: bool(skip_config_decryption), PayloadKey.WERE_TEMPLATES_FORCE_UPDATED: bool(force_create), PayloadKey.WAS_TEMP_VAPP_RETAINED: bool(retain_temp_vapp), PayloadKey.WAS_SSH_KEY_SPECIFIED: bool(ssh_key) } # Record telemetry data record_user_action_details( cse_operation=CseOperation.TEMPLATE_INSTALL, cse_params=cse_params, telemetry_settings=config['service']['telemetry']) log_filename = None log_wire = utils.str_to_bool(config['service'].get('log_wire')) if log_wire: log_filename = INSTALL_WIRELOG_FILEPATH client = Client(config['vcd']['host'], api_version=config['vcd']['api_version'], verify_ssl_certs=config['vcd']['verify'], log_file=log_filename, log_requests=log_wire, log_headers=log_wire, log_bodies=log_wire) credentials = BasicLoginCredentials(config['vcd']['username'], server_constants.SYSTEM_ORG_NAME, config['vcd']['password']) client.set_credentials(credentials) msg = f"Connected to vCD as system administrator: " \ f"{config['vcd']['host']}:{config['vcd']['port']}" msg_update_callback.general(msg) INSTALL_LOGGER.info(msg) # read remote template cookbook rtm = RemoteTemplateManager( remote_template_cookbook_url=config['broker'] ['remote_template_cookbook_url'], # noqa: E501 logger=INSTALL_LOGGER, msg_update_callback=msg_update_callback) remote_template_cookbook = rtm.get_remote_template_cookbook() found_template = False for template in remote_template_cookbook['templates']: template_name_matched = template_name in ( template[server_constants.RemoteTemplateKey.NAME], '*' ) # noqa: E501 template_revision_matched = \ str(template_revision) in (str(template[server_constants.RemoteTemplateKey.REVISION]), '*') # noqa: E501 if template_name_matched and template_revision_matched: found_template = True _install_template( client=client, remote_template_manager=rtm, template=template, org_name=config['broker']['org'], vdc_name=config['broker']['vdc'], catalog_name=config['broker']['catalog'], network_name=config['broker']['network'], ip_allocation_mode=config['broker']['ip_allocation_mode'], storage_profile=config['broker']['storage_profile'], force_update=force_create, retain_temp_vapp=retain_temp_vapp, ssh_key=ssh_key, msg_update_callback=msg_update_callback) if not found_template: msg = f"Template '{template_name}' at revision " \ f"'{template_revision}' not found in remote template " \ "cookbook." msg_update_callback.error(msg) INSTALL_LOGGER.error(msg, exc_info=True) raise Exception(msg) # Record telemetry data on successful template install record_user_action( cse_operation=CseOperation.TEMPLATE_INSTALL, status=OperationStatus.SUCCESS, telemetry_settings=config['service']['telemetry']) # noqa: E501 except Exception: msg_update_callback.error( "Template Installation Error. Check CSE install logs") INSTALL_LOGGER.error("Template Installation Error", exc_info=True) # Record telemetry data on template install failure record_user_action(cse_operation=CseOperation.TEMPLATE_INSTALL, status=OperationStatus.FAILED, telemetry_settings=config['service']['telemetry']) finally: if client is not None: client.logout()
def install_cse(config_file_name, skip_template_creation, force_update, ssh_key, retain_temp_vapp, pks_config_file_name=None, skip_config_decryption=False, decryption_password=None, msg_update_callback=utils.NullPrinter()): """Handle logistics for CSE installation. Handles decision making for configuring AMQP exchange/settings, defined entity schema registration for vCD api version >= 35, extension registration, catalog setup and template creation. Also records telemetry data on installation details. :param str config_file_name: config file name. :param bool skip_template_creation: If True, skip creating the templates. :param bool force_update: if True and templates already exist in vCD, overwrites existing templates. :param str ssh_key: public ssh key to place into template vApp(s). :param bool retain_temp_vapp: if True, temporary vApp will not destroyed, so the user can ssh into and debug the vm. :param str pks_config_file_name: pks config file name. :param bool skip_config_decryption: do not decrypt the config file. :param str decryption_password: password to decrypt the config file. :param utils.ConsoleMessagePrinter msg_update_callback: Callback object. :raises cse_exception.AmqpError: if AMQP exchange could not be created. """ config = get_validated_config( config_file_name, pks_config_file_name=pks_config_file_name, skip_config_decryption=skip_config_decryption, decryption_password=decryption_password, log_wire_file=INSTALL_WIRELOG_FILEPATH, logger_debug=INSTALL_LOGGER, msg_update_callback=msg_update_callback) populate_vsphere_list(config['vcs']) msg = f"Installing CSE on vCloud Director using config file " \ f"'{config_file_name}'" msg_update_callback.info(msg) INSTALL_LOGGER.info(msg) client = None try: # Telemetry - Construct telemetry data telemetry_data = { PayloadKey.WAS_DECRYPTION_SKIPPED: bool(skip_config_decryption), # noqa: E501 PayloadKey.WAS_PKS_CONFIG_FILE_PROVIDED: bool(pks_config_file_name), # noqa: E501 PayloadKey.WERE_TEMPLATES_SKIPPED: bool(skip_template_creation), # noqa: E501 PayloadKey.WERE_TEMPLATES_FORCE_UPDATED: bool(force_update), # noqa: E501 PayloadKey.WAS_TEMP_VAPP_RETAINED: bool(retain_temp_vapp), # noqa: E501 PayloadKey.WAS_SSH_KEY_SPECIFIED: bool(ssh_key) # noqa: E501 } # Telemetry - Record detailed telemetry data on install record_user_action_details( CseOperation.SERVICE_INSTALL, telemetry_data, telemetry_settings=config['service']['telemetry']) # noqa: E501 log_filename = None log_wire = utils.str_to_bool(config['service'].get('log_wire')) if log_wire: log_filename = INSTALL_WIRELOG_FILEPATH client = Client(config['vcd']['host'], api_version=config['vcd']['api_version'], verify_ssl_certs=config['vcd']['verify'], log_file=log_filename, log_requests=log_wire, log_headers=log_wire, log_bodies=log_wire) credentials = BasicLoginCredentials(config['vcd']['username'], server_constants.SYSTEM_ORG_NAME, config['vcd']['password']) client.set_credentials(credentials) msg = f"Connected to vCD as system administrator: " \ f"{config['vcd']['host']}:{config['vcd']['port']}" msg_update_callback.general(msg) INSTALL_LOGGER.info(msg) # create amqp exchange if it doesn't exist amqp = config['amqp'] _create_amqp_exchange(amqp['exchange'], amqp['host'], amqp['port'], amqp['vhost'], amqp['ssl'], amqp['username'], amqp['password'], msg_update_callback=msg_update_callback) # register or update cse on vCD _register_cse(client, amqp['routing_key'], amqp['exchange'], msg_update_callback=msg_update_callback) # register cse def schema on VCD # schema should be located at # ~/.cse-schema/api-v<API VERSION>/schema.json _register_def_schema(client, msg_update_callback=msg_update_callback, log_wire=log_wire) # Since we use CSE extension id as our telemetry instance_id, the # validated config won't have the instance_id yet. Now that CSE has # been registered as an extension, we should update the telemetry # config with the correct instance_id if config['service']['telemetry']['enable']: store_telemetry_settings(config) # register rights to vCD # TODO() should also remove rights when unregistering CSE _register_right( client, right_name=server_constants. CSE_NATIVE_DEPLOY_RIGHT_NAME, # noqa: E501 description=server_constants. CSE_NATIVE_DEPLOY_RIGHT_DESCRIPTION, # noqa: E501 category=server_constants. CSE_NATIVE_DEPLOY_RIGHT_CATEGORY, # noqa: E501 bundle_key=server_constants. CSE_NATIVE_DEPLOY_RIGHT_BUNDLE_KEY, # noqa: E501 msg_update_callback=msg_update_callback) _register_right( client, right_name=server_constants. CSE_PKS_DEPLOY_RIGHT_NAME, # noqa: E501 description=server_constants. CSE_PKS_DEPLOY_RIGHT_DESCRIPTION, # noqa: E501 category=server_constants. CSE_PKS_DEPLOY_RIGHT_CATEGORY, # noqa: E501 bundle_key=server_constants. CSE_PKS_DEPLOY_RIGHT_BUNDLE_KEY, # noqa: E501 msg_update_callback=msg_update_callback) # set up placement policies for all types of clusters _setup_placement_policies( client, policy_list=server_constants. CLUSTER_PLACEMENT_POLICIES, # noqa: E501 msg_update_callback=msg_update_callback, log_wire=log_wire) # set up cse catalog org = vcd_utils.get_org(client, org_name=config['broker']['org']) vcd_utils.create_and_share_catalog( org, config['broker']['catalog'], catalog_desc='CSE templates', logger=INSTALL_LOGGER, msg_update_callback=msg_update_callback) if skip_template_creation: msg = "Skipping creation of templates." msg_update_callback.info(msg) INSTALL_LOGGER.warning(msg) else: # read remote template cookbook, download all scripts rtm = RemoteTemplateManager( remote_template_cookbook_url=config['broker'] ['remote_template_cookbook_url'], # noqa: E501 logger=INSTALL_LOGGER, msg_update_callback=msg_update_callback) remote_template_cookbook = rtm.get_remote_template_cookbook() # create all templates defined in cookbook for template in remote_template_cookbook['templates']: # TODO tag created templates with placement policies _install_template( client=client, remote_template_manager=rtm, template=template, org_name=config['broker']['org'], vdc_name=config['broker']['vdc'], catalog_name=config['broker']['catalog'], network_name=config['broker']['network'], ip_allocation_mode=config['broker']['ip_allocation_mode'], storage_profile=config['broker']['storage_profile'], force_update=force_update, retain_temp_vapp=retain_temp_vapp, ssh_key=ssh_key, msg_update_callback=msg_update_callback) # if it's a PKS setup, setup NSX-T constructs if config.get('pks_config'): nsxt_servers = config['pks_config']['nsxt_servers'] wire_logger = NULL_LOGGER if log_wire: wire_logger = SERVER_NSXT_WIRE_LOGGER for nsxt_server in nsxt_servers: msg = f"Configuring NSX-T server ({nsxt_server.get('name')})" \ " for CSE. Please check install logs for details." msg_update_callback.general(msg) INSTALL_LOGGER.info(msg) nsxt_client = NSXTClient(host=nsxt_server.get('host'), username=nsxt_server.get('username'), password=nsxt_server.get('password'), logger_debug=INSTALL_LOGGER, logger_wire=wire_logger, http_proxy=nsxt_server.get('proxy'), https_proxy=nsxt_server.get('proxy'), verify_ssl=nsxt_server.get('verify')) setup_nsxt_constructs( nsxt_client=nsxt_client, nodes_ip_block_id=nsxt_server.get('nodes_ip_block_ids'), pods_ip_block_id=nsxt_server.get('pods_ip_block_ids'), ncp_boundary_firewall_section_anchor_id=nsxt_server.get( 'distributed_firewall_section_anchor_id') ) # noqa: E501 # Telemetry - Record successful install action record_user_action(CseOperation.SERVICE_INSTALL, telemetry_settings=config['service']['telemetry']) except Exception: msg_update_callback.error( "CSE Installation Error. Check CSE install logs") INSTALL_LOGGER.error("CSE Installation Error", exc_info=True) # Telemetry - Record failed install action record_user_action(CseOperation.SERVICE_INSTALL, status=OperationStatus.FAILED, telemetry_settings=config['service']['telemetry']) raise # TODO() need installation relevant exceptions for rollback finally: if client is not None: client.logout()
def install_cse(ctx, config_file_name='config.yaml', skip_template_creation=True, force_update=False, ssh_key=None, retain_temp_vapp=False, msg_update_callback=None): """Handle logistics for CSE installation. Handles decision making for configuring AMQP exchange/settings, extension registration, catalog setup, and template creation. :param click.core.Context ctx: :param str config_file_name: config file name. :param bool skip_template_creation: If True, skip creating the templates. :param bool force_update: if True and templates already exist in vCD, overwrites existing templates. :param str ssh_key: public ssh key to place into template vApp(s). :param bool retain_temp_vapp: if True, temporary vApp will not destroyed, so the user can ssh into and debug the vm. :param utils.ConsoleMessagePrinter msg_update_callback: Callback object that writes messages onto console. :raises AmqpError: if AMQP exchange could not be created. """ configure_install_logger() config = get_validated_config(config_file_name, msg_update_callback=msg_update_callback) populate_vsphere_list(config['vcs']) msg = f"Installing CSE on vCloud Director using config file " \ f"'{config_file_name}'" if msg_update_callback: msg_update_callback.info(msg) LOGGER.info(msg) client = None try: client = Client(config['vcd']['host'], api_version=config['vcd']['api_version'], verify_ssl_certs=config['vcd']['verify'], log_file=INSTALL_WIRELOG_FILEPATH, log_requests=True, log_headers=True, log_bodies=True) credentials = BasicLoginCredentials(config['vcd']['username'], SYSTEM_ORG_NAME, config['vcd']['password']) client.set_credentials(credentials) msg = f"Connected to vCD as system administrator: " \ f"{config['vcd']['host']}:{config['vcd']['port']}" if msg_update_callback: msg_update_callback.general(msg) LOGGER.info(msg) # create amqp exchange if it doesn't exist amqp = config['amqp'] _create_amqp_exchange(amqp['exchange'], amqp['host'], amqp['port'], amqp['vhost'], amqp['ssl'], amqp['username'], amqp['password'], msg_update_callback=msg_update_callback) # register or update cse on vCD _register_cse(client, amqp['routing_key'], amqp['exchange'], msg_update_callback=msg_update_callback) # register rights to vCD # TODO() should also remove rights when unregistering CSE _register_right(client, right_name=CSE_NATIVE_DEPLOY_RIGHT_NAME, description=CSE_NATIVE_DEPLOY_RIGHT_DESCRIPTION, category=CSE_NATIVE_DEPLOY_RIGHT_CATEGORY, bundle_key=CSE_NATIVE_DEPLOY_RIGHT_BUNDLE_KEY, msg_update_callback=msg_update_callback) _register_right(client, right_name=CSE_PKS_DEPLOY_RIGHT_NAME, description=CSE_PKS_DEPLOY_RIGHT_DESCRIPTION, category=CSE_PKS_DEPLOY_RIGHT_CATEGORY, bundle_key=CSE_PKS_DEPLOY_RIGHT_BUNDLE_KEY, msg_update_callback=msg_update_callback) org_name = config['broker']['org'] catalog_name = config['broker']['catalog'] # set up cse catalog org = get_org(client, org_name=org_name) create_and_share_catalog(org, catalog_name, catalog_desc='CSE templates', msg_update_callback=msg_update_callback) if skip_template_creation: msg = "Skipping creation of templates." if msg_update_callback: msg_update_callback.info(msg) LOGGER.warning(msg) else: # read remote template cookbook, download all scripts rtm = RemoteTemplateManager( remote_template_cookbook_url=config['broker'] ['remote_template_cookbook_url'], # noqa: E501 logger=LOGGER, msg_update_callback=ConsoleMessagePrinter()) remote_template_cookbook = rtm.get_remote_template_cookbook() # create all templates defined in cookbook for template in remote_template_cookbook['templates']: rtm.download_template_scripts( template_name=template[RemoteTemplateKey.NAME], revision=template[RemoteTemplateKey.REVISION], force_overwrite=force_update) catalog_item_name = get_revisioned_template_name( template[RemoteTemplateKey.NAME], template[RemoteTemplateKey.REVISION]) build_params = { 'template_name': template[RemoteTemplateKey.NAME], 'template_revision': template[RemoteTemplateKey.REVISION], 'source_ova_name': template[RemoteTemplateKey.SOURCE_OVA_NAME], # noqa: E501 'source_ova_href': template[RemoteTemplateKey.SOURCE_OVA_HREF], # noqa: E501 'source_ova_sha256': template[ RemoteTemplateKey.SOURCE_OVA_SHA256], # noqa: E501 'org_name': org_name, 'vdc_name': config['broker']['vdc'], 'catalog_name': catalog_name, 'catalog_item_name': catalog_item_name, 'catalog_item_description': template[RemoteTemplateKey.DESCRIPTION], # noqa: E501 'temp_vapp_name': template[RemoteTemplateKey.NAME] + '_temp', # noqa: E501 'cpu': template[RemoteTemplateKey.CPU], 'memory': template[RemoteTemplateKey.MEMORY], 'network_name': config['broker']['network'], 'ip_allocation_mode': config['broker']['ip_allocation_mode'], # noqa: E501 'storage_profile': config['broker']['storage_profile'] } builder = TemplateBuilder( client, client, build_params, ssh_key=ssh_key, logger=LOGGER, msg_update_callback=ConsoleMessagePrinter()) builder.build(force_recreate=force_update, retain_temp_vapp=retain_temp_vapp) # remote definition is a super set of local definition, barring # the key 'catalog_item_name' template_definition = dict(template) template_definition['catalog_item_name'] = catalog_item_name save_k8s_local_template_definition_as_metadata( client=client, catalog_name=catalog_name, catalog_item_name=catalog_item_name, template_definition=template_definition, org_name=org_name) # if it's a PKS setup, setup NSX-T constructs if config.get('pks_config'): nsxt_servers = config.get('pks_config')['nsxt_servers'] for nsxt_server in nsxt_servers: msg = f"Configuring NSX-T server ({nsxt_server.get('name')})" \ " for CSE. Please check install logs for details." if msg_update_callback: msg_update_callback.general(msg) LOGGER.info(msg) nsxt_client = NSXTClient(host=nsxt_server.get('host'), username=nsxt_server.get('username'), password=nsxt_server.get('password'), http_proxy=nsxt_server.get('proxy'), https_proxy=nsxt_server.get('proxy'), verify_ssl=nsxt_server.get('verify'), logger_instance=LOGGER, log_requests=True, log_headers=True, log_body=True) setup_nsxt_constructs( nsxt_client=nsxt_client, nodes_ip_block_id=nsxt_server.get('nodes_ip_block_ids'), pods_ip_block_id=nsxt_server.get('pods_ip_block_ids'), ncp_boundary_firewall_section_anchor_id=nsxt_server.get( 'distributed_firewall_section_anchor_id') ) # noqa: E501 except Exception: if msg_update_callback: msg_update_callback.error( "CSE Installation Error. Check CSE install logs") LOGGER.error("CSE Installation Error", exc_info=True) raise # TODO() need installation relevant exceptions for rollback finally: if client is not None: client.logout()
def create_template(ctx, client, config, template_config, update=False, no_capture=False, ssh_key=None, org=None, vdc=None): """Handles template creation phase during CSE installation. :param click.core.Context ctx: click context object. :param pyvcloud.vcd.client.Client client: :param dict config: CSE config. :param dict template_config: specific template section of @config. :param bool update: if True and templates already exist in vCD, overwrites existing templates. :param bool no_capture: if True, temporary vApp will not be captured or destroyed, so the user can ssh into the VM and debug. :param str ssh_key: public ssh key to place into the template vApp(s). :param pyvcloud.vcd.org.Org org: specific org to use. If None, uses org specified in @config. :param pyvcloud.vcd.vdc.VDC vdc: specific vdc to use. If None, uses vdc specified in @config. """ if org is None: org = get_org(client, org_name=config['broker']['org']) if vdc is None: vdc = get_vdc(client, config['broker']['vdc'], org=org) ctx.obj = {'client': client} catalog_name = config['broker']['catalog'] template_name = template_config['catalog_item'] vapp_name = template_config['temp_vapp'] ova_name = template_config['source_ova_name'] if not update and catalog_item_exists(org, catalog_name, template_name): msg = f"Found template '{template_name}' in catalog '{catalog_name}'" click.secho(msg, fg='green') LOGGER.info(msg) return # if update flag is set, delete existing template/ova file/temp vapp if update: msg = f"--update flag set. If template, source ova file, " \ f"and temporary vApp exist, they will be deleted" click.secho(msg, fg='yellow') LOGGER.info(msg) try: org.delete_catalog_item(catalog_name, template_name) wait_for_catalog_item_to_resolve(client, catalog_name, template_name, org=org) org.reload() msg = "Deleted vApp template" click.secho(msg, fg='green') LOGGER.info(msg) except EntityNotFoundException: pass try: org.delete_catalog_item(catalog_name, ova_name) wait_for_catalog_item_to_resolve(client, catalog_name, ova_name, org=org) org.reload() msg = "Deleted ova file" click.secho(msg, fg='green') LOGGER.info(msg) except EntityNotFoundException: pass try: task = vdc.delete_vapp(vapp_name, force=True) stdout(task, ctx=ctx) vdc.reload() msg = "Deleted temporary vApp" click.secho(msg, fg='green') LOGGER.info(msg) except EntityNotFoundException: pass # if needed, upload ova and create temp vapp msg = f"Creating template '{template_name}' in catalog '{catalog_name}'" click.secho(msg, fg='yellow') LOGGER.info(msg) temp_vapp_exists = True try: vapp = VApp(client, resource=vdc.get_vapp(vapp_name)) msg = f"Found vApp '{vapp_name}'" click.secho(msg, fg='green') LOGGER.info(msg) except EntityNotFoundException: temp_vapp_exists = False # flag is used to hide previous try/except error if an error occurs below if not temp_vapp_exists: if catalog_item_exists(org, catalog_name, ova_name): msg = f"Found ova file '{ova_name}' in catalog '{catalog_name}'" click.secho(msg, fg='green') LOGGER.info(msg) else: # download/upload files to catalog if necessary ova_filepath = f"cse_cache/{ova_name}" download_file(template_config['source_ova'], ova_filepath, sha256=template_config['sha256_ova'], logger=LOGGER) upload_ova_to_catalog(client, catalog_name, ova_filepath, org=org, logger=LOGGER) vapp = _create_temp_vapp(ctx, client, vdc, config, template_config, ssh_key) if no_capture: msg = f"'--no-capture' flag set. " \ f"Not capturing vApp '{vapp.name}' as a template" click.secho(msg, fg='yellow') LOGGER.info(msg) return # capture temp vapp as template msg = f"Creating template '{template_name}' from vApp '{vapp.name}'" click.secho(msg, fg='yellow') LOGGER.info(msg) capture_vapp_to_template(ctx, vapp, catalog_name, template_name, org=org, desc=template_config['description'], power_on=not template_config['cleanup']) msg = f"Created template '{template_name}' from vApp '{vapp_name}'" click.secho(msg, fg='green') LOGGER.info(msg) # delete temp vapp if template_config['cleanup']: msg = f"Deleting vApp '{vapp_name}'" click.secho(msg, fg='yellow') LOGGER.info(msg) task = vdc.delete_vapp(vapp_name, force=True) stdout(task, ctx=ctx) vdc.reload() msg = f"Deleted vApp '{vapp_name}'" click.secho(msg, fg='green') LOGGER.info(msg)
def install_cse(ctx, config_file_name='config.yaml', template_name='*', update=False, no_capture=False, ssh_key=None, amqp_install='prompt', ext_install='prompt'): """Handles logistics for CSE installation. Handles decision making for configuring AMQP exchange/settings, extension registration, catalog setup, and template creation. :param click.core.Context ctx: :param str config_file_name: config file name. :param str template_name: which templates to create/update. A value of '*' means to create/update all templates specified in config file. :param bool update: if True and templates already exist in vCD, overwrites existing templates. :param bool no_capture: if True, temporary vApp will not be captured or destroyed, so the user can ssh into and debug the VM. :param str ssh_key: public ssh key to place into template vApp(s). :param str amqp_install: 'prompt' asks the user if vCD AMQP should be configured. 'skip' does not configure vCD AMQP. 'config' configures vCD AMQP without asking the user. :param str ext_install: 'prompt' asks the user if CSE should be registered to vCD. 'skip' does not register CSE to vCD. 'config' registers CSE to vCD without asking the user. :raises AmqpError: if AMQP exchange could not be created. """ config = get_validated_config(config_file_name) configure_install_logger() msg = f"Installing CSE on vCloud Director using config file " \ f"'{config_file_name}'" click.secho(msg, fg='yellow') LOGGER.info(msg) client = None try: client = Client(config['vcd']['host'], api_version=config['vcd']['api_version'], verify_ssl_certs=config['vcd']['verify'], log_file=INSTALL_LOG_FILEPATH, log_headers=True, log_bodies=True) credentials = BasicLoginCredentials(config['vcd']['username'], SYSTEM_ORG_NAME, config['vcd']['password']) client.set_credentials(credentials) msg = f"Connected to vCD as system administrator: " \ f"{config['vcd']['host']}:{config['vcd']['port']}" click.secho(msg, fg='green') LOGGER.info(msg) # configure amqp amqp = config['amqp'] create_amqp_exchange(amqp['exchange'], amqp['host'], amqp['port'], amqp['vhost'], amqp['ssl'], amqp['username'], amqp['password']) if should_configure_amqp(client, amqp, amqp_install): configure_vcd_amqp(client, amqp['exchange'], amqp['host'], amqp['port'], amqp['prefix'], amqp['ssl_accept_all'], amqp['ssl'], amqp['vhost'], amqp['username'], amqp['password']) # register cse as extension to vCD if should_register_cse(client, ext_install): register_cse(client, amqp['routing_key'], amqp['exchange']) # set up cse catalog org = get_org(client, org_name=config['broker']['org']) create_and_share_catalog(org, config['broker']['catalog'], catalog_desc='CSE templates') # create, customize, capture VM templates for template in config['broker']['templates']: if template_name == '*' or template['name'] == template_name: create_template(ctx, client, config, template, update=update, no_capture=no_capture, ssh_key=ssh_key, org=org) except Exception: click.secho("CSE Installation Error. Check CSE install logs", fg='red') LOGGER.error("CSE Installation Error", exc_info=True) raise # TODO need installation relevant exceptions for rollback finally: if client is not None: client.logout()
def should_configure_amqp(client, amqp_config, amqp_install): """Decides if CSE installation should configure vCD AMQP settings. Returns False if config file AMQP settings are the same as vCD AMQP settings, or if the user declines configuration. :param pyvcloud.vcd.client.Client client: :param dict amqp_config: 'amqp' section of the config file :param str amqp_install: 'skip' skips vCD AMQP configuration, 'config' configures vCD AMQP settings without prompting user, 'prompt' asks user before configuring vCD AMQP settings. :return: boolean that signals whether we should configure AMQP settings. :rtype: bool """ if amqp_install == 'skip': msg = f"Skipping AMQP configuration. vCD and config file may have " \ f"different AMQP settings" click.secho(msg, fg='yellow') LOGGER.warning(msg) return False current_settings = to_dict(AmqpService(client).get_settings()) amqp = { 'AmqpExchange': amqp_config['exchange'], 'AmqpHost': amqp_config['host'], 'AmqpPort': str(amqp_config['port']), 'AmqpPrefix': amqp_config['prefix'], 'AmqpSslAcceptAll': str(amqp_config['ssl_accept_all']).lower(), 'AmqpUseSSL': str(amqp_config['ssl']).lower(), 'AmqpUsername': amqp_config['username'], 'AmqpVHost': amqp_config['vhost'] } diff_settings = [k for k, v in current_settings.items() if amqp[k] != v] if diff_settings: msg = 'current vCD AMQP setting(s):' click.secho(msg, fg='blue') LOGGER.info(msg) for setting in diff_settings: msg = f"{setting}: {current_settings[setting]}" click.echo(msg) LOGGER.info(msg) msg = '\nconfig file AMQP setting(s):' click.secho(msg, fg='blue') LOGGER.info(msg) for setting in diff_settings: msg = f"{setting}: {amqp[setting]}" click.echo(msg) LOGGER.info(msg) msg = '\nConfigure AMQP with the config file settings?' if amqp_install == 'prompt' and not click.confirm(msg): msg = f"Skipping AMQP configuration. vCD and config file may " \ f"have different AMQP settings" click.secho(msg, fg='yellow') LOGGER.warning(msg) return False return True msg = "vCD and config file AMQP settings are the same, " \ "skipping AMQP configuration" click.secho(msg, fg='green') LOGGER.info(msg) return False