def install_dependencies(config): num_of_retries = 5 sleep_interval_in_sec = 5 working_folder = config['AgentWorkingFolder'] install_dependencies_path = os.path.join( working_folder, Constants.install_dependencies_script) for i in range(num_of_retries): install_dependencies_proc = subprocess.Popen(install_dependencies_path, stdout=subprocess.PIPE, stderr=subprocess.PIPE) install_out, install_err = install_dependencies_proc.communicate() return_code = install_dependencies_proc.returncode handler_utility.log( 'Install dependencies process exit code : {0}'.format(return_code)) handler_utility.log('stdout : {0}'.format(install_out)) handler_utility.log('srderr : {0}'.format(install_err)) if (return_code == 0): handler_utility.log('Dependencies installed successfully.') break else: error_message = 'Installing dependencies failed with error : {0}'.format( install_err) if (i == (num_of_retries - 1)): raise Exception(error_message) else: handler_utility.log(error_message) sleep(sleep_interval_in_sec) handler_utility.add_handler_sub_status( Util.HandlerSubStatus('InstalledDependencies'))
def enable(): handler_utility.set_handler_status(Util.HandlerStatus('Installing')) pre_validation_checks() config = get_configuration_from_settings() if (config.get('IsPipelinesAgent') != None): enable_pipelines_agent(config) return compare_sequence_number() settings_are_same = test_extension_settings_are_same_as_disabled_version() if (settings_are_same): handler_utility.log("Skipping extension enable.") handler_utility.add_handler_sub_status( Util.HandlerSubStatus( 'SkippingEnableSameSettingsAsDisabledVersion')) else: validate_inputs(config) ConfigureDeploymentAgent.set_logger(handler_utility.log) DownloadDeploymentAgent.set_logger(handler_utility.log) execute_agent_pre_check(config) remove_existing_agent_if_required(config) download_agent_if_required(config) configure_agent_if_required(config) handler_utility.set_handler_status(Util.HandlerStatus('Installed')) add_agent_tags(config) handler_utility.log('Extension is enabled.') handler_utility.set_handler_status(Util.HandlerStatus( 'Enabled', 'success')) set_last_sequence_number() handler_utility.log('Removing disable markup file..') remove_extension_disabled_markup()
def remove_existing_agent(config): try: handler_utility.log('Agent removal started') try: ConfigureDeploymentAgent.remove_existing_agent( config['AgentWorkingFolder']) handler_utility.add_handler_sub_status( Util.HandlerSubStatus('RemovedAgent')) if (os.access(config['AgentWorkingFolder'], os.F_OK)): DownloadDeploymentAgent.clean_agent_folder( config['AgentWorkingFolder']) else: raise Exception( 'Cannot cleanup the agent working folder. Access not granted' ) except Exception as e: handler_utility.log('An unexpected error occured: {0}'.format( getattr(e, 'message'))) raise e ConfigureDeploymentAgent.setting_params = {} except Exception as e: set_error_status_and_error_exit( e, RMExtensionStatus.rm_extension_status['Uninstalling'] ['operationName'], 7)
def download_agent_if_required(config): global configured_agent_exists if (configured_agent_exists == False): get_agent(config) else: handler_utility.log('Skipping agent download as agent already exists.') handler_utility.add_handler_sub_status( Util.HandlerSubStatus('SkippingDownloadDeploymentAgent'))
def execute_agent_pre_check(config): global configured_agent_exists, agent_configuration_required try: handler_utility.add_handler_sub_status( Util.HandlerSubStatus('PreCheckingDeploymentAgent')) configured_agent_exists = ConfigureDeploymentAgent.is_agent_configured( config['AgentWorkingFolder']) if (configured_agent_exists == True): agent_configuration_required = test_agent_configuration_required( config) handler_utility.add_handler_sub_status( Util.HandlerSubStatus('PreCheckedDeploymentAgent')) except Exception as e: set_error_status_and_error_exit( e, RMExtensionStatus.rm_extension_status['PreCheckingDeploymentAgent'] ['operationName'], 3)
def register_agent(config): global configured_agent_exists try: handler_utility.add_handler_sub_status( Util.HandlerSubStatus('ConfiguringDeploymentAgent')) handler_utility.log('Configuring agent...') ConfigureDeploymentAgent.configure_agent(config['VSTSUrl'], config['PATToken'], config['TeamProject'], \ config['DeploymentGroup'], config['ConfigureAgentAsUserName'], config['AgentName'], config['AgentWorkingFolder']) handler_utility.log('Agent configured successfully') handler_utility.add_handler_sub_status( Util.HandlerSubStatus('ConfiguredDeploymentAgent')) except Exception as e: set_error_status_and_error_exit( e, RMExtensionStatus.rm_extension_status['ConfiguringDeploymentAgent'] ['operationName'], 6)
def enable_pipelines_agent(config): try: handler_utility.add_handler_sub_status( Util.HandlerSubStatus('DownloadPipelinesAgent')) agentFolder = config["AgentFolder"] # download the agent tar file downloadUrl = config["AgentDownloadUrl"] agentFile = os.path.join(agentFolder, os.path.basename(downloadUrl)) urllib.urlretrieve(downloadUrl, agentFile) # download the enable script downloadUrl = config["EnableScriptDownloadUrl"] enableFile = os.path.join(agentFolder, os.path.basename(downloadUrl)) urllib.urlretrieve(downloadUrl, enableFile) except Exception as e: set_error_status_and_error_exit( e, RMExtensionStatus.rm_extension_status[ 'DownloadPipelinesAgentError']['operationName'], getattr(e, 'message')) try: # run the enable script handler_utility.add_handler_sub_status( Util.HandlerSubStatus('EnablePipelinesAgent')) enableParameters = config["EnableScriptParameters"] enableProcess = subprocess.Popen( ['/bin/bash', '-c', enableFile, enableParameters]) # wait for the script to complete installProcess.communicate() except Exception as e: set_error_status_and_error_exit( e, RMExtensionStatus.rm_extension_status['EnablePipelinesAgentError'] ['operationName'], getattr(e, 'message')) handler_utility.add_handler_sub_status( Util.HandlerSubStatus('EnablePipelinesAgentSuccess')) handler_utility.set_handler_status(Util.HandlerStatus('Enabled')) handler_utility.log('Pipelines Agent is enabled.')
def configure_agent_if_required(config): if (agent_configuration_required): install_dependencies(config) register_agent(config) else: handler_utility.log( 'Agent is already configured with given set of parameters. Skipping agent configuration.' ) handler_utility.add_handler_sub_status( Util.HandlerSubStatus('SkippingAgentConfiguration'))
def get_agent(config): try: handler_utility.add_handler_sub_status( Util.HandlerSubStatus('DownloadingDeploymentAgent')) handler_utility.log( 'Invoking function to download Deployment agent package...') DownloadDeploymentAgent.download_deployment_agent( config['VSTSUrl'], config['PATToken'], config['AgentWorkingFolder']) handler_utility.log('Agent package downloaded and extracted') handler_utility.add_handler_sub_status( Util.HandlerSubStatus('DownloadedDeploymentAgent')) except Exception as e: set_error_status_and_error_exit( e, RMExtensionStatus.rm_extension_status['DownloadingDeploymentAgent'] ['operationName'], 5)
def add_agent_tags(config): try: handler_utility.add_handler_sub_status( Util.HandlerSubStatus('AddingAgentTags')) if (config['Tags'] != None and len(config['Tags']) > 0): handler_utility.log('Adding tags to configured agent - {0}'.format( str(config['Tags']))) tags_string = json.dumps(config['Tags'], ensure_ascii=False) ConfigureDeploymentAgent.add_agent_tags(config['VSTSUrl'], config['TeamProject'], \ config['PATToken'], config['AgentWorkingFolder'], tags_string) handler_utility.add_handler_sub_status( Util.HandlerSubStatus('AgentTagsAdded')) else: handler_utility.log('No tags provided for agent') except Exception as e: set_error_status_and_error_exit( e, RMExtensionStatus.rm_extension_status['AddingAgentTags'] ['operationName'], 8)
def pre_validation_checks(): try: validate_os() check_python_version() check_systemd_exists() except Exception as e: set_error_status_and_error_exit( e, RMExtensionStatus.rm_extension_status['PreValidationCheck'] ['operationName'], getattr(e, 'Code')) handler_utility.add_handler_sub_status( Util.HandlerSubStatus('PreValidationCheckSuccess'))
def disable(): ConfigureDeploymentAgent.set_logger(handler_utility.log) config = get_configuration_from_settings() if (config.get('IsPipelinesAgent') != None): return handler_utility.log('Disable command is no-op for agent') handler_utility.log( 'Disabling extension handler. Creating a markup file..') set_extension_disabled_markup() handler_utility.add_handler_sub_status(Util.HandlerSubStatus('Disabled')) handler_utility.set_handler_status( Util.HandlerStatus('Disabled', 'success'))
def compare_sequence_number(): try: sequence_number = int(handler_utility._context._seq_no) last_sequence_number = get_last_sequence_number() if ((sequence_number == last_sequence_number) and not (test_extension_disabled_markup())): handler_utility.log( RMExtensionStatus.rm_extension_status['SkippedInstallation'] ['Message']) handler_utility.log( 'Skipping enable since seq numbers match. Seq number: {0}.'. format(sequence_number)) handler_utility.add_handler_sub_status( Util.HandlerSubStatus('SkippedInstallation')) handler_utility.set_handler_status( Util.HandlerStatus('Enabled', 'success')) exit_with_code(0) except Exception as e: handler_utility.log('Sequence number check failed: {0}.'.format( getattr(e, 'message')))
def enable_pipelines_agent(config): try: handler_utility.log('Enable Pipelines Agent') handler_utility.add_handler_sub_status( Util.HandlerSubStatus('DownloadPipelinesAgent')) agentFolder = config["AgentFolder"] handler_utility.log(agentFolder) if (not os.path.isdir(agentFolder)): handler_utility.log('Agent folder does not exist. Creating it.') os.makedirs(agentFolder, 0o777) # download the agent tar file handler_utility.add_handler_sub_status( Util.HandlerSubStatus('DownloadPipelinesZip')) handler_utility.log('Download Pipelines Zip') downloadUrl = config["AgentDownloadUrl"] handler_utility.log(downloadUrl) filename = os.path.basename(downloadUrl) agentFile = os.path.join(agentFolder, filename) urllib.urlretrieve(downloadUrl, agentFile) # download the enable script handler_utility.add_handler_sub_status( Util.HandlerSubStatus('DownloadPipelinesScript')) handler_utility.log('Download Pipelines Script') downloadUrl = config["EnableScriptDownloadUrl"] handler_utility.log(downloadUrl) filename = os.path.basename(downloadUrl) enableFile = os.path.join(agentFolder, filename) urllib.urlretrieve(downloadUrl, enableFile) except Exception as e: handler_utility.log(getattr(e, 'message')) handler_utility.log(e) set_error_status_and_error_exit( e, RMExtensionStatus.rm_extension_status[ 'DownloadPipelinesAgentError']['operationName'], getattr(e, 'message')) return try: # grant executable access to the script os.chmod(enableFile, 0o777) # run the enable script handler_utility.add_handler_sub_status( Util.HandlerSubStatus('EnablePipelinesAgent')) handler_utility.log('Run Pipelines Script') handler_utility.log(enableFile) enableParameters = config["EnableScriptParameters"] # run the script and wait for it to complete handler_utility.log("running script") argList = ['/bin/bash', enableFile] + shlex.split(enableParameters) enableProcess = subprocess.Popen(argList) enableProcess.communicate() except Exception as e: handler_utility.log(getattr(e, 'message')) handler_utility.log(e) set_error_status_and_error_exit( e, RMExtensionStatus.rm_extension_status['EnablePipelinesAgentError'] ['operationName'], getattr(e, 'message')) return handler_utility.add_handler_sub_status( Util.HandlerSubStatus('EnablePipelinesAgentSuccess')) handler_utility.set_handler_status(Util.HandlerStatus( 'Enabled', 'success')) handler_utility.log('Pipelines Agent is enabled.')
def get_configuration_from_settings(): try: public_settings = handler_utility.get_public_settings() if (public_settings == None): public_settings = {} handler_utility.verify_public_settings_is_dict(public_settings) protected_settings = handler_utility.get_protected_settings() if (protected_settings == None): protected_settings = {} handler_utility.log("get_configuration_from_settings") #################################################################################################################################### #################################################################################################################################### # If this is a pipelines agent, read the settings and return quickly # Note that the pipelines settings come over as camelCase if (public_settings.has_key('isPipelinesAgent')): handler_utility.log("Is Pipelines Agent") # read pipelines agent settings agentDownloadUrl = public_settings['agentDownloadUrl'] handler_utility.verify_input_not_null('agentDownloadUrl', agentDownloadUrl) agentFolder = public_settings['agentFolder'] handler_utility.verify_input_not_null('agentFolder', agentFolder) enableScriptDownloadUrl = public_settings[ 'enableScriptDownloadUrl'] handler_utility.verify_input_not_null('enableScriptDownloadUrl', enableScriptDownloadUrl) # for testing, first try to get the script parameters from the public settings # in production they will be in the protected settings handler_utility.log("looking for enableScriptParameters") if (protected_settings.has_key('enableScriptParameters')): handler_utility.log("protected enableScriptParameters") enableScriptParameters = protected_settings[ 'enableScriptParameters'] if (public_settings.has_key('enableScriptParameters')): handler_utility.log("public enableScriptParameters") enableScriptParameters = public_settings[ 'enableScriptParameters'] handler_utility.log("validating enableScriptParameters") handler_utility.verify_input_not_null('enableScriptParameters', enableScriptParameters) return { 'IsPipelinesAgent': 'true', 'AgentDownloadUrl': agentDownloadUrl, 'AgentFolder': agentFolder, 'EnableScriptDownloadUrl': enableScriptDownloadUrl, 'EnableScriptParameters': enableScriptParameters } #################################################################################################################################### #################################################################################################################################### # continue with deployment agent settings handler_utility.log("Is Deployment Agent") pat_token = '' if ((protected_settings.__class__.__name__ == 'dict') and protected_settings.has_key('PATToken')): pat_token = protected_settings['PATToken'] if ((pat_token == '') and (public_settings.has_key('PATToken'))): pat_token = public_settings['PATToken'] vsts_account_url = '' if (public_settings.has_key('AzureDevOpsOrganizationUrl')): vsts_account_url = public_settings[ 'AzureDevOpsOrganizationUrl'].strip('/') elif (public_settings.has_key('VSTSAccountUrl')): vsts_account_url = public_settings['VSTSAccountUrl'].strip('/') elif (public_settings.has_key('VSTSAccountName')): vsts_account_url = public_settings['VSTSAccountName'].strip('/') handler_utility.verify_input_not_null('AzureDevOpsOrganizationUrl', vsts_account_url) vsts_url = vsts_account_url vsts_url = parse_account_name(vsts_account_url, pat_token) handler_utility.log( 'Azure DevOps Organization Url : {0}'.format(vsts_url)) team_project_name = '' if (public_settings.has_key('TeamProject')): team_project_name = public_settings['TeamProject'] handler_utility.verify_input_not_null('TeamProject', team_project_name) handler_utility.log('Team Project : {0}'.format(team_project_name)) deployment_group_name = '' if (public_settings.has_key('DeploymentGroup')): deployment_group_name = public_settings['DeploymentGroup'] elif (public_settings.has_key('MachineGroup')): deployment_group_name = public_settings['MachineGroup'] handler_utility.verify_input_not_null('DeploymentGroup', deployment_group_name) handler_utility.log( 'Deployment Group : {0}'.format(deployment_group_name)) agent_name = '' if (public_settings.has_key('AgentName')): agent_name = public_settings['AgentName'] handler_utility.log('Agent Name : {0}'.format(agent_name)) tags_input = [] if (public_settings.has_key('Tags')): tags_input = public_settings['Tags'] handler_utility.log('Tags : {0}'.format(tags_input)) tags = format_tags_input(tags_input) configure_agent_as_username = '' if (public_settings.has_key('UserName')): configure_agent_as_username = public_settings['UserName'] handler_utility.log('Done reading config settings from file...') handler_utility.add_handler_sub_status( Util.HandlerSubStatus('SuccessfullyReadSettings')) return { 'VSTSUrl': vsts_url, 'PATToken': pat_token, 'TeamProject': team_project_name, 'DeploymentGroup': deployment_group_name, 'AgentName': agent_name, 'Tags': tags, 'AgentWorkingFolder': Constants.agent_working_folder, 'ConfigureAgentAsUserName': configure_agent_as_username } except Exception as e: set_error_status_and_error_exit( e, RMExtensionStatus.rm_extension_status['ReadingSettings'] ['operationName'], RMExtensionStatus.rm_extension_status['InputConfigurationError'])
def validate_inputs(config): try: invalid_pat_error_message = "Please make sure that the Personal Access Token entered is valid and has 'Deployment Groups - Read & manage' scope." inputs_validation_error_code = RMExtensionStatus.rm_extension_status[ 'InputConfigurationError'] unexpected_error_message = "Some unexpected error occured. Status code : {0}" error_message_initial_part = "Could not verify that the deployment group '" + config[ 'DeploymentGroup'] + "' exists in the project '" + config[ 'TeamProject'] + "' in the specified organization '" + config[ 'VSTSUrl'] + "'. Status: {0} Error: {1}. " # Verify the deployment group exists and the PAT has the required(Deployment Groups - Read & manage) scope # This is the first validation http call, so using Invoke-WebRequest instead of Invoke-RestMethod, because if the PAT provided is not a token at all(not even an unauthorized one) and some random value, then the call # would redirect to sign in page and not throw an exception. So, to handle this case. specific_error_message = "" get_deployment_group_url = "{0}/{1}/_apis/distributedtask/deploymentgroups?name={2}&api-version={3}".format( config['VSTSUrl'], quote(config['TeamProject']), quote(config['DeploymentGroup']), Constants.projectAPIVersion) handler_utility.log( "Get deployment group url - {0}".format(get_deployment_group_url)) response = Util.make_http_call(get_deployment_group_url, 'GET', None, None, config['PATToken']) if (response.status != Constants.HTTP_OK): if (response.status == Constants.HTTP_FOUND): specific_error_message = invalid_pat_error_message elif (response.status == Constants.HTTP_UNAUTHORIZED): specific_error_message = invalid_pat_error_message elif (response.status == Constants.HTTP_FORBIDDEN): specific_error_message = "Please ensure that the user has 'View project-level information' permissions on the project '{0}'.".format( config['TeamProject']) elif (response.status == Constants.HTTP_NOTFOUND): specific_error_message = "Please make sure that you enter the correct organization name and verify that the project exists in the organization." else: specific_error_message = unexpected_error_message.format( response.status) inputs_validation_error_code = RMExtensionStatus.rm_extension_status[ 'GenericError'] error_message = error_message_initial_part.format( response.status, specific_error_message) raise RMExtensionStatus.new_handler_terminating_error( inputs_validation_error_code, error_message) deployment_group_data = json.loads(response.read()) if (('value' not in deployment_group_data) or len(deployment_group_data['value']) == 0): specific_error_message = "Please make sure that the deployment group {0} exists in the project {1}, and the user has 'Manage' permissions on the deployment group.".format( config['DeploymentGroup'], config['TeamProject']) raise RMExtensionStatus.new_handler_terminating_error( inputs_validation_error_code, error_message_initial_part.format(response.status, specific_error_message)) deployment_group_id = deployment_group_data['value'][0]['id'] handler_utility.log( "Validated that the deployment group {0} exists".format( config['DeploymentGroup'])) headers = {} headers['Content-Type'] = 'application/json' body = "{'name': '" + config['DeploymentGroup'] + "'}" patch_deployment_group_url = "{0}/{1}/_apis/distributedtask/deploymentgroups/{2}?api-version={3}".format( config['VSTSUrl'], quote(config['TeamProject']), deployment_group_id, Constants.projectAPIVersion) handler_utility.log("Patch deployment group url - {0}".format( patch_deployment_group_url)) response = Util.make_http_call(patch_deployment_group_url, 'PATCH', body, headers, config['PATToken']) if (response.status != Constants.HTTP_OK): if (response.status == Constants.HTTP_FORBIDDEN): specific_error_message = "Please ensure that the user has 'Manage' permissions on the deployment group {0}".format( config['DeploymentGroup']) else: specific_error_message = unexpected_error_message.format( str(response.status)) inputs_validation_error_code = RMExtensionStatus.rm_extension_status[ 'GenericError'] raise RMExtensionStatus.new_handler_terminating_error( inputs_validation_error_code, error_message_initial_part.format( response.status, response.reason) + specific_error_message) handler_utility.log( "Validated that the user has 'Manage' permissions on the deployment group '{0}'" .format(config['DeploymentGroup'])) handler_utility.log("Done validating inputs...") handler_utility.add_handler_sub_status( Util.HandlerSubStatus('SuccessfullyValidatedInputs')) except Exception as e: set_error_status_and_error_exit( e, RMExtensionStatus.rm_extension_status['ValidatingInputs'] ['operationName'], getattr(e, 'Code'))