Skip to content

Instantly share code, notes, and snippets.

@toshihirock
Created August 12, 2017 21:41
Show Gist options
  • Save toshihirock/3ec429c44d7cfa91f9d622552cb5b4a8 to your computer and use it in GitHub Desktop.
Save toshihirock/3ec429c44d7cfa91f9d622552cb5b4a8 to your computer and use it in GitHub Desktop.
AWS-RunPatchBaseline
{
"schemaVersion": "2.2",
"description": "Scans for or installs patches from a patch baseline to a Linux or Windows operating system.",
"parameters": {
"Operation": {
"type": "String",
"description": "(Required) The update or configuration to perform on the instance. The system checks if patches specified in the patch baseline are installed on the instance. The install operation installs patches missing from the baseline.",
"allowedValues": [
"Scan",
"Install"
]
},
"SnapshotId": {
"type": "String",
"description": "(Optional) The snapshot ID to use to retrieve a patch baseline snapshot.",
"allowedPattern": "(^$)|^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
"default": ""
}
},
"mainSteps": [
{
"precondition": {
"StringEquals": [
"platformType",
"Windows"
]
},
"action": "aws:runPowerShellScript",
"name": "PatchWindows",
"inputs": {
"timeoutSeconds": 7200,
"runCommand": [
"# Check the OS version",
"if ([Environment]::OSVersion.Version.Major -le 5) {",
" Write-Error 'This command is not supported on Windows 2003 or lower.'",
" exit -1",
"} elseif ([Environment]::OSVersion.Version.Major -ge 10) {",
" $sku = (Get-CimInstance -ClassName Win32_OperatingSystem).OperatingSystemSKU",
" if ($sku -eq 143 -or $sku -eq 144) {",
" Write-Host 'This command is not supported on Windows 2016 Nano Server.'",
" exit -1",
" }",
"}",
"# Check the SSM agent version",
"$ssmAgentService = Get-ItemProperty 'HKLM:SYSTEM\\CurrentControlSet\\Services\\AmazonSSMAgent\\'",
"if (-not $ssmAgentService -or $ssmAgentService.Version -lt '2.0.533.0') {",
" Write-Host 'This command is not supported with SSM Agent version less than 2.0.533.0.'",
" exit -1",
"}",
"",
"# Application specific constants",
"$appName = 'PatchBaselineOperations'",
"$psModuleFileName = 'Amazon.PatchBaselineOperations.dll'",
"$s3FileName = 'Amazon.PatchBaselineOperations-1.3.zip'",
"$s3LocationUsEast = 'https://s3.amazonaws.com/aws-ssm-{0}/' + $appName.ToLower() + '/' + $s3FileName",
"$s3LocationRegular = 'https://s3-{0}.amazonaws.com/aws-ssm-{0}/' + $appName.ToLower() + '/' + $s3FileName",
"$s3LocationCn = 'https://s3.{0}.amazonaws.com.cn/aws-ssm-{0}/' + $appName.ToLower() + '/' + $s3FileName",
"$s3FileHash = '62DC8257A7F891651AFA86D6B0260074610E76D510366E7E794D1D54A8E79428'",
"$psModuleHashes = @{ ",
" 'Amazon.PatchBaselineOperations.dll' = '9103328E3BFAB182B43A04C907134BC9F97E4106E0B83D7C76FC16E3E1AB3C5B';",
" 'AWSSDK.Core.dll' = '8B97F15D68A85AAB7AB0B0BF30C32992B24244E258D78E2C7ED7F572922CEB56';",
" 'AWSSDK.SimpleSystemsManagement.dll' = '9A868136F071DA2907A9A15B0696EFFA253EBCECD8A73D65B85C8DF8B9730720';",
" 'Newtonsoft.Json.dll' = '0516D4109263C126C779E4E8F5879349663FA0A5B23D6D44167403E14066E6F9';",
" 'THIRD_PARTY_LICENSES.txt' = '4C9B3A1C7C3E27676DD31AFC89FAC6584CA49FB850C9E62DDCF9E5E78F50640C'",
"}",
"",
"# Folders and Logging",
"$tempDirectory = $env:TEMP",
"$downloadPath = [IO.Path]::Combine($tempDirectory, $s3FileName)",
"$psModuleInstallLocation = [IO.Path]::Combine([Environment]::GetEnvironmentVariable([Environment+SpecialFolder]::ProgramFiles), 'Amazon', $appName)",
"$psModuleInstallFile = [IO.Path]::Combine($psModuleInstallLocation, $psModuleFileName)",
"$log = @()",
"",
"function CheckFileHash ($filePath, $fileHash) {",
" if (Test-Path($filePath)) {",
" $fileStream = New-Object System.IO.FileStream($filePath, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read)",
" $sha256 = [System.Security.Cryptography.HashAlgorithm]::Create('System.Security.Cryptography.SHA256CryptoServiceProvider')",
" $sourceHash = [System.BitConverter]::ToString($sha256.ComputeHash($fileStream), 0).Replace('-', '').ToLowerInvariant()",
" $sha256.Dispose()",
" $fileStream.Dispose()",
" ",
" if ($sourceHash -ne $fileHash) {",
" return $false",
" }",
" else {",
" return $true",
" }",
" }",
" else {",
" return $false",
" }",
"}",
"",
"function CheckPowerShellModuleInstallation {",
" $isInstalled = $false ",
" # Path does not exist meaning it has never been downloaded.",
" if (Test-Path($psModuleInstallLocation)) {",
" # Check if the expected number of files and directories are in the folder",
" if (((Get-ChildItem $psModuleInstallLocation -Directory | Measure-Object | %{$_.Count}) -eq 0) -and ",
" ((Get-ChildItem $psModuleInstallLocation -File | Measure-Object | %{$_.Count}) -eq $psModuleHashes.Count)) {",
" $validFileHashes = $true",
"",
" # Check each file for their expected file hash.",
" Get-ChildItem $psModuleInstallLocation -File | ForEach-Object {",
" if ($psModuleHashes.ContainsKey($_.Name)) {",
" $installFile = [IO.Path]::Combine($psModuleInstallLocation, $_.Name)",
" if (-Not (CheckFileHash $installFile $psModuleHashes[$_.Name])) {",
" $log += ('The SHA hash of the {0} file does not match the expected value.' -f $_.Name)",
" $validFileHashes = $false",
" }",
" } else {",
" $log += ('The PowerShellModule installation folder contains an unexpected file with name {0}.' -f $_.Name)",
" $validFileHashes = $false",
" }",
" }",
"",
" $isInstalled = $validFileHashes",
" } else {",
" $log += ('An incorrect number of files were present in the PowerShellModule installation folder. The contents will be deleted.')",
" }",
"",
" if (-Not $isInstalled) {",
" # Remove all files and folders as the folder contains potentially malicious software.",
" Remove-Item $psModuleInstallLocation -Recurse",
" }",
" }",
" ",
" return $isInstalled",
"}",
"",
"function ExtractZipCoreOs ([string]$zipFilePath, [string]$destPath) {",
" try {",
" [System.Reflection.Assembly]::LoadWithPartialName('System.IO.Compression.FileSystem') | Out-Null",
" $zip = [System.IO.Compression.ZipFile]::OpenRead($zipFilePath)",
" foreach ($item in $zip.Entries) {",
" $extractedPath = Join-Path $destPath $item.FullName",
"",
" if ($item.Length -eq 0) {",
" if ((Test-Path $extractedPath) -eq 0) {",
" mkdir $extractedPath | Out-Null",
" }",
" } else {",
" $fileParent = Split-Path $extractedPath",
"",
" if ((Test-Path $fileParent) -eq 0) {",
" mkdir $fileParent | Out-Null",
" }",
"",
" [System.IO.Compression.ZipFileExtensions]::ExtractToFile($item, $extractedPath, $true)",
" }",
" }",
" } catch {",
" throw 'Error encountered when extracting patch management zip file.`n$($_.Exception.Message)'",
" } finally {",
" $zip.Dispose()",
" }",
"}",
"",
"function InstallPowerShellModule {",
" if (-Not (CheckPowerShellModuleInstallation)) {",
" $log += (\"Preparing to download {0} PowerShell module from S3.`r`n\" -f $appName)",
"",
" #Setup the directories if they do not exist.",
" if (-Not (Test-Path($psModuleInstallLocation))) {",
" $noOp = New-Item $psModuleInstallLocation -ItemType Directory",
" } ",
"",
" if (-Not (Test-Path($tempDirectory))) {",
" $noOp = New-Item $tempDirectory -ItemType Directory",
" }",
" $region = $env:AWS_SSM_REGION_NAME ",
" if ($region -eq 'us-east-1') {",
" $s3Location = $s3LocationUsEast -f $region",
" } elseif ($region -eq 'cn-north-1') {",
" $s3Location = $s3LocationCn -f $region",
" } else {",
" $s3Location = $s3LocationRegular -f $region",
" }",
"",
" $log += (\"Downloading {0} PowerShell module from {1} to {2}.`r`n\" -f $appName, $s3Location, $downloadPath)",
" (New-Object Net.WebClient).DownloadFile($s3Location, $downloadPath)",
"",
" if (CheckFileHash $downloadPath $s3FileHash ) {",
" $log += (\"Extracting {0} zip file contents to temporary folder.`r`n\" -f $appName)",
" try {",
" (New-Object -Com Shell.Application).namespace($psModuleInstallLocation).CopyHere((New-Object -Com Shell.Application).namespace($downloadPath).Items(), 16)",
" } catch [Exception] {",
" ExtractZipCoreOs $downloadPath $psModuleInstallLocation",
" }",
" }",
" else {",
" throw ('The SHA hash of the {0} S3 source file does not match the expected value.' -f $appName)",
" }",
"",
" $log += (\"Verifying SHA 256 of the {0} PowerShell module files.`r`n\" -f $appName)",
" if (-Not (CheckPowerShellModuleInstallation)) {",
" throw ('The verification of the {0} PowerShell module did not pass.' -f $appName)",
" }",
"",
" $log += (\"Successfully downloaded and installed the {0} PowerShell module.`r`n\" -f $appName)",
" }",
"}",
"",
"try {",
" InstallPowerShellModule",
"} catch [Exception] {",
" $msg = \"An error occurred when executing {0}: {1}`r`nDetails:`r`n{2}\" -f $appName, $_.Exception.Message, $log",
" Write-Error $msg",
" exit -1",
"}",
"finally {",
" if (Test-Path $downloadPath) {",
" rm $downloadPath",
" }",
"}",
"",
"# Setup the command",
"Import-Module $psModuleInstallFile",
"$response = Invoke-PatchBaselineOperation -Operation {{Operation}} -SnapshotId '{{SnapshotId}}' -InstanceId $env:AWS_SSM_INSTANCE_ID -Region $env:AWS_SSM_REGION_NAME",
"",
"if ($response.ExitCode -ne 3010)",
"{",
" $response.ToString()",
"}",
"",
"exit $response.ExitCode"
]
}
},
{
"precondition": {
"StringEquals": [
"platformType",
"Linux"
]
},
"action": "aws:runShellScript",
"name": "PatchLinux",
"inputs": {
"timeoutSeconds": 7200,
"runCommand": [
"CMD=''",
"which python3 2>/dev/null",
"if [ $? -eq 0 ]",
"then",
"CMD='python3'",
"yum install python3-requests -y 2>/dev/null",
"apt-get install python3-requests -y 2>/dev/null",
"apt-get install python3-apt -y 2>/dev/null",
"else",
"which python 2>/dev/null",
"if [ $? -eq 0 ]",
"then",
"CMD='python'",
"yum install python-requests -y 2>/dev/null",
"apt-get install python-requests -y 2>/dev/null",
"apt-get install python-apt -y 2>/dev/null",
"else",
"echo 'No Python 2 or 3 found. exit.'",
"exit 1",
"fi",
"fi",
"",
"echo '",
"import errno",
"import hashlib",
"import json",
"import logging",
"import os",
"import requests",
"import shutil",
"import subprocess",
"import tarfile",
"import sys",
"",
"tmp_dir = os.path.abspath(\"/tmp/patch-baseline-operations/\")",
"reboot_dir = os.path.abspath(\"/tmp/patch-baseline-operations-194/\")",
"",
"# if the update actually installed some package,",
"# the return code will be 194, triggering a reboot",
"# checking whether this dir exists, if exists,",
"# remove it and exit 0, since after the reboot",
"# agent will try to run this again",
"# leave the remaining work to be handled by the common startup script.",
"if os.path.exists(reboot_dir):",
" sys.exit(0)",
"",
"",
"def create_dir(dirpath):",
" dirpath = os.path.abspath(dirpath)",
" if not os.path.exists(dirpath):",
" try:",
" os.makedirs(dirpath)",
" except OSError as e: # Guard against race condition",
" if e.errno != errno.EEXIST:",
" raise e",
" except Exception as e:",
" logger.error(\"Unable to create dir: %s\", dirpath)",
" logger.exception(e)",
" abort()",
"",
"",
"def download(url, path):",
" path = os.path.abspath(path)",
" create_dir(os.path.dirname(path))",
" try:",
" with open(path, \"wb\") as f:",
" response = requests.get(url, stream=True)",
" if not response.ok:",
" raise Exception(\"Response code received is %d instead of 200.\" % (response.status_code))",
" for block in response.iter_content(1024):",
" f.write(block)",
" except Exception as e:",
" logger.error(\"Unable to download url: %s.\", url)",
" logger.exception(e)",
" abort()",
"",
"",
"def extract_tar(path):",
" path = os.path.abspath(path)",
" try:",
" f = tarfile.open(path, \"r|gz\")",
" f.extractall()",
" except Exception as e:",
" logger.error(\"Unable to extract tar file: %s.\", path)",
" logger.exception(e)",
" abort()",
" finally:",
" f.close()",
"",
"",
"def shell_command(cmd_list):",
" with open(os.devnull, \"w\") as devnull:",
" p = subprocess.Popen(cmd_list, stdout=subprocess.PIPE, stderr=devnull)",
" (std_out, _) = p.communicate()",
" if not type(std_out) == str:",
" std_out = std_out.decode(\"utf-8\")",
" return (std_out, p.returncode)",
"",
"",
"def abort():",
" if os.path.exists(tmp_dir):",
" shutil.rmtree(tmp_dir)",
" sys.exit(1)",
"",
"",
"def import_public_key():",
" public_key = \"\"\"",
"-----BEGIN PGP PUBLIC KEY BLOCK-----",
"Version: GnuPG v2.0.18 (GNU/Linux)",
"",
"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",
"=pkzy",
"-----END PGP PUBLIC KEY BLOCK-----",
" \"\"\"",
"",
" with open(\"./public_key\", \"w\") as f:",
" f.write(public_key)",
" with open(os.devnull, \"w\") as devnull:",
" (_, ret) = shell_command([\"gpg\", \"--import\", \"./public_key\"])",
" if not ret == 0:",
" logger.error(\"Unable to import ssm public key, code %d.\", ret)",
" abort()",
"",
"",
"# cd into the temp directory",
"create_dir(tmp_dir)",
"os.chdir(tmp_dir)",
"",
"# initialize logging",
"LOGGER_FORMAT = \"%(asctime)s %(name)s [%(levelname)s]: %(message)s\"",
"LOGGER_DATEFORMAT = \"%m/%d/%Y %X\"",
"LOGGER_LEVEL = logging.INFO",
"LOGGER_STREAM = sys.stdout",
"",
"logging.basicConfig(format=LOGGER_FORMAT, datefmt=LOGGER_DATEFORMAT, level=LOGGER_LEVEL, stream=LOGGER_STREAM)",
"logger = logging.getLogger()",
"",
"instance_id = None",
"region = None",
"",
"try:",
" logger.info(\"Attempting to acquire instance information from ssm-cli.\")",
"",
" (ssm_cli_path, returncode) = shell_command([\"which\", \"ssm-cli\"])",
" if (returncode != 0):",
" logger.warn(\"Unable to locate ssm-cli for instance information, code: %d.\", p.returncode)",
" raise Exception()",
"",
" ssm_cli_path = ssm_cli_path.strip()",
" logger.info(\"ssm-cli path is: %s.\", ssm_cli_path)",
" (instance_metadata_json, returncode) = shell_command([ssm_cli_path, \"get-instance-information\"])",
" if (returncode != 0):",
" logger.warn(\"Unable to get instance information from ssm-cli, code: %d.\", p.returncode)",
" raise Exception()",
"",
" instance_metadata_json = instance_metadata_json.strip()",
" logger.info(\"Instance metadata from ssm-cli is: %s.\", instance_metadata_json)",
" instance_metadata = json.loads(instance_metadata_json)",
" instance_id = instance_metadata[\"instance-id\"]",
" region = instance_metadata[\"region\"]",
"except Exception as e:",
" logger.info(\"Attempt to acquire instance information ec2 metadata.\")",
" try:",
" instance_metadata_json = requests.get(",
" \"http://169.254.169.254/latest/dynamic/instance-identity/document\"",
" ).text",
" instance_metadata = json.loads(instance_metadata_json)",
" instance_id = instance_metadata[\"instanceId\"]",
" region = instance_metadata[\"region\"]",
" except Exception as e:",
" logger.error(\"Fail to acquire instance information.\")",
" logger.exception(e)",
" abort()",
"",
"# main logic",
"s3_bucket = \"aws-ssm-%s\" % (region)",
"s3_prefix = \"patchbaselineoperations/linux\"",
"manifest_file = \"MANIFEST\"",
"manifest_file_asc = manifest_file + \".asc\"",
"",
"# import public key for signature verification",
"import_public_key()",
"",
"# download manifest file and do signature verification",
"if region.startswith(\"cn-\"):",
" url_template = \"https://s3.%s.amazonaws.com.cn/%s/%s\"",
"elif region.startswith(\"us-gov-\"):",
" url_template = \"https://s3-fips-%s.amazonaws.com/%s/%s\"",
"else:",
" url_template = \"https://s3.dualstack.%s.amazonaws.com/%s/%s\"",
"",
"download(url_template % (region, s3_bucket, os.path.join(s3_prefix, manifest_file_asc)), manifest_file_asc)",
"(_, ret) = shell_command([",
" \"gpg\", \"--no-tty\", \"--output\", manifest_file, \"--decrypt\", manifest_file_asc",
"])",
"if not ret == 0:",
" logger.error(\"Unable to verify the signature of manifest, code %d.\", ret)",
" abort()",
"",
"# payloads are the actual files to be used for linux patching",
"payloads = []",
"try:",
" with open(manifest_file, \"r\") as f:",
" payloads = json.loads(f.read())",
"",
" for payload in payloads:",
" path = payload[\"path\"]",
" file_name = os.path.basename(path)",
" sha256 = payload[\"sha256\"]",
" download(url_template % (region, s3_bucket, path), file_name)",
" # verify sha256",
" with open(file_name, \"rb\") as f:",
" # TODO read file in chunks for better memory efficiency",
" if not hashlib.sha256(f.read()).hexdigest().lower() == sha256.lower():",
" logger.error(\"%s sha256 check failed, should be %s, but is %s\", \\",
" path, sha256, hashlib.sha256(f.read()).hexdigest())",
" abort()",
" extract_tar(file_name)",
"except Exception as e:",
" logger.error(\"Unable to load and extract the content of manifest, abort.\")",
" logger.exception(e)",
" abort()",
"' | $CMD && \\",
"echo '",
"import os",
"import shutil",
"import sys",
"",
"tmp_dir = os.path.abspath(\"/tmp/patch-baseline-operations/\")",
"reboot_dir = os.path.abspath(\"/tmp/patch-baseline-operations-194/\")",
"",
"# if the update actually installed some package,",
"# the return code will be 194, triggering a reboot",
"# checking whether this dir exists, if exists,",
"# remove it and exit 0, since after the reboot",
"# agent will try to run this again",
"# so if the dir exists, just cleanup and return",
"if os.path.exists(reboot_dir):",
" shutil.rmtree(reboot_dir)",
" sys.exit(0)",
"",
"os.chdir(tmp_dir)",
"sys.path.insert(0, tmp_dir)",
"",
"import boto3",
"import errno",
"import json",
"import logging",
"import requests",
"import stat",
"import subprocess",
"import time",
"import uuid",
"",
"",
"def exit(code):",
" if code == 194:",
" dirpath = os.path.abspath(reboot_dir)",
" # the dir should NOT exists, but do the check anyway",
" if not os.path.exists(dirpath):",
" try:",
" os.makedirs(dirpath)",
" except OSError as e: # Guard against race condition",
" if e.errno != errno.EEXIST:",
" raise e",
" except Exception as e:",
" logger.error(\"Unable to create dir: %s\", dirpath)",
" logger.exception(e)",
" elif os.path.exists(reboot_dir):",
" shutil.rmtree(reboot_dir)",
"",
" if os.path.exists(tmp_dir):",
" shutil.rmtree(tmp_dir)",
" sys.exit(code)",
"",
"",
"def abort():",
" exit(1)",
"",
"",
"def shell_command(cmd_list):",
" with open(os.devnull, \"w\") as devnull:",
" p = subprocess.Popen(cmd_list, stdout=subprocess.PIPE, stderr=devnull)",
" (std_out, _) = p.communicate()",
" if not type(std_out) == str:",
" std_out = std_out.decode(\"utf-8\")",
" return (std_out, p.returncode)",
"",
"",
"def restart_agent(operation_type, product, exit_code):",
" if operation_type.lower() == \"install\" and exit_code == 194:",
" if product.startswith(\"RedhatEnterpriseLinux7\"):",
" shell_command([\"shutdown\", \"-r\", \"2\"])",
" try:",
" shell_command([\"systemctl\", \"start\", \"amazon-ssm-agent\"])",
" except Exception as e:",
" pass",
"",
"# initialize logging",
"LOGGER_FORMAT = \"%(asctime)s %(name)s [%(levelname)s]: %(message)s\"",
"LOGGER_DATEFORMAT = \"%m/%d/%Y %X\"",
"LOGGER_LEVEL = logging.INFO",
"LOGGER_STREAM = sys.stdout",
"",
"logging.basicConfig(format=LOGGER_FORMAT, datefmt=LOGGER_DATEFORMAT, level=LOGGER_LEVEL, stream=LOGGER_STREAM)",
"logger = logging.getLogger()",
"",
"instance_id = None",
"region = None",
"# the two attr below are filled in by document",
"operation_type = \"{{Operation}}\"",
"snapshot_id = \"{{SnapshotId}}\"",
"if (snapshot_id == \"\"):",
" snapshot_id = str(uuid.uuid4())",
"",
"try:",
" logger.info(\"Attempting to acquire instance information from ssm-cli.\")",
"",
" (ssm_cli_path, returncode) = shell_command([\"which\", \"ssm-cli\"])",
" if (returncode != 0):",
" logger.warn(\"Unable to locate ssm-cli for instance information, code: %d.\", returncode)",
" raise Exception()",
"",
" ssm_cli_path = ssm_cli_path.strip()",
" logger.info(\"ssm-cli path is: %s.\", ssm_cli_path)",
" (instance_metadata_json, returncode) = shell_command([ssm_cli_path, \"get-instance-information\"])",
" if (returncode != 0):",
" logger.warn(\"Unable to get instance information from ssm-cli, code: %d.\", returncode)",
" raise Exception()",
"",
" instance_metadata_json = instance_metadata_json.strip()",
" logger.info(\"Instance metadata from ssm-cli is: %s.\", instance_metadata_json)",
" instance_metadata = json.loads(instance_metadata_json)",
" instance_id = instance_metadata[\"instance-id\"]",
" region = instance_metadata[\"region\"]",
"except Exception as e:",
" logger.info(\"Attempt to acquire instance information ec2 metadata.\")",
" try:",
" instance_metadata_json = requests.get(",
" \"http://169.254.169.254/latest/dynamic/instance-identity/document\"",
" ).text",
" instance_metadata = json.loads(instance_metadata_json)",
" instance_id = instance_metadata[\"instanceId\"]",
" region = instance_metadata[\"region\"]",
" except Exception as e:",
" logger.error(\"Fail to acquire instance information.\")",
" logger.exception(e)",
" abort()",
"",
"logger.info(\"Operation type: %s.\", operation_type)",
"logger.info(\"Snapshot ID: %s.\", snapshot_id)",
"logger.info(\"Instance ID: %s.\", instance_id)",
"logger.info(\"Region ID: %s.\", region)",
"",
"ssm_client = boto3.client(\"ssm\", region_name=region)",
"",
"try:",
" patch_snapshot = ssm_client.get_deployable_patch_snapshot_for_instance(",
" InstanceId=instance_id,",
" SnapshotId=snapshot_id",
" )",
" logger.debug(\"Snapshot API result: %s.\", patch_snapshot)",
"except Exception as e:",
" logger.error(\"Unable to get snapshot API result.\")",
" logger.exception(e)",
" abort()",
"",
"product = patch_snapshot[\"Product\"]",
"logger.info(\"Product: %s.\", product)",
"",
"patch_snapshot_url = patch_snapshot[\"SnapshotDownloadUrl\"]",
"logger.info(\"Snapshot download URL: %s.\", patch_snapshot_url)",
"",
"try:",
" patch_snapshot = json.loads(requests.get(patch_snapshot_url).text)",
" logger.debug(\"Snapshot: %s.\", patch_snapshot)",
"except Exception as e:",
" logger.error(\"Unable to download snapshot.\")",
" logger.exception(e)",
" abort()",
"",
"patch_baseline = patch_snapshot[\"patchBaseline\"]",
"logger.info(\"Patch baseline: %s.\", patch_baseline)",
"",
"patch_group = patch_snapshot.get(\"patchGroup\") or \"\"",
"logger.info(\"Patch group: %s.\", patch_group)",
"",
"operating_system = patch_baseline[\"operatingSystem\"]",
"logger.info(\"Operating system: %s.\", operating_system)",
"",
"# save the snapshot of baseline along with product, instance id, region",
"# so APT / YUM can use",
"try:",
" with open(\"./snapshot.json\", \"w\") as f:",
" json.dump({",
" \"patchBaseline\": patch_baseline,",
" \"product\": product,",
" \"patchGroup\": patch_group,",
" \"instanceId\": instance_id,",
" \"region\": region,",
" \"operation\": operation_type,",
" \"snapshotId\": snapshot_id",
" }, f)",
"except Exception as e:",
" logger.error(\"Unable to save processed snapshot.json.\")",
" logger.exception(e)",
" abort()",
"",
"if operating_system == \"AMAZON_LINUX\" or operating_system == \"REDHAT_ENTERPRISE_LINUX\":",
" try:",
" (chcon_path, returncode) = shell_command([\"which\", \"chcon\"])",
" if (returncode != 0):",
" logger.warn(\"Unable to locate chcon, code: %d.\", returncode)",
" raise Exception()",
" chcon_path = chcon_path.strip()",
"",
" (yum_path, returncode) = shell_command([\"which\", \"yum\"])",
" if (returncode != 0):",
" logger.warn(\"Unable to locate yum, code: %d.\", returncode)",
" raise Exception()",
" yum_path = yum_path.strip()",
"",
" (_, returncode) = shell_command([chcon_path, \"--reference\", yum_path, \"./yum_entrance.py\"])",
" if (returncode != 0):",
" logger.warn(\"Unable to gain necessary access for possible kernel updates, code: %d.\", returncode)",
" raise Exception()",
" except Exception as e:",
" # already tried to set SELinux access but failed, continue to update packages",
" pass",
"",
" try:",
" st = os.stat(\"./yum_entrance.py\")",
" os.chmod(\"./yum_entrance.py\", st.st_mode | stat.S_IEXEC)",
" p = subprocess.Popen([\"./yum_entrance.py\", \"--file\", \"snapshot.json\"], stdout=sys.stdout, stderr=sys.stderr)",
" # DO NOT REMOVE THE LINE BELOW",
" # IF REMOVED, invoked script may not find the necessary dependency",
" (std_out, std_err) = p.communicate()",
" restart_agent(operation_type, product, p.returncode)",
" exit(p.returncode)",
" except Exception as e:",
" logger.exception(e)",
" abort()",
"elif operating_system == \"UBUNTU\":",
" try:",
" st = os.stat(\"./apt_entrance.py\")",
" os.chmod(\"./apt_entrance.py\", st.st_mode | stat.S_IEXEC)",
" p = subprocess.Popen([\"./apt_entrance.py\", \"--snapshot-json\", \"snapshot.json\"], stdout=sys.stdout,",
" stderr=sys.stderr)",
" # DO NOT REMOVE THE LINE BELOW",
" # IF REMOVED, invoked script may not find the necessary dependency",
" (std_out, std_err) = p.communicate()",
" restart_agent(operation_type, product, p.returncode)",
" exit(p.returncode)",
" except Exception as e:",
" logger.exception(e)",
" abort()",
"else:",
" logger.error(\"Invalid OS: %s.\", operating_system)",
" abort()",
"' | $CMD",
""
]
}
}
]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment