azure-linux-extensions/DSC/dsc.py

1197 строки
55 KiB
Python

#!/usr/bin/env python
#
# DSC extension
#
# Copyright 2015 Microsoft Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import os.path
import re
import subprocess
import sys
import traceback
try:
from urllib.parse import urlparse, urlencode
from urllib.request import urlopen, Request
from urllib.error import HTTPError
except ImportError:
from urlparse import urlparse
from urllib import urlencode
from urllib2 import urlopen, Request, HTTPError
import time
import platform
import json
import datetime
import serializerfactory
import httpclient
import urllib2httpclient
import urllib3httpclient
import httpclientfactory
from azure.storage import BlobService
from Utils.WAAgentUtil import waagent
import Utils.HandlerUtil as Util
# Define global variables
ExtensionName = 'Microsoft.OSTCExtensions.DSCForLinux'
ExtensionShortName = 'DSCForLinux'
DownloadDirectory = 'download'
omi_package_prefix = 'packages/omi-1.7.3-0.ssl_'
dsc_package_prefix = 'packages/dsc-1.2.4-0.ssl_'
omi_major_version = 1
omi_minor_version = 7
omi_build = 3
omi_release = 0
dsc_major_version = 1
dsc_minor_version = 2
dsc_build = 4
dsc_release = 0
package_pattern = '(\d+).(\d+).(\d+).(\d+)'
nodeid_path = '/etc/opt/omi/conf/dsc/agentid'
date_time_format = "%Y-%m-%dT%H:%M:%SZ"
extension_handler_version = "3.0.0.6"
python_command = 'python3' if sys.version_info >= (3,0) else 'python'
dsc_script_path = '/opt/microsoft/dsc/Scripts/python3' if sys.version_info >= (3,0) else '/opt/microsoft/dsc/Scripts'
space_string = " "
# Error codes
UnsupportedDistro = 51 #excludes from SLA
DPKGLockedErrorCode = 51 #excludes from SLA
# DSC-specific Operation
class Operation:
Download = "Download"
ApplyMof = "ApplyMof"
ApplyMetaMof = "ApplyMetaMof"
InstallModule = "InstallModule"
RemoveModule = "RemoveModule"
Register = "Register"
Enable = "Enable"
class DistroCategory:
debian = 1
redhat = 2
suse = 3
class Mode:
push = "push"
pull = "pull"
install = "install"
remove = "remove"
register = "register"
def main():
waagent.LoggerInit('/var/log/waagent.log', '/dev/stdout')
waagent.Log("%s started to handle." % (ExtensionShortName))
global hutil
hutil = Util.HandlerUtility(waagent.Log, waagent.Error)
hutil.try_parse_context()
global public_settings
public_settings = hutil.get_public_settings()
if not public_settings:
waagent.AddExtensionEvent(name=ExtensionShortName, op='MainInProgress', isSuccess=True,
message="Public settings are NOT provided.")
public_settings = {}
global protected_settings
protected_settings = hutil.get_protected_settings()
if not protected_settings:
waagent.AddExtensionEvent(name=ExtensionShortName, op='MainInProgress', isSuccess=True,
message="protected settings are NOT provided.")
protected_settings = {}
global distro_category
vm_supported, vm_dist, vm_ver = check_supported_OS()
distro_category = get_distro_category(vm_dist.lower(), vm_ver.lower())
for a in sys.argv[1:]:
if re.match("^([-/]*)(disable)", a):
disable()
elif re.match("^([-/]*)(uninstall)", a):
uninstall()
elif re.match("^([-/]*)(install)", a):
install()
elif re.match("^([-/]*)(enable)", a):
enable()
elif re.match("^([-/]*)(update)", a):
update()
def get_distro_category(distro_name,distro_version):
if distro_name.startswith('ubuntu') or (distro_name.startswith('debian')):
return DistroCategory.debian
elif distro_name.startswith('centos') or distro_name.startswith('redhat') or distro_name.startswith('oracle') or distro_name.startswith('red hat'):
return DistroCategory.redhat
elif distro_name.startswith('suse') or distro_name.startswith('sles'):
return DistroCategory.suse
waagent.AddExtensionEvent(name=ExtensionShortName, op='InstallInProgress', isSuccess=True, message="Unsupported distro :" + distro_name + "; distro_version: " + distro_version)
hutil.do_exit(UnsupportedDistro, 'Install', 'error', str(UnsupportedDistro), distro_name + 'is not supported.')
def check_supported_OS():
"""
Checks if the VM this extension is running on is supported by DSC
Returns for platform.linux_distribution() vary widely in format, such as
'7.3.1611' returned for a VM with CentOS 7, so the first provided
digits must match.
All other distros not supported will get error code 51
"""
supported_dists = {'redhat' : ['7', '8'], # CentOS
'centos' : ['7', '8'], # CentOS
'red hat' : ['7', '8'], # Redhat
'debian' : ['8', '9', '10'], # Debian
'ubuntu' : ['14.04', '16.04', '18.04', '20.04'], # Ubuntu
'oracle' : ['7'], # Oracle
'suse' : ['12', '15'], #SLES
'sles' : ['12', '15']
}
vm_dist, vm_ver, vm_supported = '', '', False
try:
vm_dist, vm_ver, vm_id = platform.linux_distribution()
except AttributeError:
try:
vm_dist, vm_ver, vm_id = platform.dist()
except:
waagent.Log("Falling back to /etc/os-release distribution parsing")
# Fallback if either of the above fail; on some (especially newer)
# distros, linux_distribution() and dist() are unreliable or deprecated
if not vm_dist and not vm_ver:
try:
with open('/etc/os-release', 'r') as fp:
for line in fp:
if line.startswith('ID='):
vm_dist = line.split('=')[1]
vm_dist = vm_dist.split('-')[0]
vm_dist = vm_dist.replace('\"', '').replace('\n', '')
elif line.startswith('VERSION_ID='):
vm_ver = line.split('=')[1]
vm_ver = vm_ver.replace('\"', '').replace('\n', '')
except:
waagent.Log('Indeterminate operating system')
return vm_supported, 'Indeterminate operating system', ''
# Find this VM distribution in the supported list
for supported_dist in supported_dists.keys():
if vm_dist.lower().startswith(supported_dist):
# Check if this VM distribution version is supported
vm_ver_split = vm_ver.split('.')
for supported_ver in supported_dists[supported_dist]:
supported_ver_split = supported_ver.split('.')
# If vm_ver is at least as precise (at least as many digits) as
# supported_ver and matches all the supported_ver digits, then
# this VM is supported
vm_ver_match = True
for idx, supported_ver_num in enumerate(supported_ver_split):
try:
supported_ver_num = int(supported_ver_num)
vm_ver_num = int(vm_ver_split[idx])
except IndexError:
vm_ver_match = False
break
if vm_ver_num is not supported_ver_num:
vm_ver_match = False
break
if vm_ver_match:
vm_supported = True
break
if not vm_supported:
waagent.AddExtensionEvent(name=ExtensionShortName, op='InstallInProgress', isSuccess=True, message="Unsupported OS :" + vm_dist + "; distro_version: " + vm_ver)
hutil.do_exit(UnsupportedDistro, 'Install', 'error', str(UnsupportedDistro), vm_dist + "; distro_version: " + vm_ver + ' is not supported.')
return vm_supported, vm_dist, vm_ver
def install():
hutil.do_parse_context('Install')
try:
waagent.AddExtensionEvent(name=ExtensionShortName, op='InstallInProgress', isSuccess=True,
message="Installing DSCForLinux extension")
remove_old_dsc_packages()
install_dsc_packages()
waagent.AddExtensionEvent(name=ExtensionShortName, op='InstallInProgress', isSuccess=True,
message="successfully installed DSCForLinux extension")
hutil.do_exit(0, 'Install', 'success', '0', 'Install Succeeded.')
except Exception as e:
waagent.AddExtensionEvent(name=ExtensionShortName, op='InstallInProgress', isSuccess=True,
message="failed to install DSC extension with error: {0} and stacktrace: {1}".format(
str(e), traceback.format_exc()))
hutil.error(
"Failed to install DSC extension with error: %s, stack trace: %s" % (str(e), traceback.format_exc()))
hutil.do_exit(1, 'Install', 'error', '1', 'Install Failed.')
def enable():
hutil.do_parse_context('Enable')
hutil.exit_if_enabled()
try:
start_omiservice()
mode = get_config('Mode')
if mode == '':
mode = get_config('ExtensionAction')
waagent.AddExtensionEvent(name=ExtensionShortName, op='EnableInProgress', isSuccess=True,
message="Enabling the DSC extension - mode/ExtensionAction: " + mode)
if mode == '':
mode = Mode.push
else:
mode = mode.lower()
if not hasattr(Mode, mode):
waagent.AddExtensionEvent(name=ExtensionShortName,
op=Operation.Enable,
isSuccess=True,
message="(03001)Argument error, invalid ExtensionAction/mode.")
hutil.do_exit(51, 'Enable', 'error', '51', 'Enable failed, unknown ExtensionAction/mode: ' + mode)
if mode == Mode.remove:
remove_module()
elif mode == Mode.register:
registration_key = get_config('RegistrationKey')
registation_url = get_config('RegistrationUrl')
# Optional
node_configuration_name = get_config('NodeConfigurationName')
refresh_freq = get_config('RefreshFrequencyMins')
configuration_mode_freq = get_config('ConfigurationModeFrequencyMins')
configuration_mode = get_config('ConfigurationMode')
exit_code, err_msg = register_automation(registration_key, registation_url, node_configuration_name,
refresh_freq, configuration_mode_freq, configuration_mode.lower())
if exit_code != 0:
hutil.do_exit(exit_code, 'Enable', 'error', str(exit_code), err_msg)
extension_status_event = "ExtensionRegistration"
response = send_heart_beat_msg_to_agent_service(extension_status_event)
status_file_path, agent_id, vm_uuid = get_status_message_details()
update_statusfile(status_file_path, agent_id, vm_uuid, response)
sys.exit(0)
else:
file_path = download_file()
if mode == Mode.pull:
current_config = apply_dsc_meta_configuration(file_path)
elif mode == Mode.push:
current_config = apply_dsc_configuration(file_path)
else:
install_module(file_path)
if mode == Mode.push or mode == Mode.pull:
if check_dsc_configuration(current_config):
if mode == Mode.push:
waagent.AddExtensionEvent(name=ExtensionShortName,
op=Operation.ApplyMof,
isSuccess=True,
message="(03104)Succeeded to apply MOF configuration through Push Mode")
else:
waagent.AddExtensionEvent(name=ExtensionShortName,
op=Operation.ApplyMetaMof,
isSuccess=True,
message="(03106)Succeeded to apply meta MOF configuration through Pull Mode")
extension_status_event = "ExtensionRegistration"
response = send_heart_beat_msg_to_agent_service(extension_status_event)
status_file_path, agent_id, vm_uuid = get_status_message_details()
update_statusfile(status_file_path, agent_id, vm_uuid, response)
sys.exit(0)
else:
if mode == Mode.push:
waagent.AddExtensionEvent(name=ExtensionShortName,
op=Operation.ApplyMof,
isSuccess=False,
message="(03105)Failed to apply MOF configuration through Push Mode")
else:
waagent.AddExtensionEvent(name=ExtensionShortName,
op=Operation.ApplyMetaMof,
isSuccess=False,
message="(03107)Failed to apply meta MOF configuration through Pull Mode")
hutil.do_exit(1, 'Enable', 'error', '1', 'Enable failed. ' + current_config)
hutil.do_exit(0, 'Enable', 'success', '0', 'Enable Succeeded')
except Exception as e:
waagent.AddExtensionEvent(name=ExtensionShortName, op='EnableInProgress', isSuccess=True,
message="Enable failed with the error: {0}, stacktrace: {1} ".format(str(e),
traceback.format_exc()))
hutil.error('Failed to enable the extension with error: %s, stack trace: %s' % (str(e), traceback.format_exc()))
hutil.do_exit(1, 'Enable', 'error', '1', 'Enable failed: {0}'.format(e))
def send_heart_beat_msg_to_agent_service(status_event_type):
response = None
try:
retry_count = 0
canRetry = True
while retry_count <= 5 and canRetry:
waagent.AddExtensionEvent(name=ExtensionShortName, op='HeartBeatInProgress', isSuccess=True,
message="In send_heart_beat_msg_to_agent_service method")
code, output, stderr = run_cmd( python_command + space_string + dsc_script_path + "/GetDscLocalConfigurationManager.py")
if code == 0 and "RefreshMode=Pull" in output:
waagent.AddExtensionEvent(name=ExtensionShortName, op='HeartBeatInProgress', isSuccess=True,
message="sends heartbeat message in pullmode")
m = re.search("ServerURL=([^\n]+)", output)
if not m:
return
registration_url = m.group(1)
agent_id = get_nodeid(nodeid_path)
node_extended_properties_url = registration_url + "/Nodes(AgentId='" + agent_id + "')/ExtendedProperties"
waagent.AddExtensionEvent(name=ExtensionShortName, op='HeartBeatInProgress', isSuccess=True,
message="Url is " + node_extended_properties_url)
headers = {'Content-Type': "application/json; charset=utf-8", 'Accept': "application/json",
"ProtocolVersion": "2.0"}
data = construct_node_extension_properties(output, status_event_type)
http_client_factory = httpclientfactory.HttpClientFactory("/etc/opt/omi/ssl/oaas.crt",
"/etc/opt/omi/ssl/oaas.key")
http_client = http_client_factory.create_http_client(sys.version_info)
response = http_client.post(node_extended_properties_url, headers=headers, data=data)
waagent.AddExtensionEvent(name=ExtensionShortName, op='HeartBeatInProgress', isSuccess=True,
message="response code is " + str(response.status_code))
if response.status_code >= 500 and response.status_code < 600:
canRetry = True
time.sleep(10)
else:
canRetry = False
retry_count += 1
except Exception as e:
waagent.AddExtensionEvent(name=ExtensionShortName, op='HeartBeatInProgress', isSuccess=True,
message="Failed to send heartbeat message to DSC agent service: {0}, stacktrace: {1} ".format(
str(e), traceback.format_exc()))
hutil.error('Failed to send heartbeat message to DSC agent service: %s, stack trace: %s' % (
str(e), traceback.format_exc()))
return response
def get_lcm_config_setting(setting_name, lcmconfig):
valuegroup = re.search(setting_name + "=([^\n]+)", lcmconfig)
if not valuegroup:
return ""
value = valuegroup.group(1)
return value
def construct_node_extension_properties(lcmconfig, status_event_type):
waagent.AddExtensionEvent(name=ExtensionShortName, op='HeartBeatInProgress', isSuccess=True,
message="Getting properties")
OMSCLOUD_ID = get_omscloudid()
vm_dist, vm_ver, vm_id = '', '', ''
try:
vm_dist, vm_ver, vm_id = platform.linux_distribution()
except AttributeError:
try:
vm_dist, vm_ver, vm_id = platform.dist()
except AttributeError:
waagent.Log("Falling back to /etc/os-release distribution parsing")
# Fallback if either of the above fail; on some (especially newer)
# distros, linux_distribution() and dist() are unreliable or deprecated
if not vm_dist and not vm_ver:
try:
with open('/etc/os-release', 'r') as fp:
for line in fp:
if line.startswith('ID='):
vm_dist = line.split('=')[1]
vm_dist = vm_dist.split('-')[0]
vm_dist = vm_dist.replace('\"', '').replace('\n', '')
elif line.startswith('VERSION_ID='):
vm_ver = line.split('=')[1]
vm_ver = vm_ver.replace('\"', '').replace('\n', '')
except:
waagent.Log('Indeterminate operating system')
vm_dist, vm_ver, vm_id = "Indeterminate operating system", "",""
if len(vm_ver.split('.')) == 1:
major_version = vm_ver.split('.')[0]
minor_version = 0
if len(vm_ver.split('.')) >= 2:
major_version = vm_ver.split('.')[0]
minor_version = vm_ver.split('.')[1]
VMUUID = get_vmuuid()
node_config_names = get_lcm_config_setting('ConfigurationNames', lcmconfig)
configuration_mode = get_lcm_config_setting("ConfigurationMode", lcmconfig)
configuration_mode_frequency = get_lcm_config_setting("ConfigurationModeFrequencyMins", lcmconfig)
refresh_frequency_mins = get_lcm_config_setting("RefreshFrequencyMins", lcmconfig)
reboot_node = get_lcm_config_setting("RebootNodeIfNeeded", lcmconfig)
action_after_reboot = get_lcm_config_setting("ActionAfterReboot", lcmconfig)
allow_module_overwrite = get_lcm_config_setting("AllowModuleOverwrite", lcmconfig)
waagent.AddExtensionEvent(name=ExtensionShortName, op='HeartBeatInProgress', isSuccess=True,
message="Constructing properties data")
properties_data = {
"OMSCloudId": OMSCLOUD_ID,
"TimeStamp": time.strftime(date_time_format, time.gmtime()),
"VMResourceId": "",
"ExtensionStatusEvent": status_event_type,
"ExtensionInformation": {
"Name": "Microsoft.OSTCExtensions.DSCForLinux",
"Version": extension_handler_version
},
"OSProfile": {
"Name": vm_dist,
"Type": "Linux",
"MinorVersion": minor_version,
"MajorVersion": major_version,
"VMUUID": VMUUID
},
"RegistrationMetaData": {
"NodeConfigurationName": node_config_names,
"ConfigurationMode": configuration_mode,
"ConfigurationModeFrequencyMins": configuration_mode_frequency,
"RefreshFrequencyMins": refresh_frequency_mins,
"RebootNodeIfNeeded": reboot_node,
"ActionAfterReboot": action_after_reboot,
"AllowModuleOverwrite": allow_module_overwrite
}
}
return properties_data
def uninstall():
hutil.do_parse_context('Uninstall')
try:
extension_status_event = "ExtensionUninstall"
send_heart_beat_msg_to_agent_service(extension_status_event)
hutil.do_exit(0, 'Uninstall', 'success', '0', 'Uninstall Succeeded')
except Exception as e:
waagent.AddExtensionEvent(name=ExtensionShortName, op='UninstallInProgress', isSuccess=False,
message='Failed to uninstall the extension with error: %s, stack trace: %s' % (
str(e), traceback.format_exc()))
hutil.error(
'Failed to uninstall the extension with error: %s, stack trace: %s' % (str(e), traceback.format_exc()))
hutil.do_exit(1, 'Uninstall', 'error', '1', 'Uninstall failed: {0}'.format(e))
def disable():
hutil.do_parse_context('Disable')
hutil.do_exit(0, 'Disable', 'success', '0', 'Disable Succeeded')
def update():
hutil.do_parse_context('Update')
try:
extension_status_event = "ExtensionUpgrade"
send_heart_beat_msg_to_agent_service(extension_status_event)
hutil.do_exit(0, 'Update', 'success', '0', 'Update Succeeded')
except Exception as e:
waagent.AddExtensionEvent(name=ExtensionShortName, op='UpdateInProgress', isSuccess=False,
message='Failed to update the extension with error: %s, stack trace: %s' % (
str(e), traceback.format_exc()))
hutil.error('Failed to update the extension with error: %s, stack trace: %s' % (str(e), traceback.format_exc()))
hutil.do_exit(1, 'Update', 'error', '1', 'Update failed: {0}'.format(e))
def run_cmd(cmd):
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, close_fds=True)
exit_code = proc.wait()
stdout, stderr = proc.communicate()
stdout = stdout.decode("ISO-8859-1") if isinstance(stdout, bytes) else stdout
stderr = stderr.decode("ISO-8859-1") if isinstance(stderr, bytes) else stderr
return exit_code, stdout, stderr
def run_dpkg_cmd_with_retry(cmd):
"""
Attempts to run the cmd - if it fails, checks to see if dpkg is locked by another
process, if so, it will sleep for 5 seconds and then try running the command again.
If dpkg is still locked, then it will return the DPKGLockedErrorCode which won't
count against our SLA numbers.
"""
exit_code, output, stderr = run_cmd(cmd)
if not exit_code == 0:
dpkg_locked = is_dpkg_locked(exit_code, stderr)
if dpkg_locked:
# Try one more time:
time.sleep(5)
exit_code, output, stderr = run_cmd(cmd)
dpkg_locked = is_dpkg_locked(exit_code, stderr)
if dpkg_locked:
exit_code = DPKGLockedErrorCode
return exit_code, output, stderr
def get_config(key):
if key in public_settings:
value = public_settings.get(key)
if value:
return str(value).strip()
if key in protected_settings:
value = protected_settings.get(key)
if value:
return str(value).strip()
return ''
def remove_old_dsc_packages():
waagent.AddExtensionEvent(name=ExtensionShortName, op='InstallInProgress', isSuccess=True,
message="Deleting DSC and omi packages")
if distro_category == DistroCategory.debian:
deb_remove_incomptible_dsc_package()
# remove the package installed by Linux DSC 1.0, in later versions the package name is changed to 'omi'
deb_remove_old_oms_package('omiserver', '1.0.8.2')
elif distro_category == DistroCategory.redhat or distro_category == DistroCategory.suse:
rpm_remove_incomptible_dsc_package()
# remove the package installed by Linux DSC 1.0, in later versions the package name is changed to 'omi'
rpm_remove_old_oms_package('omiserver', '1.0.8-2')
def deb_remove_incomptible_dsc_package():
version = deb_get_pkg_version('dsc')
if version is not None and is_incomptible_dsc_package(version):
deb_uninstall_package('dsc')
def is_incomptible_dsc_package(package_version):
version = re.match(package_pattern, package_version)
# uninstall DSC package if the version is 1.0.x because upgrading from 1.0 to 1.1 is broken
if version is not None and (int(version.group(1)) == 1 and int(version.group(2)) == 0):
return True
return False
def is_old_oms_server(package_name):
if package_name == 'omiserver':
return True
return False
def deb_remove_old_oms_package(package_name, version):
system_pkg_version = deb_get_pkg_version(package_name)
if system_pkg_version is not None and is_old_oms_server(package_name):
deb_uninstall_package(package_name)
def deb_get_pkg_version(package_name):
code, output, stderr = run_dpkg_cmd_with_retry('dpkg -s ' + package_name + ' | grep Version:')
if code == 0:
code, output, stderr = run_dpkg_cmd_with_retry("dpkg -s " + package_name + " | grep Version: | awk '{print $2}'")
if code == 0:
return output
def rpm_remove_incomptible_dsc_package():
code, version, stderr = run_cmd('rpm -q --queryformat "%{VERSION}.%{RELEASE}" dsc')
if code == 0 and is_incomptible_dsc_package(version):
rpm_uninstall_package('dsc')
def rpm_remove_old_oms_package(package_name, version):
if rpm_check_old_oms_package(package_name, version):
rpm_uninstall_package(package_name)
def rpm_check_old_oms_package(package_name, version):
code, output, stderr = run_cmd('rpm -q ' + package_name)
if code == 0 and is_old_oms_server(package_name):
return True
return False
def install_dsc_packages():
openssl_version = get_openssl_version()
omi_package_path = omi_package_prefix + openssl_version
dsc_package_path = dsc_package_prefix + openssl_version
compiler_mitigated_omi_flag = get_compiler_mitigated_omi_flag()
waagent.AddExtensionEvent(name=ExtensionShortName, op='InstallInProgress', isSuccess=True,
message="Installing omipackage version: " + omi_package_path + "; dsc package version: " + dsc_package_path)
if distro_category == DistroCategory.debian:
deb_install_pkg(omi_package_path + '.ulinux' + compiler_mitigated_omi_flag + '.x64.deb', 'omi', omi_major_version, omi_minor_version, omi_build,
omi_release, ' --force-confold --force-confdef --refuse-downgrade ')
deb_install_pkg(dsc_package_path + '.x64.deb', 'dsc', dsc_major_version, dsc_minor_version, dsc_build,
dsc_release, '')
elif distro_category == DistroCategory.redhat or distro_category == DistroCategory.suse:
rpm_install_pkg(omi_package_path + '.ulinux' + compiler_mitigated_omi_flag + '.x64.rpm', 'omi', omi_major_version, omi_minor_version, omi_build,
omi_release)
rpm_install_pkg(dsc_package_path + '.x64.rpm', 'dsc', dsc_major_version, dsc_minor_version, dsc_build,
dsc_release)
def get_compiler_mitigated_omi_flag():
vm_supported, vm_dist, vm_ver = check_supported_OS()
if is_compiler_mitigated_omi_supported(vm_dist.lower(), vm_ver.lower()):
return '.s'
return ''
def is_compiler_mitigated_omi_supported(dist_name, dist_version):
# Compiler-mitigated OMI is not supported in the following
# SLES 11
# To be enhanced if there are future distros not supporting compiler-mitigated OMI package
if dist_name.startswith('sles') and dist_version.startswith('11'):
return False
return True
def compare_pkg_version(system_package_version, major_version, minor_version, build, release):
version = re.match(package_pattern, system_package_version)
if version is not None and ((int(version.group(1)) > major_version) or (
int(version.group(1)) == major_version and int(version.group(2)) > minor_version) or (
int(version.group(1)) == major_version and int(
version.group(2)) == minor_version and int(version.group(3)) > build) or (
int(version.group(1)) == major_version and int(
version.group(2)) == minor_version and int(version.group(3)) == build and int(
version.group(4)) >= release)):
return 1
return 0
def rpm_check_pkg_exists(package_name, major_version, minor_version, build, release):
code, output, stderr = run_cmd('rpm -q --queryformat "%{VERSION}.%{RELEASE}" ' + package_name)
waagent.AddExtensionEvent(name=ExtensionShortName, op='InstallInProgress', isSuccess=True,
message="package name: " + package_name + "; existing package version:" + output)
hutil.log("package name: " + package_name + "; existing package version:" + output)
if code == 0:
return compare_pkg_version(output, major_version, minor_version, build, release)
def rpm_install_pkg(package_path, package_name, major_version, minor_version, build, release):
if rpm_check_pkg_exists(package_name, major_version, minor_version, build, release) == 1:
# package is already installed
return
else:
code, output, stderr = run_cmd('rpm -Uvh ' + package_path)
if code == 0:
hutil.log(package_name + ' is installed successfully')
else:
waagent.AddExtensionEvent(name=ExtensionShortName, op='InstallInProgress', isSuccess=True,
message="Failed to install RPM package :" + package_path)
raise Exception('Failed to install package {0}: stdout: {1}, stderr: {2}'.format(package_name, output, stderr))
def deb_install_pkg(package_path, package_name, major_version, minor_version, build, release, install_options):
version = deb_get_pkg_version(package_name)
if version is not None and compare_pkg_version(version, major_version, minor_version, build, release) == 1:
# package is already installed
hutil.log(package_name + ' version ' + version + ' is already installed')
waagent.AddExtensionEvent(name=ExtensionShortName, op='InstallInProgress', isSuccess=True,
message="dsc package with version: " + version + "is already installed.")
return
else:
cmd = 'dpkg -i ' + install_options + ' ' + package_path
code, output, stderr = run_dpkg_cmd_with_retry(cmd)
if code == 0:
hutil.log(package_name + ' version ' + str(major_version) + '.' + str(minor_version) + '.' + str(
build) + '.' + str(release) + ' is installed successfully')
elif code == DPKGLockedErrorCode:
hutil.do_exit(DPKGLockedErrorCode, 'Install', 'error', str(DPKGLockedErrorCode), 'Install failed because the package manager on the VM is currently locked. Please try installing again.')
else:
waagent.AddExtensionEvent(name=ExtensionShortName, op='InstallInProgress', isSuccess=False,
message="Failed to install debian package :" + package_path)
raise Exception('Failed to install package {0}: stdout: {1}, stderr: {2}'.format(package_name, output, stderr))
def install_package(package):
if distro_category == DistroCategory.debian:
apt_package_install(package)
elif distro_category == DistroCategory.redhat:
yum_package_install(package)
elif distro_category == DistroCategory.suse:
zypper_package_install(package)
def zypper_package_install(package):
hutil.log('zypper --non-interactive in ' + package)
code, output, stderr = run_cmd('zypper --non-interactive in ' + package)
if code == 0:
hutil.log('Package ' + package + ' is installed successfully')
else:
waagent.AddExtensionEvent(name=ExtensionShortName, op='InstallInProgress', isSuccess=True,
message="Failed to install zypper package :" + package)
raise Exception('Failed to install package {0}: stdout: {1}, stderr: {2}'.format(package, output, stderr))
def yum_package_install(package):
hutil.log('yum install -y ' + package)
code, output, stderr = run_cmd('yum install -y ' + package)
if code == 0:
hutil.log('Package ' + package + ' is installed successfully')
else:
waagent.AddExtensionEvent(name=ExtensionShortName, op='InstallInProgress', isSuccess=True,
message="Failed to install yum package :" + package)
raise Exception('Failed to install package {0}: stdout: {1}, stderr: {2}'.format(package, output, stderr))
def apt_package_install(package):
hutil.log('apt-get install -y --force-yes ' + package)
code, output, stderr = run_cmd('apt-get install -y --force-yes ' + package)
if code == 0:
hutil.log('Package ' + package + ' is installed successfully')
else:
waagent.AddExtensionEvent(name=ExtensionShortName, op='InstallInProgress', isSuccess=True,
message="Failed to install apt package :" + package)
raise Exception('Failed to install package {0}: stdout: {1}, stderr: {2}'.format(package, output, stderr))
def get_openssl_version():
cmd_result = waagent.RunGetOutput("openssl version")
cmd_result = cmd_result.decode() if isinstance(cmd_result, bytes) else cmd_result
openssl_version = cmd_result[1].split()[1]
if re.match('^1.0.*', openssl_version):
return '100'
elif re.match('^1.1.*', openssl_version):
return '110'
else:
error_msg = 'This system does not have a supported version of OpenSSL installed. Supported version: 1.0.*, 1.1.*'
hutil.error(error_msg)
waagent.AddExtensionEvent(name=ExtensionShortName, op='InstallInProgress', isSuccess=True,
message="System doesn't have supported OpenSSL version:" + openssl_version)
hutil.do_exit(51, 'Install', 'error', '51', openssl_version + 'is not supported.')
def start_omiservice():
run_cmd('/opt/omi/bin/service_control start')
code, output, stderr =run_cmd('service omid status')
if code == 0:
hutil.log('Service omid is started')
else:
raise Exception('Failed to start service omid, status: stdout: {0}, stderr: {1}'.format(output, stderr))
def download_file():
waagent.AddExtensionEvent(name=ExtensionShortName, op="EnableInProgress", isSuccess=True,
message="Downloading file")
download_dir = prepare_download_dir(hutil.get_seq_no())
storage_account_name = get_config('StorageAccountName')
storage_account_key = get_config('StorageAccountKey')
file_uri = get_config('FileUri')
if not file_uri:
error_msg = 'Missing FileUri configuration'
waagent.AddExtensionEvent(name=ExtensionShortName,
op=Operation.Download,
isSuccess=False,
message="(03000)Argument error, invalid file location")
hutil.do_exit(51, 'Enable', 'error', '51', '(03000)Argument error, invalid file location')
if storage_account_name and storage_account_key:
hutil.log('Downloading file from azure storage...')
path = download_azure_blob(storage_account_name, storage_account_key, file_uri, download_dir)
return path
else:
hutil.log('Downloading file from external link...')
waagent.AddExtensionEvent(name=ExtensionShortName, op="EnableInProgress", isSuccess=True,
message="Downloading file from external link...")
path = download_external_file(file_uri, download_dir)
return path
def download_azure_blob(account_name, account_key, file_uri, download_dir):
waagent.AddExtensionEvent(name=ExtensionShortName, op="EnableInProgress", isSuccess=True,
message="Downloading from azure blob")
try:
(blob_name, container_name) = parse_blob_uri(file_uri)
host_base = get_host_base_from_uri(file_uri)
blob_parent_path = os.path.join(download_dir, os.path.dirname(blob_name))
if not os.path.exists(blob_parent_path):
os.makedirs(blob_parent_path)
download_path = os.path.join(download_dir, blob_name)
blob_service = BlobService(account_name, account_key, host_base=host_base)
except Exception as e:
waagent.AddExtensionEvent(name=ExtensionShortName, op='DownloadInProgress', isSuccess=True,
message='Enable failed with the azure storage error : {0}, stack trace: {1}'.format(
str(e), traceback.format_exc()))
hutil.error('Failed to enable the extension with error: %s, stack trace: %s' % (str(e), traceback.format_exc()))
hutil.do_exit(1, 'Enable', 'error', '1', 'Enable failed: {0}'.format(e))
max_retry = 3
for retry in range(1, max_retry + 1):
try:
blob_service.get_blob_to_path(container_name, blob_name, download_path)
except Exception:
hutil.error('Failed to download Azure blob, retry = ' + str(retry) + ', max_retry = ' + str(max_retry))
if retry != max_retry:
hutil.log('Sleep 10 seconds')
time.sleep(10)
else:
waagent.AddExtensionEvent(name=ExtensionShortName,
op=Operation.Download,
isSuccess=False,
message="(03303)Failed to download file from Azure Storage")
raise Exception('Failed to download azure blob: ' + blob_name)
waagent.AddExtensionEvent(name=ExtensionShortName,
op=Operation.Download,
isSuccess=True,
message="(03301)Succeeded to download file from Azure Storage")
return download_path
def parse_blob_uri(blob_uri):
path = get_path_from_uri(blob_uri).strip('/')
first_sep = path.find('/')
if first_sep == -1:
waagent.AddExtensionEvent(name=ExtensionShortName, op="EnableInProgress", isSuccess=False,
message="Error occured while extracting container and blob name.")
hutil.error("Failed to extract container and blob name from " + blob_uri)
blob_name = path[first_sep + 1:]
container_name = path[:first_sep]
return (blob_name, container_name)
def get_path_from_uri(uri):
uri = urlparse(uri)
return uri.path
def get_host_base_from_uri(blob_uri):
uri = urlparse(blob_uri)
netloc = uri.netloc
if netloc is None:
return None
return netloc[netloc.find('.'):]
def download_external_file(file_uri, download_dir):
waagent.AddExtensionEvent(name=ExtensionShortName, op="EnableInProgress", isSuccess=True,
message="Downloading from external file")
path = get_path_from_uri(file_uri)
file_name = path.split('/')[-1]
file_path = os.path.join(download_dir, file_name)
max_retry = 3
for retry in range(1, max_retry + 1):
try:
download_and_save_file(file_uri, file_path)
waagent.AddExtensionEvent(name=ExtensionShortName, op=Operation.Download, isSuccess=True,
message="(03302)Succeeded to download file from public URI")
return file_path
except Exception as e:
hutil.error('Failed to download public file, retry = ' + str(retry) + ', max_retry = ' + str(max_retry))
if retry != max_retry:
hutil.log('Sleep 10 seconds')
time.sleep(10)
else:
waagent.AddExtensionEvent(name=ExtensionShortName,
op=Operation.Download,
isSuccess=False,
message='(03304)Failed to download file from public URI, error : %s, stack trace: %s' % (
str(e), traceback.format_exc()))
raise Exception('Failed to download public file: ' + file_name)
def download_and_save_file(uri, file_path):
src = urlopen(uri)
dest = open(file_path, 'wb')
buf_size = 1024
buf = src.read(buf_size)
while (buf):
dest.write(buf)
buf = src.read(buf_size)
def prepare_download_dir(seq_no):
main_download_dir = os.path.join(os.getcwd(), DownloadDirectory)
if not os.path.exists(main_download_dir):
os.makedirs(main_download_dir)
cur_download_dir = os.path.join(main_download_dir, seq_no)
if not os.path.exists(cur_download_dir):
os.makedirs(cur_download_dir)
return cur_download_dir
def apply_dsc_configuration(config_file_path):
cmd = dsc_script_path + '/StartDscConfiguration.py -configurationmof ' + config_file_path
waagent.AddExtensionEvent(name=ExtensionShortName, op='EnableInProgress', isSuccess=True,
message='running the cmd: ' + cmd)
code, output, stderr = run_cmd(cmd)
if code == 0:
code, output, stderr = run_cmd(dsc_script_path + '/GetDscConfiguration.py')
return output
else:
error_msg = 'Failed to apply MOF configuration: stdout: {0}, stderr: {1}'.format(output, stderr)
waagent.AddExtensionEvent(name=ExtensionShortName, op=Operation.ApplyMof, isSuccess=True, message=error_msg)
hutil.error(error_msg)
raise Exception(error_msg)
def apply_dsc_meta_configuration(config_file_path):
cmd = dsc_script_path + '/SetDscLocalConfigurationManager.py -configurationmof ' + config_file_path
waagent.AddExtensionEvent(name=ExtensionShortName, op='EnableInProgress', isSuccess=True,
message='running the cmd: ' + cmd)
code, output, stderr = run_cmd(cmd)
if code == 0:
code, output, stderr = run_cmd(dsc_script_path + '/GetDscLocalConfigurationManager.py')
return output
else:
error_msg = 'Failed to apply Meta MOF configuration: stdout: {0}, stderr: {1}'.format(output, stderr)
hutil.error(error_msg)
waagent.AddExtensionEvent(name=ExtensionShortName,
op=Operation.ApplyMetaMof,
isSuccess=False,
message="(03107)" + error_msg)
raise Exception(error_msg)
def get_statusfile_path():
seq_no = hutil.get_seq_no()
waagent.AddExtensionEvent(name=ExtensionShortName, op="EnableInProgress", isSuccess=True,
message="sequence number is :" + seq_no)
status_file = None
handlerEnvironment = None
handler_env_path = os.path.join(os.getcwd(), 'HandlerEnvironment.json')
try:
with open(handler_env_path, 'r') as handler_env_file:
handler_env_txt = handler_env_file.read()
handler_env = json.loads(handler_env_txt)
if type(handler_env) == list:
handler_env = handler_env[0]
handlerEnvironment = handler_env
except Exception as e:
hutil.error(e.message)
waagent.AddExtensionEvent(name=ExtensionShortName, op="EnableInProgress", isSuccess=True,
message='exception in retrieving status_dir error : %s, stack trace: %s' % (
str(e), traceback.format_exc()))
status_dir = handlerEnvironment['handlerEnvironment']['statusFolder']
status_file = status_dir + '/' + seq_no + '.status'
waagent.AddExtensionEvent(name=ExtensionShortName, op="EnableInProgress", isSuccess=True,
message="status file path: " + status_file)
return status_file
def get_status_message_details():
agent_id = get_nodeid(nodeid_path)
vm_uuid = get_vmuuid()
status_file_path = None
if vm_uuid is not None and agent_id is not None:
status_file_path = get_statusfile_path()
return status_file_path, agent_id, vm_uuid
def update_statusfile(status_filepath, node_id, vmuuid, response):
waagent.AddExtensionEvent(name=ExtensionShortName, op="EnableInProgress", isSuccess=True,
message="updating the status file " + '[statusfile={0}][vmuuid={1}][node_id={2}]'.format(
status_filepath, vmuuid, node_id))
if status_filepath is None:
error_msg = "Unable to locate a status file"
hutil.error(error_msg)
waagent.AddExtensionEvent(name=ExtensionShortName, op="EnableInProgress", isSuccess=False, message=error_msg)
return None
status_data = None
if os.path.exists(status_filepath):
jsonData = open(status_filepath)
status_data = json.load(jsonData)
jsonData.close()
accountName = response.deserialized_data["AccountName"]
rgName = response.deserialized_data["ResourceGroupName"]
subId = response.deserialized_data["SubscriptionId"]
metadatastatus = [{"status": "success", "code": "0", "name": "metadata", "formattedMessage": {"lang": "en-US",
"message": "AgentID=" + node_id + ";VMUUID=" + vmuuid + ";AutomationAccountName=" + accountName + ";ResourceGroupName=" + rgName + ";Subscription=" + subId}}]
with open(status_filepath, "w") as fp:
status_file_content = [{"status":
{"status": "success",
"formattedMessage": {"lang": "en-US", "message": "Enable Succeeded"},
"operation": "Enable", "code": "0", "name": "Microsoft.OSTCExtensions.DSCForLinux",
"substatus": metadatastatus
},
"version": "1.0", "timestampUTC": time.strftime(date_time_format, time.gmtime())
}]
json.dump(status_file_content, fp)
waagent.AddExtensionEvent(name=ExtensionShortName, op="EnableInProgress", isSuccess=True,
message="successfully written nodeid and vmuuid")
waagent.AddExtensionEvent(name=ExtensionName, op="Enable", isSuccess=True,
message="successfully executed enable functionality")
def get_nodeid(file_path):
id = None
try:
if os.path.exists(file_path):
with open(file_path) as f:
id = f.readline().strip()
except Exception as e:
error_msg = 'get_nodeid() failed: Unable to open id file {0}'.format(file_path)
hutil.error(error_msg)
waagent.AddExtensionEvent(name=ExtensionShortName, op="EnableInProgress", isSuccess=False, message=error_msg)
return None
if not id:
error_msg = 'get_nodeid() failed: Empty content in id file {0}'.format(file_path)
hutil.error(error_msg)
waagent.AddExtensionEvent(name=ExtensionShortName, op="EnableInProgress", isSuccess=False, message=error_msg)
return None
return id
def get_vmuuid():
UUID = None
code, output, stderr = run_cmd("sudo dmidecode | grep UUID | sed -e 's/UUID: //'")
if code == 0:
UUID = output.strip()
return UUID
def get_omscloudid():
OMSCLOUD_ID = None
code, output, stderr = run_cmd("sudo dmidecode | grep 'Tag: 77' | sed -e 's/Asset Tag: //'")
if code == 0:
OMSCLOUD_ID = output.strip()
return OMSCLOUD_ID
def check_dsc_configuration(current_config):
outputlist = re.split("\n", current_config)
for line in outputlist:
if re.match(r'ReturnValue=0', line.strip()):
return True
return False
def install_module(file_path):
install_package('unzip')
cmd = dsc_script_path + '/InstallModule.py ' + file_path
code, output, stderr = run_cmd(cmd)
waagent.AddExtensionEvent(name=ExtensionShortName,
op="InstallModuleInProgress",
isSuccess=True,
message="Running the cmd: " + cmd)
if not code == 0:
error_msg = 'Failed to install DSC Module ' + file_path + ' stdout: {0}, stderr: {1}'.format(output, stderr)
hutil.error(error_msg)
waagent.AddExtensionEvent(name=ExtensionShortName,
op=Operation.InstallModule,
isSuccess=False,
message="(03100)" + error_msg)
raise Exception(error_msg)
waagent.AddExtensionEvent(name=ExtensionShortName,
op=Operation.InstallModule,
isSuccess=True,
message="(03101)Succeeded to install DSC Module")
def remove_module():
module_name = get_config('ResourceName')
cmd = dsc_script_path + '/RemoveModule.py ' + module_name
code, output, stderr = run_cmd(cmd)
waagent.AddExtensionEvent(name=ExtensionShortName,
op="RemoveModuleInProgress",
isSuccess=True,
message="Running the cmd: " + cmd)
if not code == 0:
error_msg = 'Failed to remove DSC Module ' + module_name + ' stdout: {0}, stderr: {1}'.format(output, stderr)
hutil.error(error_msg)
waagent.AddExtensionEvent(name=ExtensionShortName,
op=Operation.RemoveModule,
isSuccess=False,
message="(03102)" + error_msg)
raise Exception(error_msg)
waagent.AddExtensionEvent(name=ExtensionShortName,
op=Operation.RemoveModule,
isSuccess=True,
message="(03103)Succeeded to remove DSC Module")
def uninstall_package(package_name):
waagent.AddExtensionEvent(name=ExtensionShortName, op='InstallInProgress', isSuccess=True,
message="uninstalling the package" + package_name)
if distro_category == DistroCategory.debian:
deb_uninstall_package(package_name)
elif distro_category == DistroCategory.redhat or distro_category == DistroCategory.suse:
rpm_uninstall_package(package_name)
def deb_uninstall_package(package_name):
cmd = 'dpkg -P ' + package_name
code, output, stderr = run_dpkg_cmd_with_retry(cmd)
if code == 0:
hutil.log('Package ' + package_name + ' was removed successfully')
elif code == DPKGLockedErrorCode:
hutil.do_exit(DPKGLockedErrorCode, 'Install', 'error', str(DPKGLockedErrorCode), 'Operation failed because the package manager on the VM is currently locked. Please try again.')
else:
waagent.AddExtensionEvent(name=ExtensionShortName, op='InstallInProgress', isSuccess=True,
message="failed to remove the package" + package_name)
raise Exception('Failed to remove package ' + package_name)
def rpm_uninstall_package(package_name):
cmd = 'rpm -e ' + package_name
code, output, stderr = run_cmd(cmd)
if code == 0:
hutil.log('Package ' + package_name + ' was removed successfully')
else:
waagent.AddExtensionEvent(name=ExtensionShortName, op='InstallInProgress', isSuccess=True,
message="failed to remove the package" + package_name)
raise Exception('Failed to remove package ' + package_name)
def is_dpkg_locked(exit_code, output):
"""
If dpkg is locked, the output will contain a message similar to 'dpkg
status database is locked by another process'
"""
if exit_code is not 0:
dpkg_locked_search = r'^.*dpkg.+lock.*$'
dpkg_locked_re = re.compile(dpkg_locked_search, re.M)
if dpkg_locked_re.search(output):
return True
return False
def register_automation(registration_key, registation_url, node_configuration_name, refresh_freq,
configuration_mode_freq, configuration_mode):
if (registration_key == '' or registation_url == ''):
err_msg = "Either the Registration Key or Registration URL is NOT provided"
hutil.error(err_msg)
waagent.AddExtensionEvent(name=ExtensionShortName, op='RegisterInProgress', isSuccess=True, message=err_msg)
return 51, err_msg
if configuration_mode != '' and not (
configuration_mode == 'applyandmonitor' or configuration_mode == 'applyandautocorrect' or configuration_mode == 'applyonly'):
err_msg = "ConfigurationMode: " + configuration_mode + " is not valid."
hutil.error(err_msg + "It should be one of the values : (ApplyAndMonitor | ApplyAndAutoCorrect | ApplyOnly)")
waagent.AddExtensionEvent(name=ExtensionShortName, op='RegisterInProgress', isSuccess=True, message=err_msg)
return 51, err_msg
cmd = dsc_script_path + '/Register.py' + ' --RegistrationKey ' + registration_key \
+ ' --ServerURL ' + registation_url
optional_parameters = ""
if node_configuration_name != '':
optional_parameters += ' --ConfigurationName ' + node_configuration_name
if refresh_freq != '':
optional_parameters += ' --RefreshFrequencyMins ' + refresh_freq
if configuration_mode_freq != '':
optional_parameters += ' --ConfigurationModeFrequencyMins ' + configuration_mode_freq
if configuration_mode != '':
optional_parameters += ' --ConfigurationMode ' + configuration_mode
waagent.AddExtensionEvent(name=ExtensionShortName,
op="RegisterInProgress",
isSuccess=True,
message="Registration URL " + registation_url + "Optional parameters to Registration" + optional_parameters)
code, output, stderr = run_cmd(cmd + optional_parameters)
if not code == 0:
error_msg = '(03109)Failed to register with Azure Automation DSC: stdout: {0}, stderr: {1}'.format(output, stderr)
hutil.error(error_msg)
waagent.AddExtensionEvent(name=ExtensionShortName,
op=Operation.Register,
isSuccess=False,
message=error_msg)
return 1, error_msg
waagent.AddExtensionEvent(name=ExtensionShortName,
op=Operation.Register,
isSuccess=True,
message="(03108)Succeeded to register with Azure Automation DSC")
return 0, ''
if __name__ == '__main__':
main()