This commit is contained in:
Zack Meier
2026-04-15 15:45:50 -05:00
commit 1d304511b8
613 changed files with 140998 additions and 0 deletions
+4
View File
@@ -0,0 +1,4 @@
*.swp
*.pyc
classes/__pycache__
log/*
+110
View File
@@ -0,0 +1,110 @@
NDIT Cohesity Scripts
-------------------------
These scripts factilitate the automation of NDIT's data protection program with Cohesity. Theses scripts are designed to be used by authorized North Dakota State NDIT personnel only.
Contents
--------
* classes/cohesityAPI.py
* Defines functions that have been tested and setup in accordance with NDIT Cohesity operating guidelines.
* classes/serviceNowAPI.py
* Defines functions used for opening and assigning tickets for Cohesity issues
* classes/sharePointAPI.py
* Defines functions used for looking up assets in the sharepoint configuration management database (CMDB)
* cancelJobs.py
* Used for mass cancellation of all currently running jobs, typically used for vCenter maintenance.
* dailyErrors.py
* Pulls all protection job runs for the previous 24 hours and creates ServiceNow tickets for any jobs in a Failure state
* Sends an email summary to the backup teams for all job states on a daily basis.
* dailyProtectionReview.py
* Retrieves all VM objects from vCenter sources that are registerd to Cohesity and verifies that each object is protected or exempted in accordance with NDIT procedures.
* Reviews all protection jobs to ensure empty jobs are paused so they don't generate erroneous errors as well as ensures all popultated jobs are running.
* Creates SerivceNow Incidents for:
* Unprotected VMs
* Unprotected AppNames
* Populated Jobs that are paused
* findDuplicates.py
* Pulls all backup jobs per vCenter and determines if a VM is listed in more than one group.
* pauseOrResumeJob.py
* Used to mass pause or resume protectiong jobs, typically used for vCenter maintenance.
* sqlServerRegistration.py
* Determines if the VM that identified by '-s' has been setup as a SQL server source, if not register it as one and add it to the appropriate SQL server protection group provided by CMDB lookup or specified by '-p'.
* Pull all kSQL object types within Cohesity and ensure the ITD-COHESITY-DBA group has access to all objects.
* updateProtectionGroupTags.py
* Used to include or exclude vmWare protection job tags across a vCenter or individual job.
* beta/getSQLSourceErrorMessages.py
* Pulls any failed health check information on SQL servers that could prevent the SQL server from getting backed up
* Attempts to refresh the source to eliminate false positives
* Opens a ServiceNow ticket if refreshing the source does not resolve the issue.
ToDo
----
* dailyProtectionReview.py
* BUG:
* RFE:
* SeriveNow CMDB lookups will need to be used for AppName and exemnptions, see RFE 001 & RFE 002 sections in the script
* dailyErrors.py
* RFE:
* Determine wheter or not we need to open tickets for jobs in a warning state.
* findDuplicates.py
* RFE:
* Determine if this script needs to be ran on a routine basis.
* Add functionality to open tickets on duplicates
* updateProtectionGroupTags.py
* RFE:
* Update API calls to use class calls instead of GetFilteredRequest()
* Update tag lookup section to use the function in the class instead of the local function in the script
* sqlServerRegistration.py
* RFE:
* SeriveNow CMDB lookups will need to be used for AppName and exemnptions, see RFE 001 & RFE 002 sections in the script
* Update API calls to use class calls instead of GetFilteredRequest()
Example Python Script
---------------------
```
import json
import sys
import certifi
import urllib3
from datetime import date, timedelta
sys.path.insert(0, './classes/')
import cohesityAPI as cohesity
try:
# Define create and instantiate the API for a Cohesity cluster
mdn = cohesity.API('itdmdndpc01.nd.gov')
# Get the Auth Token and add it to the API header calls
mdnToken = mdn.GetAuthToken()
mdn.UpdateHeaders(mdnToken['accessToken'])
# Get a list of objects from an API call, for a full listing of API calls, see developer.cohesity.com
vms = mdn.GetFilteredRequest("/public/protectionSources/virtualMachines", "?protected=true")
# Print out an indented copy of the JSON data; use jsonparser.org or notepad++ to make it easier to read
print(json.dumps(vms, indent=4))
# Do something with the data based on the JSON output
for vm in vms:
print(vm['name'])
except OSError as cohesityError:
print('Cohesity Error: ' + cohesityError)
```
@@ -0,0 +1,98 @@
# Add ExcludeVMTagIds
$AllProtectionSourceObject = Get-CohesityProtectionSourceObject
$AllProtectionGroups = Get-CohesityProtectionJob -Names "ZM-TEST" | Sort-Object Name
ForEach ($ProtectionGroup in $AllProtectionGroups) {
Write-Warning ("Start " + $ProtectionGroup.name)
# Tag to Add to Exclude
$SourceObjectsToAddToExclude = $AllProtectionSourceObject | where-object { $_.Name -eq "Test" -and $_.ParentId -eq $ProtectionGroup.parentSourceId }
If ($ProtectionGroup.excludeVmTagIds) {
# if an exclusion already exists
$ProtectionGroup.excludeVmTagIds += , @($SourceObjectsToAddToExclude.Id)
Set-CohesityProtectionJob -ProtectionJob $ProtectionGroup -confirm:$false
} Else {
# if an exclusion does not already exist
$ProtectionGroup | Add-Member -MemberType NoteProperty -Name excludeVmTagIds -Value @(, @($SourceObjectsToAddToExclude.Id))
Set-CohesityProtectionJob -ProtectionJob $ProtectionGroup -confirm:$false
}
}
# Remove ExcludeVMTagIds
$AllProtectionSourceObject = Get-CohesityProtectionSourceObject
$AllProtectionGroups = Get-CohesityProtectionJob -Names "ZM-TEST" | Sort-Object Name
ForEach ($ProtectionGroup in $AllProtectionGroups) {
Write-Warning ("Start " + $ProtectionGroup.name)
# Tag to Remove to Exclude
$SourceObjectsToRemoveFromExclude = $AllProtectionSourceObject | Where-Object { $_.Name -eq "Test" -and $_.ParentId -eq $ProtectionGroup.parentSourceId }
if (! $ProtectionGroup.PSObject.Properties['excludeVmTagIds']) {
}
else {
$NewArray = @()
ForEach ($item in $ProtectionGroup.PSObject.Properties['excludeVmTagIds'].value) {
if ($item -ne $SourceObjectsToRemoveFromExclude.id) { $NewArray += $item }
}
If(@($NewArray).count -gt 0){
$ProtectionGroup.excludeVmTagIds = ,@($NewArray)
Set-CohesityProtectionJob -ProtectionJob $ProtectionGroup -confirm:$false
}
If(@($NewArray).count -eq 0){
$ProtectionGroup.PSObject.Properties.remove('excludeVmTagIds')
Set-CohesityProtectionJob -ProtectionJob $ProtectionGroup -confirm:$false
}
}
}
# Add VMTagIds
$AllProtectionSourceObject = Get-CohesityProtectionSourceObject
$AllProtectionGroups = Get-CohesityProtectionJob -Names "ZM-TEST" | Sort-Object Name
ForEach ($ProtectionGroup in $AllProtectionGroups) {
Write-Warning ("Start " + $ProtectionGroup.name)
# Tag to Add to Include
$SourceObjectsToAddToInclude = $AllProtectionSourceObject | where-object { $_.Name -eq "ITD-POC-Video" -and $_.ParentId -eq $ProtectionGroup.parentSourceId }
If ($ProtectionGroup.VmTagIds) {
# if an exclusion already exists
$ProtectionGroup.VmTagIds += , @($SourceObjectsToAddToInclude.Id)
Set-CohesityProtectionJob -ProtectionJob $ProtectionGroup -confirm:$false
} Else {
# if an exclusion does not already exist
$ProtectionGroup | Add-Member -MemberType NoteProperty -Name VmTagIds -Value @(, @($SourceObjectsToAddToInclude.Id))
Set-CohesityProtectionJob -ProtectionJob $ProtectionGroup -confirm:$false
}
}
# Remove VMTagIds
$AllProtectionSourceObject = Get-CohesityProtectionSourceObject
$AllProtectionGroups = Get-CohesityProtectionJob -Names "ZM-TEST" | Sort-Object Name
ForEach ($ProtectionGroup in $AllProtectionGroups) {
Write-Warning ("Start " + $ProtectionGroup.name)
# Tag to Remove from Include
$SourceObjectsToRemoveFromInclude = $AllProtectionSourceObject | where-object { $_.Name -eq "ITD-POC-Video" -and $_.ParentId -eq $ProtectionGroup.parentSourceId }
If ($ProtectionGroup.VmTagIds) {
# if an exclusion already exists
$ProtectionGroup.VmTagIds += , @($SourceObjectsToRemoveFromInclude.Id)
Set-CohesityProtectionJob -ProtectionJob $ProtectionGroup -confirm:$false
} Else {
# if an exclusion does not already exist
$ProtectionGroup | Add-Member -MemberType NoteProperty -Name VmTagIds -Value @(, @($SourceObjectsToRemoveFromInclude.Id))
Set-CohesityProtectionJob -ProtectionJob $ProtectionGroup -confirm:$false
}
}
@@ -0,0 +1,9 @@
<?xml version="1.0"?>
<package>
<metadata>
<id>ITD.Cohesity</id>
<version>$VERSIONHERE$</version>
<authors>Cliff Cogdill / Zack Meier</authors>
<description>Functions for Cohesity administration</description>
</metadata>
</package>
@@ -0,0 +1,10 @@
@{
RootModule = 'ITD.Cohesity.psm1'
ModuleVersion = '<ModuleVersion>'
GUID = '9b7f0813-ae13-4d3b-9b5a-608374755382'
Author = 'Cliff Cogdill / Zack Meier '
CompanyName = 'State of North Dakota'
PowerShellVersion = '7'
CompatiblePSEditions = 'Desktop','Core'
FunctionsToExport = @(<FunctionsToExport>)
}
@@ -0,0 +1,116 @@
<#
.SYNOPSIS
Run a single on-demand VMware incremental backup
.DESCRIPTION
Run a single on-demand VMware incremental backup
.EXAMPLE
Start-ITDCohesityOnDemandVMIncremental -ComputerName itdxyz.nd.gov
.NOTES
General notes
.COMPONENT
Cohesity
.ROLE
The role this cmdlet belongs to
.FUNCTIONALITY
The functionality that best describes this cmdlet
#>
function Start-ITDCohesityOnDemandVMIncremental {
[CmdletBinding()]
Param (
[Parameter()]
$ComputerName
)
begin {
}
process {
$CohesityVMwareVM = Get-CohesityProtectionSourceObject | Where-Object Name -eq $ComputerName #| Select-Object -Unique Id, Name, Environment, ParentId
Start-CohesityProtectionJob -Id (Get-CohesityProtectionJob | Where-Object sourceIds -Match $CohesityVmwareVM.parentId) -SourceIds $CohesityVMwareVM.id
}
end {
}
}
<#
.SYNOPSIS
Create a Protection Group based on VMware AppName Tag, one protection group for each VMware source.
.DESCRIPTION
Long description
.EXAMPLE
New-ITDCohesityProtectionGroup -AppName ITD-POC-zmeier
.NOTES
General notes
.COMPONENT
The component this cmdlet belongs to
.ROLE
The role this cmdlet belongs to
.FUNCTIONALITY
The functionality that best describes this cmdlet
#>
function New-ITDCohesityProtectionGroupWIP {
[CmdletBinding()]
Param (
[Parameter()]
[string[]]
$AppName,
[switch]
$NewBuildsOnly
)
begin {
}
process {
$AllProtectionSourceObject = Get-CohesityProtectionSourceObject
# create protection group based on appname, exclude placeholders
ForEach ($app in $AppName) {
$ProtectionJob = $null
$SourceObjects = $AllProtectionSourceObject | Where-Object Name -eq $app
$StartHour = Get-Random -Minimum 18 -Maximum 23
$StartDateTime = ([DateTime]::Today.AddHours($StartHour)).AddMinutes(30)
ForEach ($SourceObject in $SourceObjects) {
$Source = Get-CohesityProtectionSourceObject -Id $SourceObject.parentId
switch ($Source.environment) {
'kVMware' {
$ProtectionJobName = ($app + "@" + $Source.Name.split('.')[0])
}
'kPhysical' {
$ProtectionJobName = ($app + "@" + "physical")
}
}
$SRMTagPlaceholderObject = $AllProtectionSourceObject | Where-Object {$_.Name -eq "Placeholder" -and $_.ParentId -eq $SourceObject.parentId}
New-CohesityProtectionJob -Name $ProtectionJobName `
-PolicyName "ITD-Bronze" `
-VmTagIds $SourceObject.Id `
-ParentSourceId $SourceObject.parentId `
-ExcludeVmTagIds $SRMTagPlaceholderObject.id `
-Environment $Source.environment `
-Timezone "America/Chicago" `
-ScheduleStartTime $StartDateTime `
-StorageDomainName DefaultStorageDomain `
$indexingPolicy = [PSCustomObject]@{
disableIndexing = $false;
allowPrefixes = @('/');
denyPrefixes = @('/$Recycle.Bin', '/Windows', '/ProgramData', '/System Volume Information', '/Users/*/AppData', '/Recovery', '/usr', '/sys', '/proc', '/lib', '/grub', '/grub2', 'opt/splunk', '/splunk')
}
$ProtectionJob = Get-CohesityProtectionJob -Names $ProtectionJobName
$ProtectionJob | Add-Member -MemberType NoteProperty -Name indexingPolicy -Value $indexingPolicy
Set-CohesityProtectionJob -ProtectionJob $ProtectionJob -Confirm:$false
}
}
}
end {
}
}
@@ -0,0 +1,31 @@
# Add backup object to Cohesity User and Groups
This script lists users and groups and writes the output to a text file (addObjectToUserAccessList-clusterName.txt)
# Components
- addObjectToUserAccessList.ps1: the main powershell script
- cohesity-api.ps1: the Cohesity REST API helper module
Place the files in a folder together and run the main script like so:
# Command line example
```
./addObjectToUserAccessList.ps1 -vip mycluster `
-username myuser `
-domain mydomain.net `
-principal mydomain.net/myuser `
-addObject vm1, server1.mydomain.net `
-removeObject vm2, vm3 `
-addView view1, view2 `
-removeView view3, view4
```
# Parameters
- vip: Cohesity Cluster to connect to
- username: Cohesity username
- domain: (optional) Cohesity logon domain (defaults to local)
- principal: name(s) (comma separated) of user or group to modify (e.g. mydomain.net/myuser, or mylocaluser)
- addObject: (optional) names of registered objects to add to access list (comma separated)
- removeObject: (optional) names of registered objects to remove from access list (comma separated)
- addView: (optional) names of views to add to access list (comma separated)
- removeView: (optional) names of views to remove from access list (comma separated)
@@ -0,0 +1,27 @@
#Add to Local Security Policy
function Add-ServiceLogonRight([string] $Username) {
Write-Host "Enable ServiceLogonRight for $Username"
$tmp = New-TemporaryFile
secedit /export /cfg "$tmp.inf" | Out-Null
(Get-Content -Encoding ascii "$tmp.inf") -replace '^SeServiceLogonRight .+', "`$0,$Username" | sc -Encoding ascii "$tmp.inf"
secedit /import /cfg "$tmp.inf" /db "$tmp.sdb" | Out-Null
secedit /configure /db "$tmp.sdb" /cfg "$tmp.inf" | Out-Null
Remove-Item $tmp* -ea 0
}
Add-ServiceLogonRight -Username svccohesityadm
sc.exe config "CohesityAgent" obj="NDGOV\svccohesityadm" password="xxxxxxxxx"
#Prompt for secure password & convert to plaintext for DataStage use
$dsSecurePass = Read-Host -AsSecureString "Enter $($dsUser) password"
$dsBinaryPass = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($dsSecurePass)
$dsPass = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($dsBinaryPass)
[System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($dsBinaryPass)
@@ -0,0 +1,120 @@
### process commandline arguments
[CmdletBinding()]
param (
[Parameter(Mandatory = $True)][string]$vip, #the cluster to connect to (DNS name or IP)
[Parameter(Mandatory = $True)][string]$username, #username (local or AD)
[Parameter()][string]$domain = 'local', #local or AD domain
[Parameter()][array]$principal,
[Parameter()][array]$addObject,
[Parameter()][array]$addView,
[Parameter()][array]$removeObject,
[Parameter()][array]$removeView
)
### source the cohesity-api helper code
. $(Join-Path -Path $PSScriptRoot -ChildPath cohesity-api.ps1)
### authenticate
apiauth -vip $vip -username $username -domain $domain
$users = api get users?_includeTenantInfo=true
$groups = api get groups?_includeTenantInfo=true
$sources = api get "protectionSources"
$views = api get views
function getObjectId($objectName){
$global:_object_id = $null
function get_nodes($obj){
if($obj.protectionSource.name -eq $objectName){
$global:_object_id = $obj.protectionSource.id
break
}
if($obj.name -eq $objectName){
$global:_object_id = $obj.id
break
}
if($obj.PSObject.Properties['nodes']){
foreach($node in $obj.nodes){
if($null -eq $global:_object_id){
get_nodes $node
}
}
}
}
foreach($source in $sources){
if($null -eq $global:_object_id){
get_nodes $source
}
}
return $global:_object_id
}
foreach($p in $principal){
if($p -match '/'){
$d, $p = $p.split('/')
}else{
$d = 'local'
}
$ptype = 'user'
$thisPrincipal = $users | Where-Object {$_.username -eq $p -and $_.domain -eq $d}
if(!$thisPrincipal){
$ptype = 'group'
$thisPrincipal = $groups | Where-Object {$_.name -eq $p -and $_.domain -eq $d}
}
if(!$thisPrincipal){
Write-Host "Principal $d/$p not found!" -ForegroundColor Yellow
continue
}
$access = api get principals/protectionSources?sids=$($thisPrincipal.sid)
$newAccess = @{
"sourcesForPrincipals" = @(
@{
"sid" = $thisPrincipal.sid;
"protectionSourceIds" = [array]$access.protectionSources.id;
"viewNames" = [array]$access.views.name
}
)
}
foreach($objectName in $addObject){
$objectId = getObjectId $objectName
if(!$objectId){
Write-Host "Object $objectName not found!" -ForegroundColor Yellow
continue
}
"Adding $objectName"
$newAccess.sourcesForPrincipals[0].protectionSourceIds = @($newAccess.sourcesForPrincipals[0].protectionSourceIds + $objectId)
}
foreach($objectName in $removeObject){
$objectId = getObjectId $objectName
if($objectId){
"Removing $objectName"
$newAccess.sourcesForPrincipals[0].protectionSourceIds = @($newAccess.sourcesForPrincipals[0].protectionSourceIds | Where-Object {$_ -ne $objectId})
}
}
foreach($viewName in $addView){
$view = $views.views | Where-Object {$_.name -eq $viewName}
if(!$view){
Write-Host "View $viewName not found" -ForegroundColor Yellow
continue
}
"Adding $viewName"
$newAccess.sourcesForPrincipals[0].viewNames = @($newAccess.sourcesForPrincipals[0].viewNames + $view.name)
}
foreach($viewName in $removeView){
"Removing $viewName"
$newAccess.sourcesForPrincipals[0].viewNames = @($newAccess.sourcesForPrincipals[0].viewNames | Where-Object {$_ -ne $viewName})
}
$newAccess.sourcesForPrincipals[0].protectionSourceIds = @($newAccess.sourcesForPrincipals[0].protectionSourceIds | Sort-Object -Unique)
$newAccess.sourcesForPrincipals[0].viewNames = @($newAccess.sourcesForPrincipals[0].viewNames | Sort-Object -Unique)
$thisPrincipal.restricted = $True
if($ptype -eq 'user'){
$null = api put users $thisPrincipal
}else{
$null = api put groups $thisPrincipal
}
$null = api put principals/protectionSources $newAccess
}
@@ -0,0 +1,813 @@
# . . . . . . . . . . . . . . . . . . .
# PowerShell Module for Cohesity API
# Version 2020.12.22 - Brian Seltzer
# . . . . . . . . . . . . . . . . . . .
#
# 2020.10.16 - added password parameter to storePasswordInFile function
# 2020.10.20 - code cleanup (moved old version history to end of file)
# 2020.12.22 - added v2 support for file download
#
# . . . . . . . . . . . . . . . . . . . . . . . .
$versionCohesityAPI = '2020.12.22'
# demand modern powershell version (must support TLSv1.2)
if($Host.Version.Major -le 5 -and $Host.Version.Minor -lt 1){
Write-Warning "PowerShell version must be upgraded to 5.1 or higher to connect to Cohesity!"
Pause
exit
}
$REPORTAPIERRORS = $true
$pwfile = $(Join-Path -Path $PSScriptRoot -ChildPath YWRtaW4)
$apilogfile = $(Join-Path -Path $PSScriptRoot -ChildPath cohesity-api-debug.log)
# platform detection ==========================================================================
if ($PSVersionTable.Platform -eq 'Unix') {
$CONFDIR = '~/.cohesity-api'
if ($(Test-Path $CONFDIR) -eq $false) { $null = New-Item -Type Directory -Path $CONFDIR}
}else{
$registryPath = 'HKCU:\Software\Cohesity-API'
$WEBCLI = New-Object System.Net.WebClient;
}
if($PSVersionTable.PSEdition -eq 'Desktop'){
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = { return $true }
$ignoreCerts = @"
public class SSLHandler
{
public static System.Net.Security.RemoteCertificateValidationCallback GetSSLHandler()
{
return new System.Net.Security.RemoteCertificateValidationCallback((sender, certificate, chain, policyErrors) => { return true; });
}
}
"@
if(!("SSLHandler" -as [type])){
Add-Type -TypeDefinition $ignoreCerts
}
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = [SSLHandler]::GetSSLHandler()
}
function __writeLog($logmessage){
"$(Get-Date): $logmessage" | Out-File -FilePath $apilogfile -Append
}
# authentication functions ========================================================================
function apiauth($vip, $username='helios', $domain='local', $passwd=$null, $password = $null, $tenantId = $null, [switch] $quiet, [switch] $noprompt, [switch] $updatePassword, [switch] $helios, [switch] $useApiKey){
if(-not $vip){
if($helios){
$vip = 'helios.cohesity.com'
}else{
Write-Host 'vip is required' -foregroundcolor Yellow
break
}
}
# parse domain\username or username@domain
if($username.Contains('\')){
$domain, $username = $username.Split('\')
}
if($password){ $passwd = $password }
if($updatePassword){
$fpasswd = Get-CohesityAPIPasswordFromFile -vip $vip -username $username -domain $domain
if($fpasswd){
storePasswordInFile -vip $vip -username $username -domain $domain
}else{
Set-CohesityAPIPassword -vip $vip -username $username -domain $domain
}
}
# get password
if(!$passwd){
$passwd = Get-CohesityAPIPassword -vip $vip -username $username -domain $domain
if(!$passwd -and !$noprompt){
Set-CohesityAPIPassword -vip $vip -username $username -domain $domain
$passwd = Get-CohesityAPIPassword -vip $vip -username $username -domain $domain
}
if(!$passwd){
Write-Host "No password provided for $username at $vip" -ForegroundColor Yellow
$global:AUTHORIZED = $false
break
}
}
$body = ConvertTo-Json @{
'domain' = $domain;
'username' = $username;
'password' = $passwd
}
$global:APIROOT = 'https://' + $vip + '/irisservices/api/v1'
$global:APIROOTv2 = 'https://' + $vip + '/v2/'
$HEADER = @{'accept' = 'application/json'; 'content-type' = 'application/json'}
if($useApiKey){
$HEADER['apiKey'] = $passwd
$global:HEADER = $HEADER
$global:AUTHORIZED = $true
$global:CLUSTERSELECTED = $true
$cluster = api get cluster
if($cluster){
if(!$quiet){ Write-Host "Connected!" -foregroundcolor green }
}else{
$global:AUTHORIZED = $false
}
}elseif($vip -eq 'helios.cohesity.com' -or $helios){
# Authenticate Helios
$HEADER['apiKey'] = $passwd
$URL = 'https://helios.cohesity.com/mcm/clusters/connectionStatus'
try{
if($PSVersionTable.Edition -eq 'Core'){
$global:HELIOSALLCLUSTERS = Invoke-RestMethod -Method get -Uri $URL -Header $HEADER -SkipCertificateCheck
}else{
$global:HELIOSALLCLUSTERS = Invoke-RestMethod -Method get -Uri $URL -Header $HEADER
}
$global:HELIOSCONNECTEDCLUSTERS = $global:HELIOSALLCLUSTERS | Where-Object connectedToCluster -eq $true
$global:HEADER = $HEADER
$global:AUTHORIZED = $true
$global:CLUSTERSELECTED = $false
$global:CLUSTERREADONLY = $false
if(!$quiet){ Write-Host "Connected!" -foregroundcolor green }
}catch{
$global:AUTHORIZED = $false
__writeLog $_.ToString()
if($_.ToString().contains('"message":')){
Write-Host (ConvertFrom-Json $_.ToString()).message -foregroundcolor yellow
}else{
Write-Host $_.ToString() -foregroundcolor yellow
}
}
}else{
# Authenticate Cluster
$url = $APIROOT + '/public/accessTokens'
try {
# authenticate
if($PSVersionTable.PSEdition -eq 'Core'){
$auth = Invoke-RestMethod -Method Post -Uri $url -Header $HEADER -Body $body -SkipCertificateCheck
}else{
$auth = Invoke-RestMethod -Method Post -Uri $url -Header $HEADER -Body $body
}
# set file transfer details
if($PSVersionTable.Platform -eq 'Unix'){
$global:CURLHEADER = "authorization: $($auth.tokenType) $($auth.accessToken)"
}else{
$WEBCLI.Headers['authorization'] = $auth.tokenType + ' ' + $auth.accessToken;
}
# store token
$global:AUTHORIZED = $true
$global:CLUSTERSELECTED = $true
$global:CLUSTERREADONLY = $false
$global:HEADER = @{'accept' = 'application/json';
'content-type' = 'application/json';
'authorization' = $auth.tokenType + ' ' + $auth.accessToken
}
if($tenantId){
$global:HEADER['x-impersonate-tenant-id'] = "$tenantId/"
}
if(!$quiet){ Write-Host "Connected!" -foregroundcolor green }
}catch{
$global:AUTHORIZED = $false
__writeLog $_.ToString()
$global:AUTHORIZED = $false
if($REPORTAPIERRORS){
if($_.ToString().contains('"message":')){
Write-Host (ConvertFrom-Json $_.ToString()).message -foregroundcolor yellow
}else{
Write-Host $_.ToString() -foregroundcolor yellow
}
}
}
}
}
# select helios access cluster
function heliosCluster($clusterName, [switch] $verbose){
if($clusterName -and $HELIOSCONNECTEDCLUSTERS){
if(! ($clusterName -is [string])){
$clusterName = $clusterName.name
}
$cluster = $HELIOSCONNECTEDCLUSTERS | Where-Object name -eq $clusterName
if($cluster){
$global:HEADER.accessClusterId = $cluster.clusterId
$global:CLUSTERSELECTED = $true
$global:CLUSTERREADONLY = (api get /mcm/config).mcmReadOnly
if($verbose){
Write-Host "Connected ($($cluster.name))" -ForegroundColor Green
}
}else{
Write-Host "Cluster $clusterName not connected to Helios" -ForegroundColor Yellow
$global:CLUSTERSELECTED = $false
return $null
}
}else{
$HELIOSCONNECTEDCLUSTERS | Sort-Object -Property name | Select-Object -Property name, clusterId, softwareVersion
"`ntype heliosCluster <clustername> to connect to a cluster"
}
if (-not $global:AUTHORIZED){
if($REPORTAPIERRORS){
Write-Host 'Please use apiauth to connect to helios' -foregroundcolor yellow
}
}
}
function heliosClusters(){
return $HELIOSCONNECTEDCLUSTERS | Sort-Object -Property name
}
# terminate authentication
function apidrop([switch] $quiet){
$global:AUTHORIZED = $false
$global:HEADER = ''
$global:HELIOSALLCLUSTERS = $null
$global:HELIOSCONNECTEDCLUSTERS = $null
if(!$quiet){ Write-Host "Disonnected!" -foregroundcolor green }
}
# api call functions ==============================================================================
$methods = 'get', 'post', 'put', 'delete'
function api($method, $uri, $data, $version=1, [switch]$v2){
if (-not $global:AUTHORIZED){
if($REPORTAPIERRORS){
Write-Host 'Not authenticated to a cohesity cluster' -foregroundcolor yellow
if($MyInvocation.PSCommandPath){
exit 1
}
}
}else{
if($method -ne 'get' -and $global:CLUSTERREADONLY -eq $true){
Write-Host "Cluster connection is READ-ONLY" -ForegroundColor Yellow
break
}
if (-not $methods.Contains($method)){
if($REPORTAPIERRORS){
Write-Host "invalid api method: $method" -foregroundcolor yellow
}
break
}
try {
if($version -eq 2 -or $v2){
$url = $APIROOTv2 + $uri
}else{
if ($uri[0] -ne '/'){ $uri = '/public/' + $uri}
$url = $APIROOT + $uri
}
$body = ConvertTo-Json -Depth 100 $data
if ($PSVersionTable.PSEdition -eq 'Core'){
if($body){
$result = Invoke-RestMethod -Method $method -Uri $url -Body $body -Header $HEADER -SkipCertificateCheck
}else{
$result = Invoke-RestMethod -Method $method -Uri $url -Header $HEADER -SkipCertificateCheck
}
}else{
$result = Invoke-RestMethod -Method $method -Uri $url -Body $body -Header $HEADER
}
return $result
}catch{
__writeLog $_.ToString()
if($REPORTAPIERRORS){
if($_.ToString().contains('"message":')){
Write-Host (ConvertFrom-Json $_.ToString()).message -foregroundcolor yellow
}else{
Write-Host $_.ToString() -foregroundcolor yellow
}
}
}
}
}
# file download function
function fileDownload($uri, $fileName, $version=1, [switch]$v2){
if (-not $global:AUTHORIZED){ Write-Host 'Please use apiauth to connect to a cohesity cluster' -foregroundcolor yellow; break }
try {
if($version -eq 2 -or $v2){
$url = $APIROOTv2 + $uri
}else{
if ($uri[0] -ne '/'){ $uri = '/public/' + $uri}
$url = $APIROOT + $uri
}
if ($PSVersionTable.Platform -eq 'Unix'){
curl -k -s -H "$global:CURLHEADER" -o "$fileName" "$url"
}else{
if($fileName -notmatch '\\'){
$fileName = $(Join-Path -Path $PSScriptRoot -ChildPath $fileName)
}
$WEBCLI.DownloadFile($url, $fileName)
}
}catch{
__writeLog $_.ToString()
$_.ToString()
if($_.ToString().contains('"message":')){
Write-Host (ConvertFrom-Json $_.ToString()).message -foregroundcolor yellow
}else{
Write-Host $_.ToString() -foregroundcolor yellow
}
}
}
# date functions ==================================================================================
function timeAgo([int64] $age, [string] $units){
$currentTime = [int64](((get-date).ToUniversalTime())-([datetime]"1970-01-01 00:00:00")).TotalSeconds*1000000
$secs=@{'seconds'= 1; 'sec'= 1; 'secs' = 1;
'minutes' = 60; 'min' = 60; 'mins' = 60;
'hours' = 3600; 'hour' = 3600;
'days' = 86400; 'day' = 86400;
'weeks' = 604800; 'week' = 604800;
'months' = 2628000; 'month' = 2628000;
'years' = 31536000; 'year' = 31536000 }
$age = $age * $secs[$units.ToLower()] * 1000000
return [int64] ($currentTime - $age)
}
function usecsToDate($usecs){
$unixTime=$usecs/1000000
$origin = ([datetime]'1970-01-01 00:00:00')
return $origin.AddSeconds($unixTime).ToLocalTime()
}
function dateToUsecs($datestring){
if($datestring -isnot [datetime]){ $datestring = [datetime] $datestring }
$usecs = [int64](($datestring.ToUniversalTime())-([datetime]"1970-01-01 00:00:00")).TotalSeconds*1000000
$usecs
}
# password functions ==============================================================================
function Get-CohesityAPIPassword($vip, $username, $domain='local'){
# parse domain\username or username@domain
if($username.Contains('\')){
$domain, $username = $username.Split('\')
}
if($username.Contains('@')){
$username, $domain = $username.Split('@')
}
$passwd = Get-CohesityAPIPasswordFromFile -vip $vip -username $username -domain $domain
if($passwd){
return $passwd
}
$keyName = "$vip`:$domain`:$username"
if($PSVersionTable.Platform -eq 'Unix'){
# Unix
$keyFile = "$CONFDIR/$keyName"
if (Test-Path $keyFile) {
$key, $storedPassword = Get-Content $keyFile
return Unprotect-CohesityAPIPassword $key $storedPassword
}
}else{
# Windows
$storedPassword = Get-ItemProperty -Path "$registryPath" -Name "$keyName" -ErrorAction SilentlyContinue
If (($null -ne $storedPassword) -and ($storedPassword.Length -ne 0)) {
$securePassword = $storedPassword.$keyName | ConvertTo-SecureString
return [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR( $securePassword ))
}
}
}
function Get-CohesityAPIPasswordFromFile($vip, $username, $domain){
$pwlist = Get-Content -Path $pwfile -ErrorAction SilentlyContinue
foreach($pwitem in $pwlist){
$v, $d, $u, $cpwd = $pwitem.split(":", 4)
if($v -eq $vip -and $d -eq $domain -and $u -eq $username){
return [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($cpwd))
}
}
return $null
}
function storePasswordInFile($vip='helios.cohesity.com', $username='helios', $domain='local', [switch]$helios, $password=$null){
# parse domain\username or username@domain
if($username.Contains('\')){
$domain, $username = $username.Split('\')
}
if($username.Contains('@')){
$username, $domain = $username.Split('@')
}
if($vip -eq 'helios.cohesity.com' -and $username -eq 'helios' -and ! $helios){
# prompt for vip
__writeLog "Prompting for VIP, USERNAME, DOMAIN"
$newVip = Read-Host -Prompt "Enter VIP ($vip)"
if($newVip -ne ''){ $vip = $newVip }
# prompt for domain
$newDomain = Read-Host -Prompt "Enter domain ($domain)"
if($newDomain -ne ''){ $domain = $newDomain }
# prompt for username
$newUsername = Read-Host -Prompt "Enter username ($username)"
if($newUsername -ne ''){ $username = $newUsername }
}
# prompt for password
__writeLog "Prompting for Password"
if(!$password){
$secureString = Read-Host -Prompt "Enter password for $domain\$username at $vip" -AsSecureString
$passwd = [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR( $secureString ))
}else{
$passwd = $password
}
$opwd = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($passwd))
$pwlist = Get-Content -Path $pwfile -ErrorAction SilentlyContinue
$updatedContent = ''
$foundPwd = $false
foreach($pwitem in $pwlist){
$v, $d, $u, $cpwd = $pwitem.split(":", 4)
# update existing
if($v -eq $vip -and $d -eq $domain -and $u -eq $username){
$foundPwd = $true
$updatedContent += "{0}:{1}:{2}:{3}`n" -f $vip, $domain, $username, $opwd
# other existing records
}else{
if($pwitem -ne ''){
$updatedContent += "{0}`n" -f $pwitem
}
}
}
# add new
if(!$foundPwd){
$updatedContent += "{0}:{1}:{2}:{3}`n" -f $vip, $domain, $username, $opwd
}
$updatedContent | out-file -FilePath $pwfile
Write-Host "Password stored!" -ForegroundColor Green
}
function Set-CohesityAPIPassword($vip, $username, $domain='local', $passwd=$null){
# prompt for vip
if(-not $vip){
__writeLog "Prompting for VIP"
Write-Host 'VIP: ' -foregroundcolor green -nonewline
$vip = Read-Host
if(-not $vip){Write-Host 'vip is required' -foregroundcolor red; break}
}
# prompt for username
if(-not $username){
__writeLog "Prompting for Username"
Write-Host 'Username: ' -foregroundcolor green -nonewline
$username = Read-Host
if(-not $username){Write-Host 'username is required' -foregroundcolor red; break}
}
# parse domain\username or username@domain
if($username.Contains('\')){
$domain, $username = $username.Split('\')
}
if($username.Contains('@')){
$username, $domain = $username.Split('@')
}
$keyName = "$vip`:$domain`:$username"
if(!$passwd){
__writeLog "Prompting for Password"
$secureString = Read-Host -Prompt "Enter password for $username at $vip" -AsSecureString
$passwd = [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR( $secureString ))
}
if($PSVersionTable.Platform -eq 'Unix'){
# Unix
$keyFile = "$CONFDIR/$keyName"
$key = New-AesKey
$key | Out-File $keyFile
Protect-CohesityAPIPassword $key $passwd | Out-File $keyFile -Append
}else{
# Windows
$securePassword = ConvertTo-SecureString -String $passwd -AsPlainText -Force
$encryptedPasswordText = $securePassword | ConvertFrom-SecureString
if(!(Test-Path $registryPath)){
New-Item -Path $registryPath -Force | Out-Null
}
Set-ItemProperty -Path "$registryPath" -Name "$keyName" -Value "$encryptedPasswordText"
}
}
# security functions ==============================================================================
function New-AesManagedObject($key, $IV){
$aesManaged = New-Object "System.Security.Cryptography.AesManaged"
$aesManaged.Mode = [System.Security.Cryptography.CipherMode]::CBC
$aesManaged.Padding = [System.Security.Cryptography.PaddingMode]::Zeros
$aesManaged.BlockSize = 128
$aesManaged.KeySize = 256
if($IV){
if($IV.getType().Name -eq "String"){
$aesManaged.IV = [System.Convert]::FromBase64String($IV)
}else{
$aesManaged.IV = $IV
}
}
if($key){
if($key.getType().Name -eq "String") {
$aesManaged.Key = [System.Convert]::FromBase64String($key)
}else{
$aesManaged.Key = $key
}
}
$aesManaged
}
function New-AesKey() {
$aesManaged = New-AesManagedObject
$aesManaged.GenerateKey()
[System.Convert]::ToBase64String($aesManaged.Key)
}
function Protect-CohesityAPIPassword($key, $unencryptedString) {
$bytes = [System.Text.Encoding]::UTF8.GetBytes($unencryptedString)
$aesManaged = New-AesManagedObject $key
$encryptor = $aesManaged.CreateEncryptor()
$encryptedData = $encryptor.TransformFinalBlock($bytes, 0, $bytes.Length);
[byte[]] $fullData = $aesManaged.IV + $encryptedData
$aesManaged.Dispose()
[System.Convert]::ToBase64String($fullData)
}
function Unprotect-CohesityAPIPassword($key, $encryptedStringWithIV) {
$bytes = [System.Convert]::FromBase64String($encryptedStringWithIV)
$IV = $bytes[0..15]
$aesManaged = New-AesManagedObject $key $IV
$decryptor = $aesManaged.CreateDecryptor();
$unencryptedData = $decryptor.TransformFinalBlock($bytes, 16, $bytes.Length - 16);
$aesManaged.Dispose()
[System.Text.Encoding]::UTF8.GetString($unencryptedData).Trim([char]0)
}
# developer tools =================================================================================
function saveJson($object, $jsonFile = './debug.json'){
$object | ConvertTo-Json -Depth 99 | out-file -FilePath $jsonFile
}
function loadJson($jsonFile = './debug.json'){
return Get-Content $jsonFile | ConvertFrom-Json
}
function json2code($json = '', $jsonFile = '', $psFile = 'myObject.ps1'){
if($jsonFile -ne ''){
$json = (Get-Content $jsonFile) -join "`n"
}
$json = $json | ConvertFrom-Json | ConvertTo-Json -Depth 99
$pscode = ''
foreach ($line in $json.split("`n")) {
$line = $line.TrimEnd()
# preserve end of line character
$finalEntry = $true
if ($line[-1] -eq ',') {
$finalEntry = $false
$line = $line -replace ".$"
}
# key value delimiter :
$key, $value = $line.split(':', 2)
# line is braces only
$key = $key.Replace('{', '@{').Replace('[','@(').Replace(']', ')')
if ($value) {
$value = $value.trim()
# value is quoted text
if ($value[0] -eq '"') {
$line = "$key = $value"
}
# value is opening { brace
elseif ('{' -eq $value) {
$value = $value.Replace('{', '@{')
$line = "$key = $value"
}
# value is opening [ list
elseif ('[' -eq $value) {
$value = $value.Replace('[', '@(')
$line = "$key = $value"
}
# empty braces
elseif ('{}' -eq $value) {
$value = '@{}'
$line = "$key = $value"
}
# empty list
elseif ('[]' -eq $value) {
$value = '@()'
$line = "$key = $value"
}
# value is opening ( list
elseif ('[' -eq $value) {
$value = $value.Replace('[', '@(')
$line = "$key = $value"
}
# value is a boolean
elseif ($value -eq 'true') {
$line = "$key = " + '$true'
}
elseif ($value -eq 'false') {
$line = "$key = " + '$false'
}
# null
elseif ($value -eq 'null') {
$line = "$key = " + '$null'
}
else {
# value is numeric
if ($value -as [long] -or $value -eq '0') {
$line = "$($key) = $value"
}
else {
# delimeter : was inside of quotes
$line = "$($key):$($value)"
}
}
}
else {
# was no value on this line
$line = $key
}
# replace end of line character ;
if (! $finalEntry) {
$line = "$line;"
}
$pscode += "$line`n"
}
$pscode = '$myObject = ' + $pscode
$pscode | out-file $psFile
return $pscode
}
# add a property
function setApiProperty{
[CmdletBinding()]
Param(
[Parameter(Mandatory = $True)][string]$name,
[Parameter(Mandatory = $True)][System.Object]$value,
[Parameter(Mandatory = $True, ValueFromPipeline = $True)][System.Object]$object
)
if(! $object.PSObject.Properties[$name]){
$object | Add-Member -MemberType NoteProperty -Name $name -Value $value
}else{
$object.$name = $value
}
}
# delete a propery
function delApiProperty{
[CmdletBinding()]
Param(
[Parameter(Mandatory = $True)][string]$name,
[Parameter(Mandatory = $True, ValueFromPipeline = $True)][System.Object]$object
)
$object.PSObject.Members.Remove($name)
}
# show properties of an object
function showProps{
param (
[Parameter(Mandatory = $True)]$obj,
[Parameter()]$parent = 'myobject',
[Parameter()]$search = $null
)
if($obj.getType().Name -eq 'String' -or $obj.getType().Name -eq 'Int64'){
if($null -ne $search){
if($parent.ToLower().Contains($search) -or ($obj.getType().Name -eq 'String' -and $obj.ToLower().Contains($search))){
"$parent = $obj"
}
}else{
"$parent = $obj"
}
}else{
foreach($prop in $obj.PSObject.Properties | Sort-Object -Property Name){
if($($prop.Value.GetType().Name) -eq 'PSCustomObject'){
$thisObj = $prop.Value
showProps $thisObj "$parent.$($prop.Name)" $search
}elseif($($prop.Value.GetType().Name) -eq 'Object[]'){
$thisObj = $prop.Value
$x = 0
foreach($item in $thisObj){
showProps $thisObj[$x] "$parent.$($prop.Name)[$x]" $search
$x += 1
}
}else{
if($null -ne $search){
if($prop.Name.ToLower().Contains($search.ToLower()) -or ($prop.Value.getType().Name -eq 'String' -and $prop.Value.ToLower().Contains($search.ToLower()))){
"$parent.$($prop.Name) = $($prop.Value)"
}
}else{
"$parent.$($prop.Name) = $($prop.Value)"
}
}
}
}
}
# convert syntax to python
function py($p){
$py = $p.replace("$","").replace("].","]['").replace(".","']['")
if($py[-1] -ne ']'){
$py += "']"
}
$py
}
# self updater
function cohesityAPIversion([switch]$update){
if($update){
$repoURL = 'https://raw.githubusercontent.com/bseltz-cohesity/scripts/master/powershell'
(Invoke-WebRequest -Uri "$repoURL/cohesity-api/cohesity-api.ps1").content | Out-File -Force cohesity-api.ps1; (Get-Content cohesity-api.ps1) | Set-Content cohesity-api.ps1
Write-Host "Cohesity-API version updated! Please restart PowerShell"
}else{
Write-Host "Cohesity-API version $versionCohesityAPI" -ForegroundColor Green
}
}
# paged view list
function getViews([switch]$includeInactive){
$myViews = @()
$views = $null
while(! $views){
if($includeInactive){
$views = api get views?includeInactive=true
}else{
$views = api get views
}
}
$myViews += $views.views
$lastResult = $views.lastResult
while(! $lastResult){
$lastViewId = $views.views[-1].viewId
$views = $null
while(! $views){
if($includeInactive){
$views = api get "views?maxViewId=$lastViewId&includeInactive=true"
}else{
$views = api get views?maxViewId=$lastViewId
}
}
$lastResult = $views.lastResult
$myViews += $views.views
}
return $myViews
}
# old version history
# . . . . . . . . . . . . . . . . . . . . . . . .
# 0.06 - Consolidated Windows and Unix versions - June 2018
# 0.07 - Added saveJson, loadJson and json2code utility functions - Feb 2019
# 0.08 - added -prompt to prompt for password rather than save - Mar 2019
# 0.09 - added setApiProperty / delApiProperty - Apr 2019
# 0.10 - added $REPORTAPIERRORS constant - Apr 2019
# 0.11 - added storePassword function and username parsing - Aug 2019
# 0.12 - added -password to apiauth function - Oct 2019
# 0.13 - added showProps function - Nov 2019
# 0.14 - added storePasswordFromInput function - Dec 2019
# 0.15 - added support for PS Core on Windows - Dec 2019
# 0.16 - added ServicePoint connection workaround - Jan 2020
# 0.17 - fixed json2code line endings on Windows - Jan 2020
# 0.18 - added REINVOKE - Jan 2020
# 0.19 - fixed password encryption for PowerShell 7.0 - Mar 2020
# 0.20 - refactored, added apipwd, added helios access - Mar 2020
# 0.21 - helios changes - Mar 2020
# 0.22 - added password file storage - Apr 2020
# 0.23 - added self updater - Apr 2020
# 0.24 - added delete with body - Apr 2020
# 0.25 - added paged view list - Apr 2020
# 0.26 - added support for tenants - May 2020
# 0.27 - added support for Iris API Key - May 2020
# 0.28 - added reprompt for password, debug log - June 2020
# 0.29 - update storePasswordInFile - June 2020
# 2020.06.04 - updated version numbering - June 2020
# 2020.06.16 - improved REINVOKE - June 2020
# 2020-06.25 - added API v2 support (-version 2) or (-v2)
# 2020.07.08 - removed timout
# 2020.07.20 - fixed dateToUsecs for international date formats
# 2020.07.30 - quiet ssl handler
# 2020.08.08 - fixed timezone issue
# 2020.10.02 - set PROMPTFORPASSWORDCHANGE to false
# 2020.10.05 - retired REINVOKE
# 2020.10.06 - exit script when attempting unauthenticated api call
# 2020.10.13 - fixed timeAgo function for i14n
# . . . . . . . . . . . . . . . . . . . . . . . .
@@ -0,0 +1,32 @@
# Starter pipeline
# Start with a minimal pipeline that you can customize to build and deploy your code.
# Add steps that build, run tests, deploy, and more:
# https://aka.ms/yaml
trigger:
- main
pool:
vmImage: ubuntu-latest
variables:
fName: 'Cohesity-Package'
pyEnv: $(System.DefaultWorkingDirectory)/python
steps:
- task: ArchiveFiles@2
inputs:
rootFolderOrFile: '$(System.DefaultWorkingDirectory)'
includeRootFolder: false
archiveType: 'zip'
archiveFile: '$(Build.ArtifactStagingDirectory)/$(Build.BuildId).zip'
replaceExistingArchive: true
- task: rename@0
inputs:
Command: 'rename'
SourceFile: '$(Build.ArtifactStagingDirectory)/$(Build.BuildId).zip'
NewName: '$(fName).zip'
- upload: $(Build.ArtifactStagingDirectory)/$(fName).zip
displayName: 'Upload Package'
artifact: drop
@@ -0,0 +1,109 @@
import json
import sys
import certifi
import urllib3
import time
import pymsteams
from datetime import date, timedelta
sys.path.insert(0, './classes/')
import cohesityAPI as cohesity
import serviceNowAPI as serviceNow
debug=0
failureLimit=1
iteration=0
errorCount = 0
itdTeamsMessage = pymsteams.connectorcard("https://ndgov.webhook.office.com/webhookb2/aad89030-8f69-4a4d-a853-c882cbb01a10@2dea0464-da51-4a88-bae2-b3db94bc0c54/IncomingWebhook/d3c07921419c4920810d0c32558c05e3/edcc1502-1ff0-4c3e-9fe2-1ce3f9f7981a")
try:
# Create the API object for Cohesity
mdn = cohesity.API('itdmdndpc01.nd.gov')
mdnToken = mdn.GetAuthToken()
mdn.UpdateHeaders(mdnToken['accessToken'])
# Create the API object for ServiceNow
ticketGenerator = serviceNow.API('northdakota.service-now.com')
# Get all SQL resgistered SQL servers from Cohesity
vms = mdn.GetFilteredRequest("/public/protectionSources/registrationInfo","?environments=kSQL")
# Loop through all servers and check for health status issues
for vm in vms['rootNodes']:
for check in vm['registrationInfo']['registeredAppsInfo'][0]['hostSettingsCheckResults']:
if check['resultType'] == "kFail":
errorCount += 1
# If/Else statement for Debug only, added iteration limit to prevent debug of multiple servers at once
if (debug == 1) and (iteration < failureLimit):
print("Hostname: " + vm['rootNode']['name'])
print("NodeID: " + str(vm['rootNode']['id']))
print(check['resultType'] + ": " + check['userMessage'])
iteration += 1
#Try to refresh the source to see if the error goes away
print("Attempting to refresh " + vm['rootNode']['name'])
mdn.RefreshSource(vm['rootNode']['id'])
time.sleep(15)
#Pull the source registration information again and output
refreshedData = mdn.GetFilteredRequest("/public/protectionSources/registrationInfo","?ids=" + str(vm['rootNode']['id']))
print("Refreshed Data")
#print(json.dumps(refreshedData,indent=4))
for refreshedVM in refreshedData['rootNodes']:
persistantError = 0
for refreshedCheck in refreshedVM['registrationInfo']['registeredAppsInfo'][0]['hostSettingsCheckResults']:
if (refreshedCheck['resultType']=="kFail"):
print("Hostname: " + refreshedVM['rootNode']['name'])
print("NodeID: " + str(refreshedVM['rootNode']['id']))
print(refreshedCheck['resultType'] + ": " + refreshedCheck['userMessage'])
persistantError += 1
# Open a service now ticket for any server that has a health issue
shortDesc = 'Cohesity: SQL Registration Error for ' + vm['rootNode']['name']
description = 'Please assign this ticket to the SQL Admins to invesigate the following issue(s)\n' + check['userMessage']
print("Opening Ticket")
if persistantError == 0:
print("Refreshing the source " + vm['rootNode']['name'] + " resolved the registration error.")
# Already in a 'failure' if statement, else is only for non-debug operation
elif (debug == 0):
#Try to refresh the source to see if the error goes away
mdn.RefreshSource(vm['rootNode']['id'])
time.sleep(15)
#Pull the source registration information again and output
refreshedData = mdn.GetFilteredRequest("/public/protectionSources/registrationInfo","?ids=" + str(vm['rootNode']['id']))
for refreshedVM in refreshedData['rootNodes']:
persistantError = 0
for refreshedCheck in refreshedVM['registrationInfo']['registeredAppsInfo'][0]['hostSettingsCheckResults']:
if (refreshedCheck['resultType']=="kFail"):
# Open a service now ticket for any server that has a health issue
shortDesc = 'Cohesity: SQL Registration Error for ' + vm['rootNode']['name']
description = 'Please assign this ticket to the SQL Admins to invesigate the following issue(s)\n' + check['userMessage']
snResponse=ticketGenerator.submitTicket(shortDesc, description)
itdTeamsMessage.text("SQL Registration error found. A ServiceNow ticket has been opened to investigate " + vm['rootNode']['name'])
itdTeamsMessage.send()
# Ticket has been opened, break the loop to prevent opening multiple tickets for the same host.
break
#End refreshed error/message checks loop
#End refreshed VM data loop
#End debug if/elif block
# End if failure message found
# End for loop of all error/warning message checks
# End for loop of all VMs
itdTeamsMessage.text("Finished checking Cohesity SQL sources for registration errors, found: " + str(errorCount))
itdTeamsMessage.send()
except OSError as cohesityError:
print('Cohesity Error: ' + cohesityError)
+30
View File
@@ -0,0 +1,30 @@
#!/usr/bin/python
import sys,argparse,json,time,yaml
import re
def GetArgs():
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument('--server', '-s', type=str, action='store')
parser.add_argument('--vCenter', '-v', type=str, action='store')
return (parser.parse_args())
args = GetArgs()
with open("./exemptions.yml", "r") as exemptionJobData:
try:
exemptionJobData = yaml.load(exemptionJobData, Loader=yaml.FullLoader)
except:
print("Unable to load exemptions file.")
exemptServers = []
for i in exemptionJobData:
for e in exemptionJobData[i]['vms']:
exemptServers.append([exemptionJobData[i]['id'],e.lower()])
for entry in exemptServers:
parent = entry[0]
pattern = entry[1]
if (parent == int(args.vCenter) and re.search(pattern, args.server)):
print("Found match with pattern: " + pattern)
@@ -0,0 +1,62 @@
import sys
# Import NDIT storage classes
sys.path.insert(0, './classes/')
import cohesityAPI as cohesity
import serviceNowAPI as serviceNow
def main():
# Instantiate the ServiceNow API
svcNowInterface = serviceNow.API('northdakota.service-now.com')
# Instantiate Cohesity APIs
mdnCluster = cohesity.API('itdmdndpc01.nd.gov')
clusters = [mdnCluster]
replicationFailures = []
sqlFailures = []
backupFailures = []
undetermined = []
for cluster in clusters:
cluster.Authenticate()
alertID = 0
openAlerts = cluster.GetFilteredRequest("/public/alerts","?alertStateList=kOpen&maxAlerts=1000")
if len(openAlerts) > 1:
for alert in openAlerts:
if (alert['severity'] != "kInfo"):
# Open a unique ticket for every alert
if (alert['alertCategory'] == "kRemoteReplication"):
replicationFailures.append(alert)
continue
if (alert['alertCategory'] == "kBackupRestore"):
for item in alert['propertyList']:
if (item['value'] == "kSQL"):
sqlFailures.append(alert)
continue
shortDescription = "Backup Failure on " + str(cluster.GetClusterName()) + ": " + alert['alertDocument']['alertName']
longDescription = alert['alertDocument']['alertCause']
print("Opening incident for Cohesity alert:" + str(alertID))
print("\tShort Description: " + shortDescription)
print("\tLong Description: " + longDescription)
alertID += 1
#End if alert['severity'] != kInfo
#End for alert in alerts
# End if len(openAlerts) > 1
#End for cluster in clusters
print("Total replication failures: " + str(len(replicationFailures)))
print("Total SQL Backup failures: " + str(len(sqlFailures)))
#End main()
# Run the program
main()
@@ -0,0 +1,372 @@
import sys,argparse,json,time
sys.path.insert(0, './classes/')
import cohesityAPI as cohesity
# Global variables that will be used across all functions
global tagid
tagid = None
global tagName
tagName = None
# Begin Functions
def GetArgs():
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument('--cluster', '-c', type=str, action='store')
parser.add_argument('--vcenter', '-v', type=str, action='store')
parser.add_argument('--tag', '-t', type=str, action='store')
parser.add_argument('--group', '-g', type=str, action='store')
parser.add_argument('--action', '-a', type=str, action='store')
parser.add_argument('--list', '-l', type=str, action='store')
return (parser.parse_args())
def GetTagID(sources):
globalList = globals()
for source in sources:
if globalList['tagid'] is None:
GetNode(sources)
def GetNode(record):
globalList = globals()
# A non 'node' element was found, check each child key to see if one contains 'kTag'
if "protectionSource" in record:
if "vmWareProtectionSource" in record['protectionSource']:
if record['protectionSource']['vmWareProtectionSource']['type'] == "kTag":
# Tag elements have been found, look for the correct tag and assign the id to the global variable
if record['protectionSource']['vmWareProtectionSource']['name'] == globalList['tagName']:
# Tag was found based on the name
globalList['tagid'] = record['protectionSource']['id']
# Recurse through all 'nodes' elements looking for kTag
if "nodes" in record:
for node in record['nodes']:
# The tag hasn't been found, search the next record
if globalList['tagid'] is None:
GetNode(node)
# The tag was found and the global id value was set, stop iterating.
else:
break
def UpdateProtectionGroupExcludes(group, action):
globalList = globals()
isExcluded = False
tagArray = []
if "excludeVmTagIds" in group:
for index in group['excludeVmTagIds']:
for tag in index:
tagArray.append(tag)
if tag == globalList['tagid']:
isExcluded = True
# Add the tag to the exclusion
if action == 'add':
if isExcluded == True:
print(globalList['tagName'] + " tag is already excluded for " + group['name'])
else:
print("Adding exclusion tag '" + globalList['tagName'] + "' to group " + group['name'])
tagArray.append(globalList['tagid'])
# Remove the tag from the exclusion
elif action == 'remove':
if isExcluded == True:
print("Removing exclusion tag '" + globalList['tagName'] + "' from group " + group['name'])
tagArray.remove(globalList['tagid'])
else:
print(globalList['tagName'] + " tag is not excluded for " + group['name'])
return(tagArray)
def UpdateProtectionGroupIncludes(group, action):
globalList = globals()
isIncluded = False
tagArray = []
if "vmTagIds" in group:
for index in group['vmTagIds']:
for tag in index:
tagArray.append(tag)
if tag == globalList['tagid']:
isIncluded = True
# Add the tag to the inclusion
if action == 'add':
if isIncluded == True:
print(globalList['tagName'] + " tag is already included for " + group['name'])
else:
print("Adding inclusion tag '" + globalList['tagName'] + "' to group " + group['name'])
tagArray.append(globalList['tagid'])
# Remove the tag from the inclusion
elif action == 'remove':
if isIncluded == True:
print("Removing inclusion tag '" + globalList['tagName'] + "' from group " + group['name'])
tagArray.remove(globalList['tagid'])
else:
print(globalList['tagName'] + " tag is not included for " + group['name'])
return(tagArray)
# Begin Main
globalList = globals()
sourceFound = False
vCenter = None
vCenterID = None
tagIds = []
args = GetArgs()
# Validate arguments & assign variables if necessary
if not args.cluster:
sys.exit("Error: Specify a Cohesity cluster fqdn with -c parameter.")
else:
globalList['tagName'] = args.tag
if not args.vcenter:
sys.exit("Error: Specify a vCenter with -v parameter")
if not args.tag:
sys.exit("Error: Specify a tag with the -t parameter.")
if not args.action:
sys.exit("Error: Specify an action [add|remove] with -a parameter.")
elif args.action != 'add' and args.action != 'remove':
sys.exit("Error: Use only 'add' or 'remove' with -a parameter.")
if not args.list:
sys.exit("Error: Specify a list [include|exclude] with -l parameter.")
elif args.list != 'exclude' and args.list != 'include':
sys.exit("Error: Use only 'include' or 'exclude' with -l parameter.")
# Connect to the Cohesity cluster
cluster = cohesity.API(args.cluster)
authToken = cluster.GetAuthToken()
cluster.UpdateHeaders(authToken['accessToken'])
# Locate the correct vCenter and capture the id, this will be useful to validate child objects
vmSources = cluster.GetFilteredRequest("/public/protectionSources", "?environments=kVMware")
for source in vmSources:
if source['protectionSource']['name'] == args.vcenter:
sourceFound = True
vCenter = source
vCenterID = source['protectionSource']['id']
break
# The vCenter we are looking for was not in the list of kVMware environments, stop processing.
if sourceFound == False:
sys.exit("Error: " + args.vcenter + " is not registered to " + args.cluster)
# Look up the tag id based on the tagName and set a global variable when found
# IMPROVEMENT TO GET AWAY FROM GLOBAL VAR
GetTagID(vCenter)
if globalList['tagid'] is None:
sys.exit("Error: The tag '" + args.tag + "' does not exist in " + vCenter['protectionSource']['name'])
if args.group:
# Replace '@' symbol with '%40' for URL encoding in the REST API
protectionGroup = args.group.replace("@","%40")
job = cluster.GetFilteredRequest("/public/protectionJobs", "?names=" + protectionGroup)
print(json.dumps(job[0], indent=4))
if job[0]['parentSourceId'] != vCenterID:
sys.exit(job[0]['name'] + " does not belong to source " + vCenter['protectionSource']['name'])
else:
if (args.list == 'exclude'):
excludeTagIds = UpdateProtectionGroupExcludes(job[0], args.action)
if "excludeVmTagIds" in job[0]:
job[0]['excludeVmTagIds'][0] = excludeTagIds
else:
job[0].update({"excludeVmTagIds": [ excludeTagIds ]})
else:
includeTagIds = UpdateProtectionGroupIncludes(job[0], args.action)
if "vmTagIds" in job[0]:
job[0]['vmTagIds'][0] = includeTagIds
else:
job[0].update({"includeVmTagIds": [ includeTagIds ]})
# FIX the screw up
# Temp code to grab start time from previous runs
run = cluster.GetFilteredRequest("/public/protectionRuns","?numRuns=10&jobId=" + str(job[0]['id']))
try:
hour = round(run[7]['backupRun']['stats']['startTimeUsecs']/1000000,0)
hour = time.strftime("%H", time.localtime(hour))
minute = round(run[7]['backupRun']['stats']['startTimeUsecs']/1000000,0)
minute = time.strftime("%M", time.localtime(minute))
hour = int(hour)
minute = int(minute)
except:
hour = 21
minute = 00
job[0]['startTime']['hour'] = hour
job[0]['startTime']['minute'] = minute
# EndTime
# Temp code to build indexing
job[0].update({"indexingPolicy":{
"disableIndexing": False,
"allowPrefixes": [
"/"
],
"denyPrefixes": [
"/$Recycle.Bin",
"/Windows",
"/ProgramData",
"/System Volume Information",
"/Users/*/AppData",
"/Recovery",
"/usr",
"/sys",
"/proc",
"/lib",
"/grub",
"/grub2",
"/opt/splunk",
"/splunk",
]
}})
# Temp code to set SLA
job[0].update({"incrementalProtectionSlaTimeMins": 480})
job[0].update({"fullProtectionSlaTimeMins": 480})
job[0].update({"abortInBlackoutPeriod": False})
job[0].update({"quiesce": False})
job[0].update({"qosType": "kBackupHDD"})
job[0].update({"environmentParameters":{
"vmwareParameters": {
"fallbackToCrashConsistent": False,
"skipPhysicalRdmDisks": False
}
}})
job[0].update({"cloudParameters":{"failoverToCloud": False}})
job[0].update({"leverageStorageSnapshots": False})
job[0].update({"leverageStorageSnapshotsForHyperFlex": False})
job[0].update({"description": ""})
# End Fix
# Take acion on group(s)
resp = cluster.UpdateVMProtectionJob(job[0])
print(resp.content)
else:
vmJobs = cluster.GetFilteredRequest("/public/protectionJobs", "?environments=kVMware")
uniqueJobs = {job['id'] : job for job in vmJobs}.values()
for job in uniqueJobs:
if "isPaused" in job:
if job['isPaused']:
continue
# If the job is marked for deletion, skip
if 'isDeleted' in job:
continue
# If the job does not belong to this vCetner, skip
if job['parentSourceId'] != vCenterID:
continue
else:
if (args.list == 'exclude'):
excludeTagIds = UpdateProtectionGroupExcludes(job, args.action)
if "excludeVmTagIds" in job:
job['excludeVmTagIds'][0] = excludeTagIds
else:
job.update({"excludeVmTagIds": [ excludeTagIds ]})
else:
includeTagIds = UpdateProtectionGroupIncludes(job, args.action)
if "vmTagIds" in job:
job['vmTagIds'][0] = includeTagIds
else:
job.update({"includeVmTagIds": [ includeTagIds ]})
# FIX the screw up
# Temp code to grab start time from previous runs
run = cluster.GetFilteredRequest("/public/protectionRuns","?numRuns=10&jobId=" + str(job['id']))
try:
hour = round(run[7]['backupRun']['stats']['startTimeUsecs']/1000000,0)
hour = time.strftime("%H", time.localtime(hour))
minute = round(run[7]['backupRun']['stats']['startTimeUsecs']/1000000,0)
minute = time.strftime("%M", time.localtime(minute))
hour = int(hour)
minute = int(minute)
except:
hour = 21
minute = 00
job['startTime']['hour'] = hour
job['startTime']['minute'] = minute
# EndTime
# Temp code to build indexing
job.update({"indexingPolicy":{
"disableIndexing": False,
"allowPrefixes": [
"/"
],
"denyPrefixes": [
"/$Recycle.Bin",
"/Windows",
"/ProgramData",
"/System Volume Information",
"/Users/*/AppData",
"/Recovery",
"/usr",
"/sys",
"/proc",
"/lib",
"/grub",
"/grub2",
"/opt/splunk",
"/splunk",
]
}})
# Temp code to set SLA
job.update({"incrementalProtectionSlaTimeMins": 480})
job.update({"fullProtectionSlaTimeMins": 480})
job.update({"abortInBlackoutPeriod": False})
job.update({"quiesce": False})
job.update({"qosType": "kBackupHDD"})
job.update({"environmentParameters":{
"vmwareParameters": {
"fallbackToCrashConsistent": False,
"skipPhysicalRdmDisks": False
}
}})
job.update({"cloudParameters":{"failoverToCloud": False}})
job.update({"leverageStorageSnapshots": False})
job.update({"leverageStorageSnapshotsForHyperFlex": False})
job.update({"description": ""})
# Take acion on group(s)
print("Updating: " + job['name'])
resp = cluster.UpdateVMProtectionJob(job)
print(resp.content)
+89
View File
@@ -0,0 +1,89 @@
import sys,argparse,json,time
sys.path.insert(0, './classes/')
import cohesityAPI as cohesity
def GetArgs():
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument('--cluster', '-c', type=str, action='store')
parser.add_argument('--vcenter', '-v', type=str, action='store')
parser.add_argument('--job', '-j', type=str, action='store')
parser.add_argument('--help', '-h', action='store_true')
return (parser.parse_args())
def PrintHelp():
print("\nBasic Usage:")
print("Set OS environment variables ITD_SHAREPOINT_PASS and ITD_SHAREPOINT_USER")
print("\nExample:")
print(r"$ export COHESITY_USER=\"NDGOV\jDoe\"")
print("$ export COHESITY_PASS=\"1Lik3Jane\"")
print("\n python3 <SCRIPT_NAME> -c cluster1.domain.tld [ -v vCenter.domain.tld ] [ -j protectionJobName ]")
print("\t -c FQDN of Cohesity cluster address")
print("\t -v FQDN of vCenter Server, used in conjuntion with -t kVMware ")
print("\t -j Cohesity job name")
print("\t -h Prints this help message")
args = GetArgs()
if args.help:
PrintHelp()
if not args.cluster:
sys.exit("Error: Specify Cohesity cluster fqdn with -c parameter.")
else:
cluster = cohesity.API(args.cluster)
authToken = cluster.GetAuthToken()
cluster.UpdateHeaders(authToken['accessToken'])
if args.vcenter:
vmSources = cluster.GetFilteredRequest("/public/protectionSources", "?environments=kVMware")
for source in vmSources:
if source['protectionSource']['name'] == args.vcenter:
vCenter = source
vCenterID = source['protectionSource']['id']
break
if args.job:
# Get the details of a single job
vmJobs = cluster.GetFilteredRequest("/public/protectionJobs", "?names=" + args.job)
else:
# Get the details of all jobs under a vCenter
vmJobs = cluster.GetFilteredRequest("/public/protectionJobs", "?environments=kVMware")
uniqueJobs = {job['id'] : job for job in vmJobs}.values()
# Do stuff with the JSON data
# Example:
if args.pause:
pausedJobs = []
for job in uniqueJobs:
if job['parentSourceId'] != vCenterID:
print("not vcenter")
continue
elif "isDeleted" in job:
continue
elif "isPaused" in job:
if job['isPaused']:
pausedJobs.append(job)
continue
print("Pausing: " + job['name'])
resp = cluster.PauseJob(job['id'])
print("The following jobs were previously paused before this operation.")
for pJob in pausedJobs:
print(pJob['name'])
if args.resume:
for job in uniqueJobs:
if job['parentSourceId'] != vCenterID:
continue
elif "isDeleted" in job:
continue
print("Resuming: " + job['name'])
resp = cluster.ResumeJob(job['id'])
@@ -0,0 +1,21 @@
import sys,argparse,json,time
sys.path.insert(0, './classes/')
import cohesityAPI as cohesity
mdn = cohesity.API('itdmdndpc01.nd.gov')
mdnToken = mdn.GetAuthToken()
mdn.UpdateHeaders(mdnToken['accessToken'])
# Block Update all SQL jobs with new SQL policy
sqlPolicyName = mdn.GetFilteredRequest("/public/protectionPolicies", "?names=ITD-SQL")
sqlProtectionJobs = mdn.GetFilteredRequest("/public/protectionJobs", "?environments=kSQL")
uniqueJobs = {job['id'] : job for job in sqlProtectionJobs}.values()
isPaused=bool('false')
for job in uniqueJobs:
resp = mdn.UpdateProtectionJob(job['sourceIds'],job['parentSourceId'], job['name'], sqlPolicyName[0]['id'], job['viewBoxId'], job['id'], isPaused)
print(resp.content)
#End SQL Policy Update Block
+57
View File
@@ -0,0 +1,57 @@
#!/usr/bin/python
import sys,argparse,json,time
import time
sys.path.insert(0, './classes/')
import cohesityAPI as cohesity
def GetArgs():
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument('--cluster', '-c', type=str, action='store')
parser.add_argument('--help', '-h', action='store_true')
return (parser.parse_args())
def PrintHelp():
print("\nBasic Usage:")
print("\n python3 cancelJobs.py -c cluster1.domain.tld" )
print("\t -c FQDN of Cohesity cluster address")
print("\t -h Prints this help message")
args = GetArgs()
if arg.help:
PrintHelp()
exit(1)
# Validate arguments & assign variables if necessary
if not args.cluster:
sys.exit("Error: Specify a Cohesity cluster fqdn with -c parameter.")
# Connect to the Cohesity cluster
cluster = cohesity.API(args.cluster)
authToken = cluster.GetAuthToken()
cluster.UpdateHeaders(authToken['accessToken'])
vmJobs = cluster.GetFilteredRequest("/public/protectionJobs", "?environments=kVMware")
uniqueJobs = {job['id'] : job for job in vmJobs}.values()
# Non Running states
finStates = ['kCanceled', 'kSuccess', 'kFailure', 'kWarning']
for job in uniqueJobs:
if 'isDeleted' in job:
continue
if 'isPaused' in job:
continue
# Get running protections per vmware protection job id, most recent is returned
run = cluster.GetFilteredRequest("/public/protectionRuns","?numRuns=10&jobId=" + str(job['id']))
if run[0]['backupRun']['status'] not in finStates:
# Cancel the running protection job
resp = cluster.CancelJob(job['id'],run[0]['backupRun']['jobRunId'])
# Print the response
print(resp.content)
@@ -0,0 +1,7 @@
import requests
def send_automation(_data: dict):
_url = 'http://itdnettools.nd.gov/services/automation-tracking.py'
_r = requests.post(_url, json=_data)
return _r.text
@@ -0,0 +1,792 @@
import json
import random
import requests
import urllib3
import certifi
import os
import pytz
from datetime import datetime, timedelta, time
class API(object):
def __init__(self, server):
if not ((os.environ.get('COHESITY_USER')) and (os.environ.get('COHESITY_PASS'))):
print("\n*** Environment Variables are not set for authentication! ***")
exit()
# Get the authorization bearer token
self.user = os.environ['COHESITY_USER']
self.pwd = os.environ['COHESITY_PASS']
if self.user == "svcitdchstyauto":
self.domain = "local"
else:
self.domain = "nd.gov"
self.server = server
self.base_url = ('https://{}/irisservices/api/v1').format(self.server)
self.headers = {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
def GetClusterName(self):
return self.server
###################################################### Depricated Methods #################################################
def GetAuthToken(self):
apiCall = "/public/accessTokens"
payload = {
"username": self.user,
"password": self.pwd,
"domain": self.domain
}
encoded_payload = json.dumps(payload)
token = requests.request("POST", self.base_url + apiCall, headers=self.headers, data=encoded_payload, verify=True)
if 'locked' in token.text:
print("Account {} is locked".format(self.user))
exit(1)
if 'expired' in token.text:
print("Password for local account {} has expired".format(self.user))
exit(1)
return(self.FormatData(token))
###########################################################################################################################
# Methods under maintenance
def FormatData(self, response):
jsonData = json.loads(response.text)
return (jsonData)
def GetClusterName(self):
return(self.server)
def Authenticate(self):
apiCall = "/public/accessTokens"
payload = {
"username": self.user,
"password": self.pwd,
"domain": self.domain
}
encoded_payload = json.dumps(payload)
authResponse = requests.request("POST", self.base_url + apiCall, headers=self.headers, data=encoded_payload, verify=True)
token = self.FormatData(authResponse)
if (token['accessToken']):
self.headers.update({'Authorization': 'Bearer ' + token['accessToken']})
return()
else:
print("ERROR: Unable to authenticate to " + self.server)
def UpdateHeaders(self, token):
self.headers.update({'Authorization': 'Bearer ' + token})
def GetRelativeTimestamp(self, d, h, m, s):
# Timezone
tz = pytz.timezone("America/Chicago")
# Current date
date = datetime.now(tz).date()
# Convert to yesterday
start_date = date + timedelta(days=d)
# Set time to 1700
start_time = datetime(
year = start_date.year,
month = start_date.month,
day = start_date.day,
hour = h,
minute = m,
second = s
)
# Add timezone info to the start time
start_time = tz.localize(start_time)
# Convert to UTC
start_utc_time = start_time.astimezone(pytz.utc)
# Convert to unix epoch microseconds
start_epoch_ms = int(start_utc_time.timestamp() * 1000000)
return(start_epoch_ms)
def GetRandomStartTime(self):
hours = [17, 18, 19, 20, 21, 22, 23, 00, 1, 2, 3, 4, 5, 6]
min = [0, 15, 30, 45]
rHour = random.choice(hours)
rMin = random.choice(min)
sTime=[rHour,rMin]
return sTime
#################### DELETE Methods ####################
def DeleteJob(self, url):
apiCall = url
payload = {
"deleteSnapshots": True
}
encoded_payload = json.dumps(payload)
data = requests.delete(self.base_url + apiCall, headers=self.headers, data=encoded_payload)
return(data)
#################### SET Methods #######################
def ResolveAlert(self, alert_id, snow_id):
apiCall = "/public/alertResolutions"
payload = {
"alertIdList": [
alert_id
],
"resolutionDetails": {
"resolutionDetails":"See ServiceNow",
"resolutionSummary": snow_id
}
}
encoded_payload = json.dumps(payload)
response = requests.request("POST", self.base_url + apiCall, headers=self.headers, data=encoded_payload)
return(self.GetRequest(apiCall))
#################### GET Methods #######################
def GetRequest(self, url):
apiCall = url
payload = {}
encoded_payload = json.dumps(payload)
data = requests.request("GET", self.base_url + apiCall, headers=self.headers, data=encoded_payload)
return(self.FormatData(data))
def GetRequestV2(self, url):
base_url = ('https://{}/v2').format(self.server)
apiCall = url
payload = {}
encoded_payload = json.dumps(payload)
data = requests.request("GET", base_url + apiCall, headers=self.headers, data=encoded_payload)
return(self.FormatData(data))
def GetFilteredRequest(self, url, filters):
apiCall = url + filters
payload = {}
encoded_payload = json.dumps(payload)
data = requests.request("GET", self.base_url + apiCall, headers=self.headers, data=encoded_payload)
return(self.FormatData(data))
def GetPhysicalsources(self):
apiCall = "/public/protectionSources/registrationInfo?isDeleted=False&environments=kPhysical"
return(self.GetRequest(apiCall))
def GetSQLsources(self):
apiCall = "/public/protectionSources/registrationInfo?isDeleted=False&environments=kSQL"
return(self.GetRequest(apiCall))
def GetSQLjobs(self):
apiCall = "/public/protectionJobs/?isDeleted=False&environments=kSQL"
return(self.FormatData(self.GetRequest(apiCall)))
def GetVMsources(self):
apiCall = "/public/protectionSources?environments=kVMware"
return(self.GetRequest(apiCall))
def GetVMjobs(self):
apiCall = "/public/protectionJobs?environments=kVMware"
return(self.GetRequest(apiCall))
def GetDiskAlerts(self, startTime):
apiCall = "/public/alerts?alertStateList=kOpen&alertCategoryList=kDisk&startDateUsecs=" + str(startTime)
return(self.GetRequest(apiCall))
def GetProtectedVMobjects(self, hypervisorID):
apiCall = "/public/protectionSources/protectedObjects?environment=kVMware&id=" + str(hypervisorID)
return(self.GetRequest(apiCall))
def GetProtectionPolicyByName(self, policyName):
apiCall = "/public/protectionPolicies/?name=" + policyName
return(self.GetRequest(apiCall))
def GetVMObjects(self, hypervisorID):
apiCall = "/public/protectionSources/virtualMachines?vCenterId=" + str(hypervisorID)
return(self.GetRequest(apiCall))
def GetAZsources(self):
apiCall = "/public/protectionSources?environments=kAzure"
return(self.GetRequest(apiCall))
def GetAZNativeJobs(self):
apiCall = "/public/protectionJobs?environments=kAzureNative"
return(self.GetRequest(apiCall))
def GetAZObjects(self, hypervisorID):
apiCall = "/public/protectionSources?environments=kAzure&id=" + str(hypervisorID)
data = self.GetRequest(apiCall)
azureVMs = []
for node in data[0]['nodes']:
if 'nodes' in node:
for subNode in node['nodes']:
if 'protectionSource' in subNode:
if 'azureProtectionSource' in subNode['protectionSource']:
if "type" in subNode['protectionSource']['azureProtectionSource']:
if subNode['protectionSource']['azureProtectionSource']['type'] == 'kVirtualMachine':
azureVMs.append(subNode['protectionSource'])
return(azureVMs)
def GetProtectedAZobjects(self, hypervisorID):
apiCall = "/public/protectionSources/protectedObjects?environment=kAzure&id=" + str(hypervisorID)
return(self.GetRequest(apiCall))
def GetPhysicalJobs(self):
apiCalll = "/public/protectionJobs?isDeleted=False&environments=kPhysicalFiles"
return(self.FormatData(self.GetRequest(apiCall)))
def GetProtectionSourceId(self, hostname):
sourceRegistered = False
hostname = hostname.lower()
sources = self.GetFilteredRequest("/public/protectionSources/registrationInfo", "?environments=kPhysical")
for source in sources['rootNodes']:
if (source['rootNode']['name']).lower() == hostname:
sourceId = source['rootNode']['id']
sourceRegistered = True
break
if sourceRegistered:
return(sourceId)
else:
sys.exit("Source not registered")
def GetVcenterTagId(self, vCenterId, tagName):
tagFound=0
for record in vCenterId[0]['nodes'][0]['nodes']:
if "protectionSource" in record:
if "name" in record['protectionSource']:
# Check if tagName matches AppName
if record['protectionSource']['name'] == "AppName":
for tagRecord in record['nodes']:
if tagRecord['protectionSource']['name'] == tagName and tagRecord['protectionSource']['vmWareProtectionSource']['type'] == "kTag":
tagId = tagRecord['protectionSource']['id']
tagFound = 1
break
# Check if tagName matches DTAP (Development, Test, Acceptable, Production)
if record['protectionSource']['name'] == "DTAP":
for tagRecord in record['nodes']:
if tagRecord['protectionSource']['name'] == tagName and tagRecord['protectionSource']['vmWareProtectionSource']['type'] == "kTag":
tagId = tagRecord['protectionSource']['id']
tagFound = 1
break
if tagFound == 1:
break
if tagFound == 1:
return tagId
else:
return None
def GetAzureTagId(self, azSubId, tagName):
tagFound = 0
for record in azSubId[0]['nodes']:
if "protectionSource" in record:
if "name" in record['protectionSource']:
if record['protectionSource']['name'] == "ApplicationName#~#" + tagName:
if record['protectionSource']['azureProtectionSource']['type'] == "kTag":
tagId = record['protectionSource']['id']
tagFound = 1
break
if tagFound == 1:
break
if tagFound == 1:
return tagId
else:
return None
def GetProtectionJobByHost(self, hostname):
jobFound = False
jobs = self.GetFilteredRequest("/public/protectionJobs", "?isDeleted=False&environments=kPhysicalFiles,kSQL")
sourceId = self.GetProtectionSourceId(hostname)
for job in jobs:
for source in job['sourceIds']:
if source == sourceId:
jobName = job['name']
jobFound = True
break
# Job was found break out of the jobs loop
if jobFound:
break
if jobFound:
return(job)
else:
return(-1)
################### Registration Methods ##################
def RemoveProtectionSource(self, sourceId):
apiCall = "/public/protectionSources/" + str(sourceId)
response = requests.request("DELETE", self.base_url + apiCall, headers=self.headers)
return(response)
def RegisterPhysical(self, hostName):
apiCall = "/public/protectionSources/register"
payload = {
"environment": "kPhysical",
"physicalType": "kHost",
"forceRegister" : True,
# The API will error if hostType is not specified; however during the registration process Cohesity will register
# the correct type even if the incorrect type was given in the payload.
"hostType": "kWindows",
"endpoint": hostName
}
encoded_payload = json.dumps(payload)
response = requests.request("POST", self.base_url + apiCall, headers=self.headers, data=encoded_payload)
return(self.FormatData(response))
def RegisterSQL(self, sourceID):
apiCall = "/public/protectionSources/applicationServers"
payload = {
"applications": [
"kSQL"
],
"hasPersistentAgent": bool("true"),
"protectionSourceId": sourceID
}
encoded_payload = json.dumps(payload)
response = requests.request("POST", self.base_url + apiCall, headers=self.headers, data=encoded_payload)
return (self.FormatData(response))
def RefreshSource(self, sourceID):
apiCall = "/public/protectionSources/refresh/" + str(sourceID)
payload = {}
encoded_payload = json.dumps(payload)
response = requests.request("POST", self.base_url + apiCall, headers=self.headers)
return
################ Protection Job Methods #######################
def PauseJob(self, jobId):
apiCall = "/public/protectionJobState/" + str(jobId)
payload = {
"pause": True,
"pauseReason": 0
}
encoded_payload = json.dumps(payload)
response = requests.request("POST", self.base_url + apiCall, headers=self.headers, data=encoded_payload)
return (response)
def ResumeJob(self, jobId):
apiCall = "/public/protectionJobState/" + str(jobId)
payload = {
"pause": False,
"pauseReason": 0
}
encoded_payload = json.dumps(payload)
response = requests.request("POST", self.base_url + apiCall, headers=self.headers, data=encoded_payload)
return (response)
def CancelJob(self, jobId, runId):
apiCall = "/public/protectionRuns/cancel/" + str(jobId)
payload = {
"jobRunId": runId
}
encoded_payload = json.dumps(payload)
response = requests.request("POST", self.base_url + apiCall, headers=self.headers, data=encoded_payload)
return (response)
def UpdateVMProtectionJob(self, job):
apiCall = "/public/protectionJobs/" + str(job['id'])
payload = job
encoded_payload = json.dumps(payload)
response = requests.request("PUT", self.base_url + apiCall, headers=self.headers, data=encoded_payload)
return (response)
def UpdateProtectionJob(self, job):
apiCall = "/public/protectionJobs/" + str(job['id'])
payload = job
encoded_payload = json.dumps(payload)
response = requests.request("PUT", self.base_url + apiCall, headers=self.headers, data=encoded_payload)
return (response)
def UpdateAZprotectionJob(self, job):
base_url = ('https://{}/v2').format(self.server)
apiCall = "/data-protect/protection-groups/7780085755317378:1694019083558:" + str(job['id'])
payload = job
encoded_payload = json.dumps(payload)
response = requests.request("PUT", base_url + apiCall, headers=self.headers, data=encoded_payload)
return (response)
def CreateVMProtectionJob(self, pgName, parentId, tagId, excludeTag, polId):
apiCall = "/public/protectionJobs/"
sTime=self.GetRandomStartTime()
payload = {
"name": pgName,
"description": "",
"environment": "kVMware",
# NDIT - Bronze Policy
"policyId": polId,
#"policyId": "4847477517838800:1610060943809:6402",
# Default Storage Domain
"viewBoxId": 198,
"parentSourceId": parentId,
"vmTagIds": [
[
tagId
]
],
"excludeVmTagIds": [
[
excludeTag
]
],
"startTime": {
"hour": sTime[0],
"minute": sTime[1]
},
"timezone": "America/Chicago",
"incrementalProtectionSlaTimeMins": 480,
"fullProtectionSlaTimeMins": 480,
"priority": "kLow",
"indexingPolicy":{
"disableIndexing": False,
"allowPrefixes": [
"/"
],
"denyPrefixes": [
"/$Recycle.Bin",
"/Windows",
"/ProgramData",
"/System Volume Information",
"/Users/*/AppData",
"/Recovery",
"/usr",
"/sys",
"/proc",
"/lib",
"/grub",
"/grub2",
"/opt/splunk",
"/splunk",
]
},
"abortInBlackoutPeriod": False,
"quiesce": False,
"qosType": "kBackupHDD",
"environmentParameters":{
"vmwareParameters":{
"fallbackToCrashConsistent": False,
"skipPhysicalRdmDisks": False
}
},
"cloudParameters": {
"failoverToCloud": False
},
"leverageStorageSnapshots": False,
"leverageStorageSnapshotsForHyperFlex": False,
"isPaused": False
}
encoded_payload = json.dumps(payload)
response = requests.request("POST", self.base_url + apiCall, headers=self.headers, data=encoded_payload)
return (response)
def CreateAZSQLProtectionJob(self, srcId, pgName):
apiCall = "/public/protectionJobs/"
payload = {
"name": pgName,
# NDIT - Bronze Policy
"policyId": "7780085755317378:1694019083558:289",
# Default Storage Domain
"viewBoxId": 36,
# SQL Server kRootContainer
"parentSourceId": 6,
"sourceIds": [
srcId
],
"startTime": {
"hour": 22,
"minute": 00
},
"timezone": "America/Chicago",
"fullProtectionSlaTimeMins": 480,
"incrementalProtectionSlaTimeMins": 480,
"priority": "kLow",
"LeverageSanTransport": False,
# Default Indexing Policy
"indexingPolicy":{
"disableIndexing": False,
"allowPrefixes": [
"/"
],
"denyPrefixes": [
"/$Recycle.Bin",
"/Windows",
"/ProgramData",
"/System Volume Information",
"/Users/*/AppData",
"/Recovery",
"/usr",
"/sys",
"/proc",
"/lib",
"/grub",
"/grub2",
"/opt/splunk",
"/splunk",
]
},
"environment": "kSQL",
"environmentParameters": {
"sqlParameters": {
"userDatabasePreference": "kBackupAllDatabases",
"backupSystemDatabases": bool('true'),
"aagPreferenceFromSqlServer": bool('true'),
"backupType": "kSqlVSSVolume",
"backupVolumesOnly": bool('true')
}
},
"abortInBlackoutPeriod": False,
"qosType": "kBackupHDD",
"description": "",
"isPaused": False
}
encoded_payload = json.dumps(payload)
response = requests.request("POST", self.base_url + apiCall, headers=self.headers, data=encoded_payload)
return (response)
def CreateAZProtectionJob(self, pgName, parentId, tagId):
base_url = ('https://{}/v2').format(self.server)
apiCall = "/data-protect/protection-groups"
payload = {
"name": pgName,
"description": "",
# NDIT - Bronze Policy
#"policyId": "5033496487614715:1619543500905:271778",
"policyId": "7780085755317378:1694019083558:2291",
# Default Storage Domain
"storageDomainId": 36,
"startTime": {
"hour": 19,
"minute": 00,
"timezone": "America/Chicago"
},
"priority": "kLow",
"sla": [
{
"backupRunType": "kIncremental",
"slaMinutes": 480
},
{
"backupRunType": "kFull",
"slaMinutes": 480
}
],
"isPaused": True,
"environment": "kAzure",
"azureParams": {
"protectionType": "kNative",
"nativeProtectionTypeParams":{
"objects": [],
"vmTagIds": [
[
tagId
]
],
"indexingPolicy":{
"enableIndexing": True,
"includePaths": [
"/"
],
"excludePaths": [
"/$Recycle.Bin",
"/Windows",
"/ProgramData",
"/System Volume Information",
"/Users/*/AppData",
"/Recovery",
"/usr",
"/sys",
"/proc",
"/lib",
"/grub",
"/grub2",
"/splunk",
]
},
"sourceId": parentId
},
},
"abortInBlackoutPeriod": False
}
encoded_payload = json.dumps(payload)
print(base_url + apiCall)
response = requests.request("POST", base_url + apiCall, headers=self.headers, data=encoded_payload)
return (response)
def CreateSQLProtectionJob(self, srcId, pgName):
apiCall = "/public/protectionJobs/"
payload = {
"name": pgName,
# NDIT - Bronze Policy
"policyId": "4847477517838800:1610060943809:6402",
# Default Storage Domain
"viewBoxId": 198,
# SQL Server kRootContainer
"parentSourceId": 8702,
"sourceIds": [
srcId
],
"startTime": {
"hour": 22,
"minute": 00
},
"timezone": "America/Chicago",
"fullProtectionSlaTimeMins": 480,
"incrementalProtectionSlaTimeMins": 480,
"priority": "kLow",
"LeverageSanTransport": False,
# Default Indexing Policy
"indexingPolicy":{
"disableIndexing": False,
"allowPrefixes": [
"/"
],
"denyPrefixes": [
"/$Recycle.Bin",
"/Windows",
"/ProgramData",
"/System Volume Information",
"/Users/*/AppData",
"/Recovery",
"/usr",
"/sys",
"/proc",
"/lib",
"/grub",
"/grub2",
"/opt/splunk",
"/splunk",
]
},
"environment": "kSQL",
"environmentParameters": {
"sqlParameters": {
"userDatabasePreference": "kBackupAllDatabases",
"backupSystemDatabases": bool('true'),
"aagPreferenceFromSqlServer": bool('true'),
"backupType": "kSqlVSSVolume",
"backupVolumesOnly": bool('true')
}
},
"abortInBlackoutPeriod": False,
"qosType": "kBackupHDD",
"description": "",
"isPaused": False
}
encoded_payload = json.dumps(payload)
response = requests.request("POST", self.base_url + apiCall, headers=self.headers, data=encoded_payload)
return (response)
def CreatePhysicalProtectionJob(self, srcId, pgName):
apiCall = "/public/protectionJobs/"
payload = {
"name": pgName,
"environment": "kPhysical",
# NDIT - Bronze Policy
"policyId": "4847477517838800:1610060943809:6402",
# Default Storage Domain
"viewBoxId": 198,
# Physical Servers
"parentSourceId": 3149,
"sourceIds": [
srcId
],
"startTime": {
"hour": 18,
"minute": 00
},
"timezone": "America/Chicago",
"fullProtectionSlaTimeMins": 480,
"incrementalProtectionSlaTimeMins": 480,
"priority": "kLow",
"LeverageSanTransport": False,
# Default Indexing Policy
"indexingPolicy":{
"disableIndexing": False,
"allowPrefixes": [
"/"
],
"denyPrefixes": [
"/$Recycle.Bin",
"/Windows",
"/ProgramData",
"/System Volume Information",
"/Users/*/AppData",
"/Recovery",
"/usr",
"/sys",
"/proc",
"/lib",
"/grub",
"/grub2",
"/opt/splunk",
"/splunk",
]
},
"abortInBlackoutPeriod": False,
"performSourceSideDedup": True,
"qosType": "kBackupHDD",
"description": "",
"isPaused": False
}
encoded_payload = json.dumps(payload)
response = requests.request("POST", self.base_url + apiCall, headers=self.headers, data=encoded_payload)
return (response)
################ Security Methods #######################
def UpdatePermissions(self, sourceIds, cohesitySid):
apiCall = "/public/principals/protectionSources"
payload = {
"sourcesForPrincipals": [
{
"protectionSourceIds": sourceIds,
"sid": cohesitySid
}
]
}
encoded_payload = json.dumps(payload)
response = requests.request("PUT", self.base_url + apiCall, headers=self.headers, data=encoded_payload)
return (response)
+502
View File
@@ -0,0 +1,502 @@
import requests
import json
import os
import pytz
from datetime import datetime
from datetime import timedelta
from datetime import time
# API Documentation
# https://docs.servicenow.com/bundle/sandiego-application-development/page/build/applications/concept/api-rest.html
class SnowAPI:
def __init__(self, snInstance):
if not ((os.environ.get('SN_USER')) and (os.environ.get('SN_PASS'))):
print("\n*** Environment variables are not set for ServiceNow Authentication ***")
exit()
self.snInstance = snInstance
self.headers = {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
self.user = os.environ['SN_USER']
self.pwd = os.environ['SN_PASS']
def getGroupID(self, grpName):
grpName = grpName.replace(" ","%20")
url = 'https://{}/api/now/table/sys_user_group?sysparm_query=name={}'.format(self.snInstance, grpName)
apiResponse = requests.get(url, auth=(self.user, self.pwd), headers=self.headers)
apiData = apiResponse.json()
return apiData['result'][0]['sys_id']
def getUserID(self, userName):
userName = userName.replace(" ","%20")
apiCall = 'https://{}/api/now/table/sys_user?sysparm_query=user_name={}'.format(self.snInstance, userName)
apiResponse = requests.get(apiCall, auth=(self.user, self.pwd), headers=self.headers)
apiData = apiResponse.json()
return apiData['result'][0]['sys_id']
def getCMDBItemByIP(self, pattern):
apiCall = "https://{0}/api/now/table/cmdb_ci_server?sysparm_query=ip_address={1}&sysparm_limit=10".format(self.snInstance, pattern)
apiResponse = requests.get(apiCall, auth=(self.user, self.pwd), headers=self.headers)
apiData = apiResponse.json()
try:
appNameRec = self.getCMDBAppById(apiData['result'][0]['u_nd_application_svc']['value'])
appName = appNameRec['name']
apiData['result'][0].update({
"u_nd_application_svc": {
"link": apiData['result'][0]['u_nd_application_svc']['link'],
"value": apiData['result'][0]['u_nd_application_svc']['value'],
"name": appName
}
})
except:
apiData['result'][0].update({
"u_nd_application_svc": {
"name": "UNDEFINED"
},
"environment": "UNDEFINED"
})
return apiData['result']
def getCMDBItemByHostName(self, pattern):
apiCall = "https://{0}/api/now/table/cmdb_ci_server?sysparm_query=host_name={1}&sysparm_limit=10".format(self.snInstance, pattern)
apiResponse = requests.get(apiCall, auth=(self.user, self.pwd), headers=self.headers)
apiData = apiResponse.json()
try:
appNameRec = self.getCMDBAppById(apiData['result'][0]['u_nd_application_svc']['value'])
appName = appNameRec['name']
apiData['result'][0].update({
"u_nd_application_svc": {
"link": apiData['result'][0]['u_nd_application_svc']['link'],
"value": apiData['result'][0]['u_nd_application_svc']['value'],
"name": appName
}
})
except:
apiData['result'][0].update({
"u_nd_application_svc": {
"name": "UNDEFINED"
},
"environment": "UNDEFINED"
})
return apiData['result']
def getCMDBItemByFQDN(self, pattern):
apiCall = "https://{0}/api/now/table/cmdb_ci_server?sysparm_query=fqdn={1}&sysparm_limit=10".format(self.snInstance, pattern)
apiResponse = requests.get(apiCall, auth=(self.user, self.pwd), headers=self.headers)
apiData = apiResponse.json()
if len(apiData['result']) > 0:
try:
appNameRec = self.getCMDBAppById(apiData['result'][0]['u_nd_application_svc']['value'])
appName = appNameRec['name']
apiData['result'][0].update({
"u_nd_application_svc": {
"link": apiData['result'][0]['u_nd_application_svc']['link'],
"value": apiData['result'][0]['u_nd_application_svc']['value'],
"name": appName
}
})
except:
print("Result 2: {0}".format(apiData))
apiData['result'][0].update({
"u_nd_application_svc": {
"name": "UNDEFINED"
},
"environment": "UNDEFINED"
})
return apiData['result']
def getCMDBAppById(self, sysId):
apiCall = "https://{0}/api/now/table/cmdb_ci_service_auto?sys_id={1}".format(self.snInstance, sysId)
apiResponse = requests.get(apiCall, auth=(self.user, self.pwd), headers=self.headers)
apiData = apiResponse.json()
return apiData['result'][0]
def getStandardChangeTemplateID(self, templateName):
# Could be improved, might throw unexpected results if the group isn't found
# Needs error handling
allData = []
apiCall = "https://{0}/api/sn_chg_rest/change/standard/template?sysparm_query=active=true".format(self.snInstance)
apiResponse = requests.get(apiCall, auth=(self.user, self.pwd), headers=self.headers)
data = apiResponse.json()
for i in data['result']:
allData.append(i)
for record in allData:
if 'sys_name' in record:
if record['sys_name']['display_value'] == templateName:
sysID = record['sys_id']['value']
break
return sysID
def getRequestItemFromReqNum(self, reqNum):
apiCall = "https://{0}/api/now/table/sc_req_item?sysparm_query=request.number={1}&sysparm_limit=1".format(self.snInstance, reqNum)
apiResponse = requests.get(apiCall, auth=(self.user, self.pwd), headers=self.headers)
data = apiResponse.json()
item = data['result'][0]['number']
return item
def getTaskNumFromReqNum(self, reqNum):
apiCall = "https://{0}/api/now/table/sc_task?sysparm_query=request.number={1}&sysparm_limit=1".format(self.snInstance, reqNum)
apiResponse = requests.get(apiCall, auth=(self.user, self.pwd), headers=self.headers)
data = apiResponse.json()
item = data['result'][0]['number']
return item
def getTaskSysIdFromReqNum(self, reqNum):
apiCall = "https://{0}/api/now/table/sc_task?sysparm_query=request.number={1}&sysparm_limit=1".format(self.snInstance, reqNum)
apiResponse = requests.get(apiCall, auth=(self.user, self.pwd), headers=self.headers)
data = apiResponse.json()
item = data['result'][0]['sys_id']
return item
def getServiceCatalogs(self):
apiCall = "https://{0}/api/sn_sc/servicecatalog/catalogs".format(self.snInstance)
apiResponse = requests.get(apiCall, auth=(self.user, self.pwd), headers=self.headers)
items = apiResponse.json()
return items
def getServiceCatalogCategories(self, sys_id):
apiCall = "https://{0}/api/sn_sc/servicecatalog/catalogs/{1}/categories".format(self.snInstance,sys_id)
apiResponse = requests.get(apiCall, auth=(self.user, self.pwd), headers=self.headers)
items = apiResponse.json()
return items
def getServiceCatalogItems(self):
apiCall = "https://{0}/api/sn_sc/servicecatalog/items?sysparm_limit=10000&sysparm_offset=0".format(self.snInstance)
apiResponse = requests.get(apiCall, auth=(self.user, self.pwd), headers=self.headers)
items = apiResponse.json()
return items
def getServiceCatalogItemByName(self, itemName):
apiCall = "https://{0}/api/sn_sc/servicecatalog/items?sysparm_limit=10000&sysparm_offset=0".format(self.snInstance)
apiResponse = requests.get(apiCall, auth=(self.user, self.pwd), headers=self.headers)
items = apiResponse.json()
for item in items['result']:
if item['name'] == itemName:
return item
def getSpecificCatalogItem(self, sysId):
apiCall = "https://{0}/api/sn_sc/servicecatalog/items/{1}".format(self.snInstance,sysId)
apiResponse = requests.get(apiCall, auth=(self.user, self.pwd), headers=self.headers)
items = apiResponse.json()
return items
def getDataFromTaskByReqNum(self, sctask, reqNum):
# This is only written to handle one task per REQ number!!!
apiCall = "https://{0}/api/now/table/sc_task?sysparm_query=request.number={1}&sysparm_fields=state%2Cnumber%2Csys_id%2Cdescription%2Cshort_description%2Cvariables.application_name%2Cvariables.additional_comments%2Cclose_notes%2Cclosed_by&sysparm_limit=1&sysparm_display_value=true".format(self.snInstance, reqNum)
apiResponse = requests.get(apiCall, auth=(self.user, self.pwd), headers=self.headers)
data = apiResponse.json()
if data['result'][0]['number'] == sctask:
items = data['result'][0]
else:
items = ""
return items
def updateTaskDescriptions(self, sysId, short_desc, desc):
apiCall = "https://{0}/api/now/table/sc_task/{1}".format(self.snInstance,sysId)
payload = {
'short_description': short_desc,
'description': desc
}
encoded_payload = json.dumps(payload)
apiResponse = requests.put(apiCall, auth=(self.user, self.pwd), headers=self.headers, data=encoded_payload)
if apiResponse.status_code != 201 and apiResponse.status_code != 200:
print('Failed to update task:')
print('\tStatus:', apiResponse.status_code)
print('\tError Response:',apiResponse.json())
exit(1)
else:
return apiResponse.json()
def assignTaskToUser(self, sysId, userName):
userID = self.getUserID(userName)
apiCall = "https://{0}/api/now/table/sc_task/{1}".format(self.snInstance,sysId)
payload = {
"assigned_to": userID
}
encoded_payload = json.dumps(payload)
apiResponse = requests.put(apiCall, auth=(self.user, self.pwd), headers=self.headers, data=encoded_payload)
if apiResponse.status_code != 201 and apiResponse.status_code != 200:
print('Failed to update task:')
print('\tStatus:', apiResponse.status_code)
print('\tError Response:',apiResponse.json())
exit(1)
else:
return apiResponse.json()
def postAppServerRequest(self,itemID,requestFor,requestBy,approver,requestType,comments,appName,env):
requestForID = self.getUserID(requestFor)
requestByID = self.getUserID(requestBy)
approvalID = self.getUserID(approver)
# Statically set these so we always force approvals to go to Computer Systems
departmentID = "f3c65cef1bfed050bba0113fad4bcb1d"
departmentCode = "112"
divisionID = "f40758231b321450bba0113fad4bcb2d"
divisionCode = "32"
payload = {
"get_portal_messages" : "true",
"sysparm_quantity" : "1",
"sysparm_no_validation" : "true",
"variables" : {
"v_approval_department" : departmentID,
"v_approval_department_code" : departmentCode,
"v_approval_division" : divisionID,
"v_approval_division_code" : divisionCode,
"v_manager" : approvalID,
"v_requested_by" : requestByID,
"v_requested_for" : requestForID,
"request_type" : requestType,
"additional_comments" : comments,
"application_name" : appName,
"environment" : env,
"require_hosting_quote" : "No",
"add_change_disaster_recovery" : "No"
}
}
apiCall = ('https://{0}/api/sn_sc/v1/servicecatalog/items/{1}/order_now').format(self.snInstance,itemID)
encoded_payload = json.dumps(payload)
apiResponse = requests.post(apiCall, auth=(self.user, self.pwd), headers=self.headers, data=encoded_payload)
if apiResponse.status_code != 201 and apiResponse.status_code != 200:
print('Failed to create ticket:')
print('\tStatus:', apiResponse.status_code)
print('\tError Response:',apiResponse.json())
exit(1)
else:
return apiResponse.json()
return apiResponse
def postGenericSerivceRequest(self,itemID,requestFor,requestBy,approver,requestType,comments):
requestForID = self.getUserID(requestFor)
requestByID = self.getUserID(requestBy)
approvalID = self.getUserID(approver)
departmentID = "f3c65cef1bfed050bba0113fad4bcb1d"
divisionID = "f40758231b321450bba0113fad4bcb2d"
payload = {
"get_portal_messages" : "true",
"sysparm_quantity" : "1",
"sysparm_no_validation" : "true",
"variables" : {
"v_approval_department" : departmentID,
"v_approval_division" : divisionID,
"v_manager" : approvalID,
"v_requested_by" : requestByID,
"v_requested_for" : requestForID,
"v_type" : requestType,
"additional_comments" : comments
}
}
apiCall = ('https://{0}/api/sn_sc/v1/servicecatalog/items/{1}/order_now').format(self.snInstance,itemID)
encoded_payload = json.dumps(payload)
apiResponse = requests.post(apiCall, auth=(self.user, self.pwd), headers=self.headers, data=encoded_payload)
if apiResponse.status_code != 201 and apiResponse.status_code != 200:
print('Failed to create ticket:')
print('\tStatus:', apiResponse.status_code)
print('\tError Response:',apiResponse.json())
exit(1)
else:
return apiResponse.json()
return apiResponse
def getGenericData(self, url):
apiCall = "https://{0}/".format(self.snInstance,url)
apiResponse = requests.get(apiCall, auth=(self.user, self.pwd), headers=self.headers)
items = apiResponse.json()
return items
def submitTicket(self, user, subject, content):
apiCall = ('https://{}/api/now/table/incident').format(self.snInstance)
userID=self.getUserID(user)
payload = {
'caller_id': userID,
'short_description': subject,
'description': content
}
encoded_payload = json.dumps(payload)
apiResponse = requests.post(apiCall, auth=(self.user, self.pwd), headers=self.headers, data=encoded_payload)
if apiResponse.status_code != 201 and apiResponse.status_code != 200:
print('Failed to create ticket:')
print('\tStatus:', apiResponse.status_code)
print('\tError Response:',apiResponse.json())
exit(1)
else:
return apiResponse.json()
def assignTicketToGroup(self, sysId, grpName):
grpId = self.getGroupID(grpName)
url = 'https://{}/api/now/table/incident/{}'.format(self.snInstance,sysId)
#Statically set the group to storage, this value was derived by decoding the web URL on storage tickets
payload = {
'assignment_group': grpId
}
encoded_payload = json.dumps(payload)
apiResponse = requests.put(url, auth=(self.user, self.pwd), headers=self.headers, data=encoded_payload)
if apiResponse.status_code != 201 and apiResponse.status_code != 200:
print('Failed to create ticket:')
print('\tStatus:', apiResponse.status_code)
print('\tError Response:',apiResponse.json())
exit(1)
else:
return apiResponse.json()
def assessNormalChange(self, sysID):
apiCall = "https://{0}/api/sn_chg_rest/change/normal/{1}".format(self.snInstance,sysID)
payload = {
"state": "assess"
}
encoded_payload=json.dumps(payload)
apiResponse = requests.patch(apiCall, auth=(self.user, self.pwd), headers=self.headers, data=encoded_payload)
return apiResponse.json()
def addStandardChangeNotes(self, sysID, txtWorkNotes):
apiCall = "https://{0}/api/sn_chg_rest/change/standard/{1}".format(self.snInstance,sysID)
payload = {
"work_notes": txtWorkNotes
}
encoded_payload=json.dumps(payload)
apiResponse = requests.patch(apiCall, auth=(self.user, self.pwd), headers=self.headers, data=encoded_payload)
return apiResponse.json()
def scheduleStandardChange(self, sysID):
apiCall = "https://{0}/api/sn_chg_rest/change/standard/{1}".format(self.snInstance,sysID)
payload = {
"state": "Scheduled"
}
encoded_payload=json.dumps(payload)
apiResponse = requests.patch(apiCall, auth=(self.user, self.pwd), headers=self.headers, data=encoded_payload)
return apiResponse.json()
def implementStandardChange(self, sysID):
apiCall = "https://{0}/api/sn_chg_rest/change/standard/{1}".format(self.snInstance, sysID)
payload = {
"state": "Implement"
}
encoded_payload=json.dumps(payload)
apiResponse = requests.patch(apiCall, auth=(self.user, self.pwd), headers=self.headers, data=encoded_payload)
return apiResponse.json()
def reviewStandardChange(self, sysID):
apiCall = "https://{0}/api/sn_chg_rest/change/standard/{1}".format(self.snInstance, sysID)
payload = {
"state": "Review"
}
encoded_payload=json.dumps(payload)
apiResponse = requests.patch(apiCall, auth=(self.user, self.pwd), headers=self.headers, data=encoded_payload)
return apiResponse.json()
def closeStandardChange(self, sysID, code, notes):
apiCall = "https://{0}/api/sn_chg_rest/change/standard/{1}".format(self.snInstance, sysID)
payload = {
"state": "Closed",
"close_code": code,
"close_notes": notes
}
encoded_payload=json.dumps(payload)
apiResponse = requests.patch(apiCall, auth=(self.user, self.pwd), headers=self.headers, data=encoded_payload)
return apiResponse.json()
def CreateNormalChange(self, grpName, assignee, coordinator, approver, category, subCategory, shortDesc, desc, justDesc, implmntPlan, riskImpact, backoutPlan, testPlan, sTime, eTime):
# Creates a normal change request that will require approval.
grpID = self.getGroupID(grpName)
userID = self.getUserID(assignee)
chngCoordID = self.getUserID(coordinator)
chngMngrID = self.getUserID(approver)
apiCall = "https://{0}/api/sn_chg_rest/change/normal".format(self.snInstance)
payload = {
"category": category,
"u_subcategory": subCategory,
"u_change_manager": chngMngrID,
"assigned_to": userID,
"u_change_coordinator": chngCoordID,
"assignment_group": grpID,
"short_description": shortDesc,
"description": desc,
"justification": justDesc,
"implementation_plan": implmntPlan,
"risk_impact_analysis": riskImpact,
"backout_plan": backoutPlan,
"test_plan": testPlan,
"start_date": sTime,
"end_date": eTime,
"cab_required": False,
}
encoded_payload=json.dumps(payload)
changeResponse = requests.post(apiCall, auth=(self.user, self.pwd), headers=self.headers, data=encoded_payload)
changeData = changeResponse.json()
return changeData
def CreateStandardChange(self, templateName, grpName, assignee, coordinator, approver, category, subcategory, txtShortDesc, txtJustification):
templateID = self.getStandardChangeTemplateID(templateName)
grpID = self.getGroupID(grpName)
userID = self.getUserID(assignee)
chngCoordID = self.getUserID(coordinator)
chngMngrID = self.getUserID(approver)
sTime = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
eTime = (datetime.now()+timedelta(minutes=+1)).strftime("%Y-%m-%d %H:%M:%S")
apiCall = "https://{0}/api/sn_chg_rest/change/standard/{1}".format(self.snInstance,templateID)
payload={
"category": category,
"u_subcategory": subcategory,
"u_change_manager": chngMngrID,
"assigned_to": userID,
"u_change_coordinator": chngCoordID,
"assignment_group": grpID,
"short_description": txtShortDesc,
"justification": txtJustification,
"start_date": sTime,
"end_date": eTime
}
encoded_payload=json.dumps(payload)
apiResponse = requests.post(apiCall, auth=(self.user, self.pwd), headers=self.headers, data=encoded_payload)
return apiResponse.json()
######################################### END CLASS ################################
@@ -0,0 +1,502 @@
import requests
import json
import os
import pytz
from datetime import datetime
from datetime import timedelta
from datetime import time
# API Documentation
# https://docs.servicenow.com/bundle/sandiego-application-development/page/build/applications/concept/api-rest.html
class SnowAPI:
def __init__(self, snInstance):
if not ((os.environ.get('SN_USER')) and (os.environ.get('SN_PASS'))):
print("\n*** Environment variables are not set for ServiceNow Authentication ***")
exit()
self.snInstance = snInstance
self.headers = {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
self.user = os.environ['SN_USER']
self.pwd = os.environ['SN_PASS']
def getGroupID(self, grpName):
grpName = grpName.replace(" ","%20")
url = 'https://{}/api/now/table/sys_user_group?sysparm_query=name={}'.format(self.snInstance, grpName)
apiResponse = requests.get(url, auth=(self.user, self.pwd), headers=self.headers)
apiData = apiResponse.json()
return apiData['result'][0]['sys_id']
def getUserID(self, userName):
userName = userName.replace(" ","%20")
apiCall = 'https://{}/api/now/table/sys_user?sysparm_query=user_name={}'.format(self.snInstance, userName)
apiResponse = requests.get(apiCall, auth=(self.user, self.pwd), headers=self.headers)
apiData = apiResponse.json()
return apiData['result'][0]['sys_id']
def getCMDBItemByIP(self, pattern):
apiCall = "https://{0}/api/now/table/cmdb_ci_server?sysparm_query=ip_address={1}&sysparm_limit=10".format(self.snInstance, pattern)
apiResponse = requests.get(apiCall, auth=(self.user, self.pwd), headers=self.headers)
apiData = apiResponse.json()
try:
appNameRec = self.getCMDBAppById(apiData['result'][0]['u_nd_application_svc']['value'])
appName = appNameRec['name']
apiData['result'][0].update({
"u_nd_application_svc": {
"link": apiData['result'][0]['u_nd_application_svc']['link'],
"value": apiData['result'][0]['u_nd_application_svc']['value'],
"name": appName
}
})
except:
apiData['result'][0].update({
"u_nd_application_svc": {
"name": "UNDEFINED"
},
"environment": "UNDEFINED"
})
return apiData['result']
def getCMDBItemByHostName(self, pattern):
apiCall = "https://{0}/api/now/table/cmdb_ci_server?sysparm_query=host_name={1}&sysparm_limit=10".format(self.snInstance, pattern)
apiResponse = requests.get(apiCall, auth=(self.user, self.pwd), headers=self.headers)
apiData = apiResponse.json()
try:
appNameRec = self.getCMDBAppById(apiData['result'][0]['u_nd_application_svc']['value'])
appName = appNameRec['name']
apiData['result'][0].update({
"u_nd_application_svc": {
"link": apiData['result'][0]['u_nd_application_svc']['link'],
"value": apiData['result'][0]['u_nd_application_svc']['value'],
"name": appName
}
})
except:
apiData['result'][0].update({
"u_nd_application_svc": {
"name": "UNDEFINED"
},
"environment": "UNDEFINED"
})
return apiData['result']
def getCMDBItemByFQDN(self, pattern):
apiCall = "https://{0}/api/now/table/cmdb_ci_server?sysparm_query=fqdn={1}&sysparm_limit=10".format(self.snInstance, pattern)
apiResponse = requests.get(apiCall, auth=(self.user, self.pwd), headers=self.headers)
apiData = apiResponse.json()
if len(apiData['result']) > 0:
try:
appNameRec = self.getCMDBAppById(apiData['result'][0]['u_nd_application_svc']['value'])
appName = appNameRec['name']
apiData['result'][0].update({
"u_nd_application_svc": {
"link": apiData['result'][0]['u_nd_application_svc']['link'],
"value": apiData['result'][0]['u_nd_application_svc']['value'],
"name": appName
}
})
except:
print("Result 2: {0}".format(apiData))
apiData['result'][0].update({
"u_nd_application_svc": {
"name": "UNDEFINED"
},
"environment": "UNDEFINED"
})
return apiData['result']
def getCMDBAppById(self, sysId):
apiCall = "https://{0}/api/now/table/cmdb_ci_service_auto?sys_id={1}".format(self.snInstance, sysId)
apiResponse = requests.get(apiCall, auth=(self.user, self.pwd), headers=self.headers)
apiData = apiResponse.json()
return apiData['result'][0]
def getStandardChangeTemplateID(self, templateName):
# Could be improved, might throw unexpected results if the group isn't found
# Needs error handling
allData = []
apiCall = "https://{0}/api/sn_chg_rest/change/standard/template?sysparm_query=active=true".format(self.snInstance)
apiResponse = requests.get(apiCall, auth=(self.user, self.pwd), headers=self.headers)
data = apiResponse.json()
for i in data['result']:
allData.append(i)
for record in allData:
if 'sys_name' in record:
if record['sys_name']['display_value'] == templateName:
sysID = record['sys_id']['value']
break
return sysID
def getRequestItemFromReqNum(self, reqNum):
apiCall = "https://{0}/api/now/table/sc_req_item?sysparm_query=request.number={1}&sysparm_limit=1".format(self.snInstance, reqNum)
apiResponse = requests.get(apiCall, auth=(self.user, self.pwd), headers=self.headers)
data = apiResponse.json()
item = data['result'][0]['number']
return item
def getTaskNumFromReqNum(self, reqNum):
apiCall = "https://{0}/api/now/table/sc_task?sysparm_query=request.number={1}&sysparm_limit=1".format(self.snInstance, reqNum)
apiResponse = requests.get(apiCall, auth=(self.user, self.pwd), headers=self.headers)
data = apiResponse.json()
item = data['result'][0]['number']
return item
def getTaskSysIdFromReqNum(self, reqNum):
apiCall = "https://{0}/api/now/table/sc_task?sysparm_query=request.number={1}&sysparm_limit=1".format(self.snInstance, reqNum)
apiResponse = requests.get(apiCall, auth=(self.user, self.pwd), headers=self.headers)
data = apiResponse.json()
item = data['result'][0]['sys_id']
return item
def getServiceCatalogs(self):
apiCall = "https://{0}/api/sn_sc/servicecatalog/catalogs".format(self.snInstance)
apiResponse = requests.get(apiCall, auth=(self.user, self.pwd), headers=self.headers)
items = apiResponse.json()
return items
def getServiceCatalogCategories(self, sys_id):
apiCall = "https://{0}/api/sn_sc/servicecatalog/catalogs/{1}/categories".format(self.snInstance,sys_id)
apiResponse = requests.get(apiCall, auth=(self.user, self.pwd), headers=self.headers)
items = apiResponse.json()
return items
def getServiceCatalogItems(self):
apiCall = "https://{0}/api/sn_sc/servicecatalog/items?sysparm_limit=10000&sysparm_offset=0".format(self.snInstance)
apiResponse = requests.get(apiCall, auth=(self.user, self.pwd), headers=self.headers)
items = apiResponse.json()
return items
def getServiceCatalogItemByName(self, itemName):
apiCall = "https://{0}/api/sn_sc/servicecatalog/items?sysparm_limit=10000&sysparm_offset=0".format(self.snInstance)
apiResponse = requests.get(apiCall, auth=(self.user, self.pwd), headers=self.headers)
items = apiResponse.json()
for item in items['result']:
if item['name'] == itemName:
return item
def getSpecificCatalogItem(self, sysId):
apiCall = "https://{0}/api/sn_sc/servicecatalog/items/{1}".format(self.snInstance,sysId)
apiResponse = requests.get(apiCall, auth=(self.user, self.pwd), headers=self.headers)
items = apiResponse.json()
return items
def getDataFromTaskByReqNum(self, sctask, reqNum):
# This is only written to handle one task per REQ number!!!
apiCall = "https://{0}/api/now/table/sc_task?sysparm_query=request.number={1}&sysparm_fields=state%2Cnumber%2Csys_id%2Cdescription%2Cshort_description%2Cvariables.application_name%2Cvariables.additional_comments%2Cclose_notes%2Cclosed_by&sysparm_limit=1&sysparm_display_value=true".format(self.snInstance, reqNum)
apiResponse = requests.get(apiCall, auth=(self.user, self.pwd), headers=self.headers)
data = apiResponse.json()
if data['result'][0]['number'] == sctask:
items = data['result'][0]
else:
items = ""
return items
def updateTaskDescriptions(self, sysId, short_desc, desc):
apiCall = "https://{0}/api/now/table/sc_task/{1}".format(self.snInstance,sysId)
payload = {
'short_description': short_desc,
'description': desc
}
encoded_payload = json.dumps(payload)
apiResponse = requests.put(apiCall, auth=(self.user, self.pwd), headers=self.headers, data=encoded_payload)
if apiResponse.status_code != 201 and apiResponse.status_code != 200:
print('Failed to update task:')
print('\tStatus:', apiResponse.status_code)
print('\tError Response:',apiResponse.json())
exit(1)
else:
return apiResponse.json()
def assignTaskToUser(self, sysId, userName):
userID = self.getUserID(userName)
apiCall = "https://{0}/api/now/table/sc_task/{1}".format(self.snInstance,sysId)
payload = {
"assigned_to": userID
}
encoded_payload = json.dumps(payload)
apiResponse = requests.put(apiCall, auth=(self.user, self.pwd), headers=self.headers, data=encoded_payload)
if apiResponse.status_code != 201 and apiResponse.status_code != 200:
print('Failed to update task:')
print('\tStatus:', apiResponse.status_code)
print('\tError Response:',apiResponse.json())
exit(1)
else:
return apiResponse.json()
def postAppServerRequest(self,itemID,requestFor,requestBy,approver,requestType,comments,appName,env):
requestForID = self.getUserID(requestFor)
requestByID = self.getUserID(requestBy)
approvalID = self.getUserID(approver)
# Statically set these so we always force approvals to go to Computer Systems
departmentID = "f3c65cef1bfed050bba0113fad4bcb1d"
departmentCode = "112"
divisionID = "f40758231b321450bba0113fad4bcb2d"
divisionCode = "32"
payload = {
"get_portal_messages" : "true",
"sysparm_quantity" : "1",
"sysparm_no_validation" : "true",
"variables" : {
"v_approval_department" : departmentID,
"v_approval_department_code" : departmentCode,
"v_approval_division" : divisionID,
"v_approval_division_code" : divisionCode,
"v_manager" : approvalID,
"v_requested_by" : requestByID,
"v_requested_for" : requestForID,
"request_type" : requestType,
"additional_comments" : comments,
"application_name" : appName,
"environment" : env,
"require_hosting_quote" : "No",
"add_change_disaster_recovery" : "No"
}
}
apiCall = ('https://{0}/api/sn_sc/v1/servicecatalog/items/{1}/order_now').format(self.snInstance,itemID)
encoded_payload = json.dumps(payload)
apiResponse = requests.post(apiCall, auth=(self.user, self.pwd), headers=self.headers, data=encoded_payload)
if apiResponse.status_code != 201 and apiResponse.status_code != 200:
print('Failed to create ticket:')
print('\tStatus:', apiResponse.status_code)
print('\tError Response:',apiResponse.json())
exit(1)
else:
return apiResponse.json()
return apiResponse
def postGenericSerivceRequest(self,itemID,requestFor,requestBy,approver,requestType,comments):
requestForID = self.getUserID(requestFor)
requestByID = self.getUserID(requestBy)
approvalID = self.getUserID(approver)
departmentID = "f3c65cef1bfed050bba0113fad4bcb1d"
divisionID = "f40758231b321450bba0113fad4bcb2d"
payload = {
"get_portal_messages" : "true",
"sysparm_quantity" : "1",
"sysparm_no_validation" : "true",
"variables" : {
"v_approval_department" : departmentID,
"v_approval_division" : divisionID,
"v_manager" : approvalID,
"v_requested_by" : requestByID,
"v_requested_for" : requestForID,
"v_type" : requestType,
"additional_comments" : comments
}
}
apiCall = ('https://{0}/api/sn_sc/v1/servicecatalog/items/{1}/order_now').format(self.snInstance,itemID)
encoded_payload = json.dumps(payload)
apiResponse = requests.post(apiCall, auth=(self.user, self.pwd), headers=self.headers, data=encoded_payload)
if apiResponse.status_code != 201 and apiResponse.status_code != 200:
print('Failed to create ticket:')
print('\tStatus:', apiResponse.status_code)
print('\tError Response:',apiResponse.json())
exit(1)
else:
return apiResponse.json()
return apiResponse
def getGenericData(self, url):
apiCall = "https://{0}/".format(self.snInstance,url)
apiResponse = requests.get(apiCall, auth=(self.user, self.pwd), headers=self.headers)
items = apiResponse.json()
return items
def submitTicket(self, user, subject, content):
apiCall = ('https://{}/api/now/table/incident').format(self.snInstance)
userID=self.getUserID(user)
payload = {
'caller_id': userID,
'short_description': subject,
'description': content
}
encoded_payload = json.dumps(payload)
apiResponse = requests.post(apiCall, auth=(self.user, self.pwd), headers=self.headers, data=encoded_payload)
if apiResponse.status_code != 201 and apiResponse.status_code != 200:
print('Failed to create ticket:')
print('\tStatus:', apiResponse.status_code)
print('\tError Response:',apiResponse.json())
exit(1)
else:
return apiResponse.json()
def assignTicketToGroup(self, sysId, grpName):
grpId = self.getGroupID(grpName)
url = 'https://{}/api/now/table/incident/{}'.format(self.snInstance,sysId)
#Statically set the group to storage, this value was derived by decoding the web URL on storage tickets
payload = {
'assignment_group': grpId
}
encoded_payload = json.dumps(payload)
apiResponse = requests.put(url, auth=(self.user, self.pwd), headers=self.headers, data=encoded_payload)
if apiResponse.status_code != 201 and apiResponse.status_code != 200:
print('Failed to create ticket:')
print('\tStatus:', apiResponse.status_code)
print('\tError Response:',apiResponse.json())
exit(1)
else:
return apiResponse.json()
def assessNormalChange(self, sysID):
apiCall = "https://{0}/api/sn_chg_rest/change/normal/{1}".format(self.snInstance,sysID)
payload = {
"state": "assess"
}
encoded_payload=json.dumps(payload)
apiResponse = requests.patch(apiCall, auth=(self.user, self.pwd), headers=self.headers, data=encoded_payload)
return apiResponse.json()
def addStandardChangeNotes(self, sysID, txtWorkNotes):
apiCall = "https://{0}/api/sn_chg_rest/change/standard/{1}".format(self.snInstance,sysID)
payload = {
"work_notes": txtWorkNotes
}
encoded_payload=json.dumps(payload)
apiResponse = requests.patch(apiCall, auth=(self.user, self.pwd), headers=self.headers, data=encoded_payload)
return apiResponse.json()
def scheduleStandardChange(self, sysID):
apiCall = "https://{0}/api/sn_chg_rest/change/standard/{1}".format(self.snInstance,sysID)
payload = {
"state": "Scheduled"
}
encoded_payload=json.dumps(payload)
apiResponse = requests.patch(apiCall, auth=(self.user, self.pwd), headers=self.headers, data=encoded_payload)
return apiResponse.json()
def implementStandardChange(self, sysID):
apiCall = "https://{0}/api/sn_chg_rest/change/standard/{1}".format(self.snInstance, sysID)
payload = {
"state": "Implement"
}
encoded_payload=json.dumps(payload)
apiResponse = requests.patch(apiCall, auth=(self.user, self.pwd), headers=self.headers, data=encoded_payload)
return apiResponse.json()
def reviewStandardChange(self, sysID):
apiCall = "https://{0}/api/sn_chg_rest/change/standard/{1}".format(self.snInstance, sysID)
payload = {
"state": "Review"
}
encoded_payload=json.dumps(payload)
apiResponse = requests.patch(apiCall, auth=(self.user, self.pwd), headers=self.headers, data=encoded_payload)
return apiResponse.json()
def closeStandardChange(self, sysID, code, notes):
apiCall = "https://{0}/api/sn_chg_rest/change/standard/{1}".format(self.snInstance, sysID)
payload = {
"state": "Closed",
"close_code": code,
"close_notes": notes
}
encoded_payload=json.dumps(payload)
apiResponse = requests.patch(apiCall, auth=(self.user, self.pwd), headers=self.headers, data=encoded_payload)
return apiResponse.json()
def CreateNormalChange(self, grpName, assignee, coordinator, approver, category, subCategory, shortDesc, desc, justDesc, implmntPlan, riskImpact, backoutPlan, testPlan, sTime, eTime):
# Creates a normal change request that will require approval.
grpID = self.getGroupID(grpName)
userID = self.getUserID(assignee)
chngCoordID = self.getUserID(coordinator)
chngMngrID = self.getUserID(approver)
apiCall = "https://{0}/api/sn_chg_rest/change/normal".format(self.snInstance)
payload = {
"category": category,
"u_subcategory": subCategory,
"u_change_manager": chngMngrID,
"assigned_to": userID,
"u_change_coordinator": chngCoordID,
"assignment_group": grpID,
"short_description": shortDesc,
"description": desc,
"justification": justDesc,
"implementation_plan": implmntPlan,
"risk_impact_analysis": riskImpact,
"backout_plan": backoutPlan,
"test_plan": testPlan,
"start_date": sTime,
"end_date": eTime,
"cab_required": False,
}
encoded_payload=json.dumps(payload)
changeResponse = requests.post(apiCall, auth=(self.user, self.pwd), headers=self.headers, data=encoded_payload)
changeData = changeResponse.json()
return changeData
def CreateStandardChange(self, templateName, grpName, assignee, coordinator, approver, category, subcategory, txtShortDesc, txtJustification):
templateID = self.getStandardChangeTemplateID(templateName)
grpID = self.getGroupID(grpName)
userID = self.getUserID(assignee)
chngCoordID = self.getUserID(coordinator)
chngMngrID = self.getUserID(approver)
sTime = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
eTime = (datetime.now()+timedelta(minutes=+1)).strftime("%Y-%m-%d %H:%M:%S")
apiCall = "https://{0}/api/sn_chg_rest/change/standard/{1}".format(self.snInstance,templateID)
payload={
"category": category,
"u_subcategory": subcategory,
"u_change_manager": chngMngrID,
"assigned_to": userID,
"u_change_coordinator": chngCoordID,
"assignment_group": grpID,
"short_description": txtShortDesc,
"justification": txtJustification,
"start_date": sTime,
"end_date": eTime
}
encoded_payload=json.dumps(payload)
apiResponse = requests.post(apiCall, auth=(self.user, self.pwd), headers=self.headers, data=encoded_payload)
return apiResponse.json()
######################################### END CLASS ################################
@@ -0,0 +1,175 @@
import os
import argparse
import json
import urllib3
import requests
from requests_ntlm import HttpNtlmAuth
# Use pip install requests_ntlm to install the necessary modules
class API(object):
def __init__(self):
if not ((os.environ.get('ITD_SHAREPOINT_PASS')) and (os.environ.get('ITD_SHAREPOINT_USER'))):
print("\n*** ERROR: Environment Variables are not set for SharePoint ***")
print("Set OS environment variables ITD_SHAREPOINT_PASS and ITD_SHAREPOINT_USER")
exit(1)
else:
# Username is in the format of 'NDGOV\\<userName>'
sp_user = os.environ['ITD_SHAREPOINT_USER']
sp_pass = os.environ['ITD_SHAREPOINT_PASS']
self.server_url = "https://share.nd.gov/"
self.site_url = self.server_url + "itd/Computer-Systems/Distributed-Systems/VMWare/_api/web/"
self.context_url = self.server_url + "itd/Computer-Systems/Distributed-Systems/VMWare/_api/contextinfo/"
self.env = "All"
self.headers = {
"Accept":"application/json; odata=verbose",
"Content-Type":"application/json; odata=verbose",
"odata":"verbose",
"X-RequestForceAuthentication":"true"
}
self.auth = HttpNtlmAuth(sp_user, sp_pass)
def get_AllNodes(self):
list_name = 'VM Guests'
# Get list information
requests.packages.urllib3.disable_warnings()
r = requests.get(self.site_url + "lists/GetByTitle('%s')" % list_name, auth=self.auth, headers=self.headers, verify=False)
list_guid = r.json()['d']['Id']
list_itemcount = r.json()['d']['ItemCount']
# Query list items
api_items_url = self.site_url + "Lists(guid'%s')/Items" % list_guid
concat_items = []
# Parse the list
requests.packages.urllib3.disable_warnings()
cur_page = requests.get(api_items_url, auth=self.auth, headers=self.headers, verify=False)
concat_items += cur_page.json()['d']['results']
# Sharepoint uses pagination with '__next' in the JSON results to indicate additional pages of information
# are present. Loop through the pages and concatenate the information into one dataset
while '__next' in cur_page.json()['d']:
requests.packages.urllib3.disable_warnings()
cur_page = requests.get(cur_page.json()['d']['__next'], auth=self.auth, headers=self.headers, verify=False)
concat_items += cur_page.json()['d']['results']
return concat_items
def get_ActiveNodes(self, e):
self.env = e
list_name = 'VM Guests'
# Get list information
requests.packages.urllib3.disable_warnings()
r = requests.get(self.site_url + "lists/GetByTitle('%s')" % list_name, auth=self.auth, headers=self.headers, verify=False)
list_guid = r.json()['d']['Id']
list_itemcount = r.json()['d']['ItemCount']
# Query list items
api_items_url = self.site_url + "Lists(guid'%s')/Items" % list_guid
if self.env == 'All':
active_items_url = api_items_url + "?$filter=(Status eq 'Current') or (Status eq 'Change')"
else:
active_items_url = api_items_url + "?$filter=(Environment eq '%s') and ((Status eq 'Current') or (Status eq 'Change'))" % self.env
concat_items = []
# Parse the list
requests.packages.urllib3.disable_warnings()
cur_page = requests.get(active_items_url, auth=self.auth, headers=self.headers, verify=False)
concat_items += cur_page.json()['d']['results']
# Sharepoint uses pagination with '__next' in the JSON results to indicate additional pages of information
# are present. Loop through the pages and concatenate the information into one dataset
while '__next' in cur_page.json()['d']:
requests.packages.urllib3.disable_warnings()
cur_page = requests.get(cur_page.json()['d']['__next'], auth=self.auth, headers=self.headers, verify=False)
concat_items += cur_page.json()['d']['results']
return concat_items
def find_VM(self, hostname):
domains = [".nd.gov", ".ndtestdev.nd.dev", ".ndcloud.gov", ".testnd.gov", ".itd.nd.gov", ".ns.nd.gov", ".cloudapp.azure.com", ".uat.mmis.nd.gov", ".test.ndcloud.gov", ".k12tst.nd.us", ".legend.nd.gov", ".tst.mmis.dev", ".trn.mmis.nd.gov", ".sit.mmis.nd.gov", ".nd.gov-mag", ".uat.mmis.dev", ".mo.legend.nd.gov", ".video.nd.gov", ".mmis.nd.gov", ".sit.legend.nd.gov", ".trn.mmis.dev", ".prm.mmis.nd.gov", ".state.nd.us", ".k12.nd.us", ".stg.k12.nd.us", ".sit.mmis.dev", "ndnic.com"]
for domain in domains:
try:
data = self.get_Node(hostname + domain)
if 'Title' in data[0]:
hostname = data[0]['Title']
break
except:
continue
return(hostname)
def get_Node(self, server):
myNode = server
list_name = 'VM Guests'
# Get list information
requests.packages.urllib3.disable_warnings()
r = requests.get(self.site_url + "lists/GetByTitle('%s')" % list_name, auth=self.auth, headers=self.headers, verify=False)
list_guid = r.json()['d']['Id']
# Query list items
api_items_url = self.site_url + "Lists(guid'%s')/Items" % list_guid
singleServer_url = api_items_url + "?$filter=Title eq '%s'" % myNode
cur_page = requests.get(singleServer_url, auth=self.auth, headers=self.headers, verify=False)
return cur_page.json()['d']['results']
def get_Agency(self, aID):
agencyID = aID
list_name = 'Agency'
# Get list information
requests.packages.urllib3.disable_warnings()
r = requests.get(self.site_url + "lists/GetByTitle('%s')" % list_name, auth=self.auth, headers=self.headers, verify=False)
list_guid = r.json()['d']['Id']
# Query list items
api_items_url = self.site_url + "Lists(guid'%s')/Items" % list_guid
singleServer_url = api_items_url + "?$filter=ID eq '%s'" % agencyID
cur_page = requests.get(singleServer_url, auth=self.auth, headers=self.headers, verify=False)
return cur_page.json()['d']['results'][0]['Title']
def get_AppName(self, appID):
applicationID = appID
list_name = 'VM App Name'
# Get list information
requests.packages.urllib3.disable_warnings()
r = requests.get(self.site_url + "lists/GetByTitle('%s')" % list_name, auth=self.auth, headers=self.headers, verify=False)
list_guid = r.json()['d']['Id']
# Query list items
api_items_url = self.site_url + "Lists(guid'%s')/Items" % list_guid
singleServer_url = api_items_url + "?$filter=ID eq '%s'" % applicationID
cur_page = requests.get(singleServer_url, auth=self.auth, headers=self.headers, verify=False)
return cur_page.json()['d']['results'][0]['Title']
def get_AppNodes(self, appID):
applicationID = appID
list_name = 'VM Guests'
# Get list information
requests.packages.urllib3.disable_warnings()
r = requests.get(self.site_url + "lists/GetByTitle('%s')" % list_name, auth=self.auth, headers=self.headers, verify=False)
list_guid = r.json()['d']['Id']
# Query list items
api_items_url = self.site_url + "Lists(guid'%s')/Items" % list_guid
singleServer_url = api_items_url + "?$filter=AppName eq '%s'" % appID
cur_page = requests.get(singleServer_url, auth=self.auth, headers=self.headers, verify=False)
return cur_page.json()['d']['results']
+770
View File
@@ -0,0 +1,770 @@
#!/usr/bin/python
#-------------------------------------------------------------------------------------------------#
# Author: Cliff Cogdill
# Description: This script accomplishes a few things:
# - Reviews all assets in the CMDB to determine if they have been setup in backups.
# - If not, identify them and send create a ticket for investigation.
# - Reviews paused protection jobs to determine if the job is still empty.
# - If not empty, resume the job.
# - Send summary report of unprotected servers and VM jobs to the storage team.
# ------------------------------------------------------------------------------------------------#
import sys,argparse,json,time,yaml,re,smtplib
from email.message import EmailMessage
sys.path.insert(0, './classes/')
import cohesityAPI as cohesity
import sharePointAPI as sharePoint
import automationsAPI as dashboard
import serviceNowAPI as snow
#import nditServiceNow as snow
import logging
#import pymsteams
def GetArgs():
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument('--cluster', '-c', type=str, action='store')
parser.add_argument('--debug', '-d', action='store_true')
parser.add_argument('--excludeTickets', '-x', action='store_true')
parser.add_argument('--refresh', '-r', action='store_true')
parser.add_argument('--type', '-t', type=str, action='store')
parser.add_argument('--vCenter', '-v', type=str, action='store')
parser.add_argument('--help', '-h', action='store_true')
return (parser.parse_args())
def SendEmail(body, cluster):
if debug:
recipients = ['itbackups@nd.gov']
else:
recipients = ['zmeier@nd.gov', 'cecogdill@nd.gov','khellman@nd.gov','mlaverdure@nd.gov','ndheck@nd.gov']
email = EmailMessage()
email['Subject'] = "Backup Review for {0}".format(cluster)
email['From'] = "No-Reply@nd.gov"
email['To'] = ", ".join(recipients)
email.set_content(body)
with smtplib.SMTP('apprelay.nd.gov') as smtp:
smtp.send_message(email)
if args.debug:
print("Sent email")
def PrintHelp():
print("\nBasic Usage:")
print("\n python3 protectedItems.py -c cluster1.domain.tld [ -v vCenter.domain.tld ] [ -j protectionJobName ]")
print("\t -d debug: execute in debug mode to expand summary and do not open tickets")
print("\t -x excludeTickets: run against prod serivce now but do not open tickets or changes")
print("\t -r refresh: force source refresh in debug mode")
print("\t -c FQDN of Cohesity cluster address")
print("\t -t type of protection source")
print("\t -h Prints this help message")
#itdTeamsMessage = pymsteams.connectorcard("https://ndgov.webhook.office.com/webhookb2/aad89030-8f69-4a4d-a853-c882cbb01a10@2dea0464-da51-4a88-bae2-b3db94bc0c54/IncomingWebhook/d3c07921419c4920810d0c32558c05e3/edcc1502-1ff0-4c3e-9fe2-1ce3f9f7981a")
args = GetArgs()
if args.help:
PrintHelp()
exit()
logging.basicConfig(filename="log/" + args.cluster + '.log', filemode='w', format='%(asctime)s %(message)s', datefmt='%Y-%m-%d %I:%M:%S %p', level=logging.DEBUG)
# Define empty lists that will hold JSON dictionaries
vmObjects = []
protectedObjects = []
vCenters = []
protectedVM = []
unprotectedVM = []
undocumentedVM = []
vmExemptions = []
populatedJobs = []
populatedPausedJobs = []
jobExemptions = []
emptyJobs = []
tickets = []
# Default to the main cluster on premise if the cluster argument was not specified
if not args.cluster:
args.cluster = "itdmdndpc01.nd.gov"
args.type = "kVMware"
if not args.type:
args.type = "kVMware"
if args.cluster == "itdazdpc01.nd.gov":
args.type = "kAzure"
if args.cluster == "itdazdpc02.nd.gov":
args.type = "kAzure"
logging.info("Creating cluster object for {0} with type {1}".format(args.cluster, args.type))
# Setup the token for cluster API usage
cluster = cohesity.API(args.cluster)
authToken = cluster.GetAuthToken()
cluster.UpdateHeaders(authToken['accessToken'])
# Lookup the AppNames and vCenter for unprotected VMs
logging.info("Creating cmdb object for SharePoint list")
#cmdb = sharePoint.API()
cmdb = snow.SnowAPI("northdakota.service-now.com")
# When true, tickets will only be created in ServiceNow dev and JSON output may be expanded to stdout
if args.debug:
debug = True
else:
debug = False
if args.excludeTickets:
excludeTickets = True
else:
excludeTickets = False
# Create the connection to ServiceNow
logging.info("Creating ticketSystem object for ServiceNow")
if debug:
ticketSystem = snow.SnowAPI("northdakotadev.service-now.com")
else:
ticketSystem = snow.SnowAPI("northdakota.service-now.com")
if args.type == "kPhysical":
physicalJobs = cluster.GetPhysicalJobs()
sys.exit("Warning: Type not implmented for this script.")
elif args.type == "kSQL":
sys.exit("Warning: Type not implmented for this script.")
elif args.type == "kAzure":
# Get all registered subscription sources so we can use the source ids for other REST calls
logging.info("Collecting all registered Azure sources")
nodes = cluster.GetAZsources()
# Iterate through all the Azure subscriptions to create one large dataset for all VMs
logging.info("Creating dataset for all VMs in our Azure subscriptions")
logging.info("Creating dataset for all protected VMs in our Azure subscriptions")
for node in nodes:
hypervisorId = node['protectionSource']['id']
#Refresh the sources so we ensure we are getting valid information
if debug:
print("Debug enabled, not refreshing source use -r option to override")
else:
cluster.RefreshSource(node['protectionSource']['id'])
if args.refresh:
cluster.RefreshSource(node['protectionSource']['id'])
myObjects = cluster.GetAZObjects(hypervisorId)
myProtectedObjects = cluster.GetProtectedAZobjects(hypervisorId)
# Combine all VM objects into one dictionary
for val in myObjects:
vmObjects.append(val)
# Combine all protected objects into one dictionary
for val in myProtectedObjects:
protectedObjects.append(val)
# Get all the native snapshot protection jobs that exist in the cluster
logging.info("Gathering all current protection jobs from {0}".format(args.cluster))
sourceJobs = cluster.GetAZNativeJobs()
elif args.type == "kVMware":
# Get all registered vCenter sources so we can use the source ids for other REST calls
logging.info("Collecting all registered vmWare sources")
nodes = cluster.GetVMsources()
# Iterate through all the vCenter servers to create one large dataset for all VMs
logging.info("Creating dataset for all VMs in our Azure subscriptions")
logging.info("Creating dataset for all protected VMs in our Azure subscriptions")
for node in nodes:
hypervisorId = node['protectionSource']['id']
#Refresh the sources so we ensure we are getting valid information
cluster.RefreshSource(node['protectionSource']['id'])
myObjects = cluster.GetVMObjects(hypervisorId)
myProtectedObjects = cluster.GetProtectedVMobjects(hypervisorId)
# Combine all VM objects into one dictionary
for val in myObjects:
vmObjects.append(val)
# Combine all protected objects into one dictionary
for val in myProtectedObjects:
protectedObjects.append(val)
# At this point we have two sets of data
# 1. All vms objects whether they are protected or not ( vmObjects )
# 2. All protected vm objects ( protectedObjects )
# Get all the protection jobs that exist for vmware in the cluster
logging.info("Gathering all current protection jobs from {0}".format(args.cluster))
sourceJobs = cluster.GetVMjobs()
if args.type == "kVMware" or args.type == "kAzure":
############################## BEGIN: IDENTIFY EMPTY & POPULATE PAUSED JOBS #######################################
# Loop through the sourceJobs to see if they show up in at least once in the list of protected objects.
# if they don't show up, we can assume the job is created but empty, which is usually the case for
# placeholder VMs
# In certain circumstances, jobs can be defined but exempt from normal processing. These jobs are normally used from time to time for POC work and do not necessarily need to be protected, here we will load them into the exempt jobs array from the exceptions.yml file.
logging.info("Opening exemptions file: vars/exemptions.yml")
with open("./vars/exemptions.yml", "r") as exemptionJobData:
try:
exemptionJobData = yaml.load(exemptionJobData, Loader=yaml.FullLoader)
except:
print("Unable to load exemptions file.")
exemptJobs = []
logging.info("Parsing exemptions YAML")
for i in exemptionJobData:
for e in exemptionJobData[i]['jobs']:
#exemptJobs.append(e.lower())
exemptJobs.append([exemptionJobData[i]['id'],e.lower()])
# Loop through all the jobs for
logging.info("Iterating through all protection jobs")
print("Starting interation of all protection jobs")
for sourceJob in sourceJobs:
#Set default control variables for each job
isJobExempt = False
isJobEmpty = True
# The REST GetVMjobs function returns unfiltered jobs, so lets get rid of the deleted ones
if 'isDeleted' in sourceJob:
if sourceJob['isDeleted'] == True:
logging.info("--Job {0} is marked for deletion, skipping review".format(sourceJob['name']))
continue
# Look through the exemptJobs listing
for entry in exemptJobs:
parent = entry[0]
pattern = entry[1]
if parent == sourceJob['parentSourceId']:
if re.search(pattern, sourceJob['name'].lower()):
isJobExempt = True
break
if isJobExempt == True:
jobExemptions.append(sourceJob['name'])
if debug == True:
logging.info("--Job {0} has been exempt from backups, skipping review".format(sourceJob['name']))
print("Job {0} is exempt from tickets".format(sourceJob['name']))
continue
# The protection jobs only show tags, they do not show VM ojbects; therefore, to determine if the job is still valid, we have to iterate through all protected objects to see if the job name shows up or not. If it does not show up, then it is assumed to be empty and paused to prevent backup errors. Also, there is usually only one job listed per protected VM; however, there could be cases where a VM is in two or more jobs so we need to check each job listed.
# Sample dataset can be seen by using the following url:
# https://itdmdndpc01.nd.gov/irisservices/api/v1/public/protectionSources/protectedObjects?environment=kVMware&id=3156
logging.info("--Iterating through all protected objects to verify if job {0} is empty".format(sourceJob['name']))
for item in protectedObjects:
for pJob in item['protectionJobs']:
if pJob['name'] == sourceJob['name']:
isJobEmpty = False
continue
# Seperate empty from non-empty jobs
if isJobEmpty:
logging.info("--{0} is empty, pausing future runs".format(sourceJob['name']))
emptyJobs.append(sourceJob)
# Pause the job so we don't generate erroneous errors.
cluster.PauseJob(sourceJob['id'])
else:
# The job is not empty, lets check to see if it is paused so we can create an alert.
# First we have to check if the isPaused key exists, if it has never been set, it will not.
if 'isPaused' in sourceJob:
# Now we check to see if isPaused is set to True
if sourceJob['isPaused']:
logging.info("----{0} is paused but contains VM objects, adding to populatedPausedJobs dataset".format(sourceJob['name']))
populatedPausedJobs.append(sourceJob)
# isPaused might be defined but it is set to False
else:
populatedJobs.append(sourceJob)
# If the job has never been paused, isPaused may not be defined
else:
populatedJobs.append(sourceJob)
############################## END: IDENTIFY EMPTY & POPULATE JOBS #######################################
logging.info("Iteration of all protection jobs is complete")
print("Iteration of all protection jobs is complete")
######################### BEGIN: OPEN TICKETS FOR POPULATED PAUSED JOBS ##################################
# This block of code is duplicate logic from above, the populatedPausedJobs array should not contain exempt jobs, we already checked for that. - CC 20230928
# if debug:
# print("Exempt jobs:\n{0}".format(exemptJobs))
#
# for job in populatedPausedJobs:
# isJobExempt = False
#
# for entry in exemptJobs:
# parent = entry[0]
# pattern = entry[1]
#
# if parent == job['parentSourceId']:
# if re.search(pattern, job['name'].lower()):
# isJobExempt = True
# break
#
# if isJobExempt:
# continue
# else:
logging.info("Opening tickets for paused jobs that contain VM objects")
for job in populatedPausedJobs:
if not excludeTickets:
subject = "Backup job " + job['name'] + " is paused"
message = "This backup job contains VMs and should not be paused per SLA. Either fix the issue and resume the job, exempt the VM(s) from backups, or decommission the VMs."
incident = ticketSystem.submitTicket("svccohesityadm", subject, message)
incidentID = incident['result']['sys_id']
ticketSystem.assignTicketToGroup(incidentID, "NDIT-Cloud Platforms")
logging.info("Ticket {0} created for {1}".format(incident['result']['number'],job['name']))
tickets.append("Ticket {0} created for {1}".format(incident['result']['number'],job['name']))
else:
jobExemptions.append(job['name'])
######################### END: OPEN TICKETS FOR POPULATED PAUSED JOBS ##################################
########################### END: JOB CENTRIC WORK #########################
############################## BEGIN: IDENTIFY UNPROTECTED VMS #################################
# RFE: 002
# Open the list of documented exceptions, in the long term this may be in the ServiceNow CMDB
# if it is, we would have to look it up using the SNOW API
with open("./vars/exemptions.yml", "r") as exemptionData:
try:
exemptionData = yaml.load(exemptionData, Loader=yaml.FullLoader)
except:
print("Unable to load exemptions file.")
exemptions = []
for i in exemptionData:
for e in exemptionData[i]['vms']:
#exemptions.append([exemptionData[i]['id'],e.lower()])
exemptions.append([exemptionData[i]['id'],e])
# Find protected / unprotected vms
logging.info("Iterating through all vmObjects found under sources registered to {0}".format(args.cluster))
print("Iterating through all vmObjects found under sources registered to {0}".format(args.cluster))
for vm in vmObjects:
# We don't need to monitor VMs that are marked as a template VM
if vm['environment'] == "kVMware":
if 'isVmTemplate' in vm['vmWareProtectionSource']:
if vm['vmWareProtectionSource']['isVmTemplate'] == True:
continue
# Assume all VM objects are not protected until we prove otherwise
isProtected = False
# Use flag to determine if VM was found or not
for pVM in protectedObjects:
if vm['name'] == pVM['protectionSource']['name']:
isProtected = True
protectedVM.append(vm)
break
# The Azure naming convention is different from the vmware naming convention, we need to normalize the names for sharepoint loookups
# and exemption lookups
# 4/22/2025
# Do we have to lookup the names to quantify the domain anymore?
# Will need a sample dataset from Cohesity call and SNOW lookups to determine.
if vm['environment'] == "kAzure":
# Azure VMs usually follow the naming convetion of vm-myserver.domain.com
# so we split on '-' and use the second part.
if "-" in vm['name']:
nameParts = vm['name'].split("-")
# SharePoint
#hostname= cmdb.find_VM(nameParts[1])
# ServiceNow
try:
cmdbData=cmdb.getCMDBItemByHostName(nameParts[1])
cmdbData=cmdbData[0]
except:
try:
print("INFO: possible name mismatch, searching via full name")
cmdbData = cmdb.getCMDBItemByHostName(vm['name'])
cmdbData=cmdbData[0]
except:
print(vm['name'] + " not found")
undocumentedVM.append(vm['name'])
continue
#if nameParts[1] == hostname:
if nameParts[1] != cmdbData['host_name']:
print("INFO: possible name mismatch, searching via full name")
# Data wasn't found, search for the full vm name
# SharePoint
#hostname = cmdb.find_VM(vm['name'])
# ServiceNow
cmdbData = cmdb.getCMDBItemByHostName(vm['name'])
cmdbData=cmdbData[0]
# 2024-04-23: This double lookup was created to handle an edge case where there was a
# '-' in the hostname. The VM has since been decomissioned but we will
# leave the logic in place but 'continue' the loop and write out a warning.
#if cmdbData['host_name'] == "vm-lcsdev1-1":
# # Sharepoint
# #hostname = cmdb.get_Node("vm-lcsdev1dev.centralus.cloudapp.azure.com")
# # ServiceNow
# cmdbData = cmdb.getCMDBItemByFQDN('vm-lcsdev1dev.centralus.cloudapp.azure.com')
# cmdbData=cmdbData[0]
else:
# SharePoint
# hostname = cmdb.find_VM(vm['name'])
# ServiceNow
try:
cmdbData = cmdb.getCMDBItemByFQDN(vm['name'])
cmdbData=cmdbData[0]
except:
print(vm['name'] + " not found")
undocumentedVM.append(vm['name'])
continue
vm.update({"name": cmdbData['fqdn']})
# This part needs some significant improvement on handling the exceptions. Ideally we would want to ensure the
# hostname that is excluded belongs to the same parent in the exemption file and in Cohesity.
if not isProtected:
isExempted = False
for entry in exemptions:
parent = entry[0]
pattern = entry[1]
if parent == vm['parentId']:
if re.search(pattern, vm['name'].lower()):
isExempted = True
vmExemptions.append(vm['name'])
break
else:
continue
if isExempted == False:
unprotectedVM.append(vm)
############################## END: IDENTIFY UNPROTECTED VMS #################################
###################### BEGIN: OPEN TICKETS FOR UNPROTECTED & UNEXEMPTED VMS ########################
# Setup an array to hold unique AppNames
unProtectedApps = []
for vm in unprotectedVM:
vcenterSource = cluster.GetFilteredRequest("/public/protectionSources", "?id=" + str(vm['parentId']))
vcenterName = vcenterSource[0]['protectionSource']['name'].split(".")[0]
if args.type == "kAzure":
if vcenterName == "437b2bfa-850e-4464-b6c2-38a68cda7c69":
vcenterName = "prd01"
policyData = cluster.GetProtectionPolicyByName("ITD-Bronze-PRD")
for pol in policyData:
if pol['name'] == "ITD-Bronze-PRD":
jobPolicy=pol['id']
elif vcenterName == "76297098-764c-43de-8525-c9fda1b237be":
vcenterName = "npd01"
policyData = cluster.GetProtectionPolicyByName("ITD-Bronze-NPD")
for pol in policyData:
if pol['name'] == "ITD-Bronze-NPD":
jobPolicy=pol['id']
elif vcenterName == "e53aa0c7-824d-40a2-b420-4ab77b1051d2":
vcenterName = "infra01"
policyData = cluster.GetProtectionPolicyByName("ITD-Bronze-INFRA")
for pol in policyData:
if pol['name'] == "ITD-Bronze-INFRA":
jobPolicy=pol['id']
elif vcenterName == "cb39dbef-0ecf-434d-a8b9-444cef8f390a":
vcenterName = "cares01"
policyData = cluster.GetProtectionPolicyByName("ITD-Bronze-CARES")
for pol in policyData:
if pol['name'] == "ITD-Bronze-CARES":
jobPolicy=pol['id']
elif vcenterName == "54048952-c2b2-4c65-ab44-157750caa8e6":
vcenterName = "NDIT-PaaS"
policyData = cluster.GetProtectionPolicyByName("ITD-Bronze-NDIT-PaaS")
for pol in policyData:
if pol['name'] == "ITD-Bronze-NDIT-PaaS":
jobPolicy=pol['id']
# Trim off .nd.gov from the name
vcenterName = vcenterName.split(".")[0]
#RFE 001: CMDB reference lookups will need to be updated to the SeriveNow CMDB once it is ready for production, this is slated for October 2022.
# SharePoint
#cmdbRecord = cmdb.get_Node(vm['name'])
# Commenting this out & setting it to null because the SeriveNow logic already exists below
# ServiceNow
try:
cmdbRecord = cmdb.getCMDBItemByFQDN(vm['name'])
cmdbRecord = cmdbRecord[0]
inSnow = True
except:
inSnow = False
if not inSnow:
# 2024-04-23: Sharepoint is gone, the primary source is ServiceNow & the code has been
# adjusted accordingly
#if len(cmdbRecord) == 0:
# Handle cases where the record doesn't exist in Sharepoint yet, but might be going through
# the build process in ServiceNow
#try:
# print("Trying to find: {}".format(vm['name']))
# cmdbData = ticketSystem.getCMDBItemByFQDN(vm['name'])
# print("ServiceNow: {0}, vmware: {1}".format(cmdbData[0]['fqdn'],vm['name']))
# if cmdbData[0]['fqdn'] == vm['name']:
# print("Found in Snow CMDB")
# inSnow = True
#except:
# inSnow = False
# Part of old logic
#if not excludeTickets and (inSnow == False):
# Open a ticket for unprotected VMs that are not in the CMDB
if not excludeTickets:
undocumentedVM.append(vm['name'] + " on " + vcenterName)
#12/11/2023: Removed tickets for undocumented VMs until the CMDB is fixed.
#2024-04-23: Resume tickets for undocumented VMs
subject = "Backup job for " + vm['name'] + " on " + vcenterName + " was not found."
message = "This VM was not found in the CMDB and is not being backed up.\nInvestigate why the object is not documented in the vmware list, and not being backed up. If necessary have the backup admins exclude the item in the exemptions.yml file"
#12/11/2023: Removed tickets for undocumented VMs until the CMDB is fixed.
incident = ticketSystem.submitTicket("svccohesityadm", subject, message)
incidentID = incident['result']['sys_id']
ticketSystem.assignTicketToGroup(incidentID, "NDIT-Cloud Platforms")
print("Ticket {} created for {}".format(incident['result']['number'], vm['name']))
tickets.append("Ticket {} created for {}".format(incident['result']['number'], vm['name']))
continue
else:
continue
# There is a record for the VM in the CMDB so lets construct the appropriate protection group name
else:
#RFE 001: CMDB reference lookups will need to be updated to the SeriveNow CMDB once it is ready for production, this is slated for October 2022.
# Sharepoint
#vmAppName = cmdb.get_AppName(cmdbRecord[0]['AppNameId'])
#vmEnvironment = cmdbRecord[0]['Environment']
# ServiceNow
vmAppName = cmdbRecord['u_nd_application_svc']['name']
vmEnvironment = cmdbRecord['environment']
if args.type == "kVMware":
if vmEnvironment == "Production":
jobName = vmAppName + "@" + "PRD-" + vcenterName
policyData = cluster.GetProtectionPolicyByName("ITD-Bronze-PRD")
for pol in policyData:
if pol['name'] == "ITD-Bronze-PRD":
jobPolicy=pol['id']
#jobPolicy="4847477517838800:1610060943809:72255100"
elif vmEnvironment == "Test":
jobName = vmAppName + "@" + "NPD-" + vcenterName
policyData = cluster.GetProtectionPolicyByName("ITD-Bronze-NPD")
for pol in policyData:
if pol['name'] == "ITD-Bronze-NPD":
jobPolicy=pol['id']
#jobPolicy="4847477517838800:1610060943809:72255060"
elif args.type == "kAzure":
jobName = "{0}@{1}".format(vmAppName, vcenterName)
# Don't add the job name to the unprotectedApps array more than once
if jobName in unProtectedApps:
continue
else:
# Verify that the job does not already exist in Cohesity
verify = cluster.GetFilteredRequest("/public/protectionJobs", "?names=" + jobName )
if len(verify) > 0:
for record in verify:
if record['name'] != jobName:
jobExists = False
else:
jobExists = True
break
if (len(verify) == 0) or (jobExists == False):
unProtectedApps.append(jobName)
if args.type == "kVMware":
tagId = cluster.GetVcenterTagId(vcenterSource, vmAppName)
if vmEnvironment == "Test":
excludeTag = cluster.GetVcenterTagId(vcenterSource, "Production")
elif vmEnvironment == "Production":
excludeTag = cluster.GetVcenterTagId(vcenterSource, "Test")
if tagId is not None:
confirm = "yes"
#confirm = input("About to create a protection job for {0}. Are you sure? [yes|no]".format(jobName))
if (confirm.lower() == "yes") and (not excludeTickets):
print("Creating standard change request for {0}.".format(jobName))
changeID = ticketSystem.CreateStandardChange("NDIT-SPS-Cohesity Data Protection", "NDIT-Cloud Platforms", "svccohesityadm", "ndheck", "mlaverdure", "Systems Platform - Systems", "Backup/Restore", "Automated Change - Create protection job {0}".format(jobName), "Initial protection group creation.")
changeSysID = changeID['result']['sys_id']['value']
ticketSystem.scheduleStandardChange(changeSysID)
ticketSystem.implementStandardChange(changeSysID)
ticketSystem.reviewStandardChange(changeSysID)
resp = cluster.CreateVMProtectionJob(jobName, vcenterSource[0]['protectionSource']['id'], tagId, excludeTag,jobPolicy)
postChangeNotes = resp.text
ticketSystem.addStandardChangeNotes(changeSysID, postChangeNotes)
ticketSystem.closeStandardChange(changeSysID, "Successful", "Change Complete")
print("Job {} created, {} complete".format(jobName, changeID['result']['number']['value']))
tickets.append("Change {} created for {}".format(changeID['result']['number']['value'],jobName))
if args.type == "kAzure":
print("Create backup job for job {0}".format(jobName))
print("Looking up Tag ID for {0}".format(vmAppName))
tagId = cluster.GetAzureTagId(vcenterSource, vmAppName)
if (tagId is not None) and (not excludeTickets):
changeID = ticketSystem.CreateStandardChange("NDIT-SPS-Cohesity Data Protection", "NDIT-Cloud Platforms", "svccohesityadm", "ndheck", "mlaverdure", "Systems Platforms - Systems", "Backup/Restore", "Automated Change - Create protection job {0}".format(jobName), "Initial protection group creation.")
changeSysID = changeID['result']['sys_id']['value']
ticketSystem.scheduleStandardChange(changeSysID)
ticketSystem.implementStandardChange(changeSysID)
ticketSystem.reviewStandardChange(changeSysID)
resp = cluster.CreateAZProtectionJob(jobName, vcenterSource[0]['protectionSource']['id'], tagId)
postChangeNotes = resp.text
ticketSystem.addStandardChangeNotes(changeSysID, postChangeNotes)
ticketSystem.closeStandardChange(changeSysID, "Successful", "Change Complete")
print("Created {0} for {1}".format(changeID,jobName))
tickets.append("Ticket {0} created for {1}".format(changeID['result']['number']['value'],jobName))
else:
print("Tag not found in Azure: {}".format(vmAppName))
subject = "Tag {} was not found in the {} virtualization platform".format(vmAppName, args.type)
message = "The application name tag {0} used to create backup job {1} was not found. The ApplicationName tag for {2} must be a case sensitive match with the Sharepoint record.".format(vmAppName, jobName, vm['name'])
incident = ticketSystem.submitTicket("svccohesityadm", subject, message)
incidentID = incident['result']['sys_id']
ticketSystem.assignTicketToGroup(incidentID, "NDIT-Cloud Platforms")
tickets.append("Ticket {} created for {}".format(incident['result']['number'],vmAppName))
else:
if not excludeTickets:
subject = "Backup job for " + vm['name'] + " was not found in " + jobName + "."
message = "Assign ticket to vmWare or Storage to investigate vm tagging issues."
incident = ticketSystem.submitTicket("svccohesityadm", subject, message)
incidentID = incident['result']['sys_id']
ticketSystem.assignTicketToGroup(incidentID, "NDIT-Cloud Platforms")
print("Ticket {} created for {}".format(incident['result']['number'], vm['name']))
tickets.append("Ticket {} created for {}".format(incident['result']['number'], vm['name']))
undocumentedVM.append(vm['name'])
#itdTeamsMessage.text("Empty Jobs: {0}\nPopulated Jobs: {1}\nPopulated Paused Jobs: {2}\nUnprotected VMs: {3}".format(str(len(emptyJobs)),str(len(populatedJobs)),str(len(populatedPausedJobs)),str(len(unprotectedVM))))
#itdTeamsMessage.send()
body = ""
body = body + "Job Summary:"
data = str(len(populatedJobs)) if len(populatedJobs) > 0 else str(0)
body = body + "\n\tPopulated Jobs: " + data
data = str(len(populatedPausedJobs)) if len(populatedPausedJobs) > 0 else str(0)
body = body + "\n\tPaused Jobs: " + data
data = str(len(emptyJobs)) if len(emptyJobs) > 0 else str(0)
body = body + "\n\tEmpty Jobs: " + data
data = str(len(unProtectedApps)) if len(unProtectedApps) > 0 else str(0)
body = body + "\n\tUnprotected Apps: " + data
body = body + "\n\nVM Summary:"
data = str(len(unprotectedVM)) if len(unprotectedVM) > 0 else str(0)
body = body + "\n\tUnprotected VMs: " + data
data = str(len(undocumentedVM)) if len(undocumentedVM) > 0 else str(0)
body = body + "\n\tUndocumented VMs: " + data
body = body + "\n\nTickets Created:"
for i in tickets:
body = body + "\n\t" + str(i)
body = body + "\n\nDetails:"
body = body + "\n\tEmpty Jobs: "
jobs=[]
for i in emptyJobs:
jobs.append(i['name'])
jobs.sort()
for i in jobs:
body = body + "\n\t\t" + str(i)
jobs=[]
body = body + "\n\tPaused Jobs:"
for i in populatedPausedJobs:
jobs.append(i['name'])
jobs.sort()
for i in jobs:
body = body + "\n\t\t" + str(i)
body = body + "\n\tUnprotected Apps:"
unProtectedApps.sort()
for i in unProtectedApps:
body = body + "\n\t\t" + str(i)
# The unprotededVM array is a dict of vm objects, we only need the vm name here
vms = []
body = body + "\n\tUnprotected VMs: "
for i in unprotectedVM:
vms.append(i['name'])
vms.sort()
for i in vms:
body = body + "\n\t\t" + str(i)
# End sorting of unprotectedVM
undocumentedVM.sort()
body = body + "\n\tUndocumented Deployed VMs:"
for i in undocumentedVM:
body = body + "\n\t\t" + str(i)
body = body + "\n\tDeployed Backup Exempt VMs:"
vmExemptions.sort()
for i in vmExemptions:
body = body + "\n\t\t" + str(i)
body = body + "\n\tDeployed Backup Exempt Apps:"
jobExemptions.sort()
for i in jobExemptions:
body = body + "\n\t\t" + str(i)
SendEmail(body, args.cluster)
if debug:
print(body)
else:
dashboard.send_automation({'AutomationName': 'Infra-Cohesity', 'Action': 'Provisioning', 'Platform': 'Python-dailyProtectionReview.py', 'Units': 120})
+217
View File
@@ -0,0 +1,217 @@
#!/usr/bin/python
#-------------------------------------------------------------------------------------------------------------#
# Author: Cliff Cogdill
# Description: Gather the protection runs from the last 24 hours, limited to 5000 jobs, and seperate them into
# Running, Canceled, Failures, warning, hold, and missed.
# If the protection group is empty pause it.
# If there are failures in the protection job, open a ServiceNow ticket.
# Send out a summary of the previous 24 hour run
#
#
#-------------------------------------------------------------------------------------------------------------#
import sys,argparse,json,time,smtplib
from email.message import EmailMessage
sys.path.insert(0, './classes/')
import cohesityAPI as cohesity
import serviceNowAPI as snow
import automationsAPI as dashboard
def GetArgs():
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument('--cluster', '-c', type=str, action='store')
parser.add_argument('--vcenter', '-v', type=str, action='store')
parser.add_argument('--job', '-j', type=str, action='store')
parser.add_argument('--help', '-h', action='store_true')
parser.add_argument('--debugMode', '-d', action='store_true')
return (parser.parse_args())
def PrintHelp():
print("\nBasic Usage:")
print("\n python3 dailyErrors.py -c cluster1.domain.tld" )
print("\t -c FQDN of Cohesity cluster address")
print("\t -h Prints this help message")
def SendEmail(body, cluster):
if debugMode:
recipients = ['cecogdill@nd.gov']
else:
recipients = ['zmeier@nd.gov', 'cecogdill@nd.gov']
email = EmailMessage()
email['Subject'] = "Cohesity Job Status for {0}".format(cluster)
email['From'] = "No-Reply@nd.gov"
email['To'] = ", ".join(recipients)
email.set_content(body)
with smtplib.SMTP('apprelay.nd.gov') as smtp:
smtp.send_message(email)
print("Sent email")
# Define variables
args = GetArgs()
if args.debugMode:
debugMode = True
else:
debugMode = False
# Check for arguments and act accoringly
if args.help:
PrintHelp()
exit(1)
# Establish a connection to ServiceNow
if debugMode:
ticketSystem = snow.SnowAPI("northdakotadev.service-now.com")
else:
ticketSystem = snow.SnowAPI("northdakota.service-now.com")
# Establish a connection to Cohesity
cluster = cohesity.API(args.cluster)
authToken = cluster.GetAuthToken()
cluster.UpdateHeaders(authToken['accessToken'])
# Get yesterday's start time in unixEpoch:
prevWindow = cluster.GetRelativeTimestamp(-1, 17, 0, 0)
# Debug Output
if args.debugMode:
print("\nPulling backup jobs from {}\n".format(prevWindow))
# Pull a list of protection runs from yesterday's backup window.
jobStatus = cluster.GetFilteredRequest("/public/protectionRuns","?startTimeUsecs=" + str(prevWindow) + "&numRuns=5000")
#Define the arrays we will use to sort the protectionRuns
running = []
cancel = []
failures = []
failControl = []
warnings = []
holds = []
missed = []
tickets = []
# Examine and sort the jobs by type status
for job in jobStatus:
# Debug Output
if args.debugMode:
print("Status of {0}: {1}\n".format(job['jobName'],job['backupRun']['status']))
# Look for failures
if job['backupRun']['status'] == "kRunning":
running.append(job)
elif job['backupRun']['status'] == "kCanceling" or job['backupRun']['status'] == "kCanceled":
cancel.append(job)
elif job['backupRun']['status'] == "kFailure":
if job['jobName'] + job['backupRun']['error'] in failControl:
continue
else:
failControl.append(job['jobName'] + job['backupRun']['error'])
failures.append(job)
elif job['backupRun']['status'] == "kWarning":
warnings.append(job)
elif job['backupRun']['status'] == "kOnHold":
holds.append(job)
elif job['backupRun']['status'] == "kMissed":
missed.append(job)
else:
continue
# Start formatting the email output into the 'body' variable.
body = "Running: " + str(len(running))
for entry in running:
message = "\n\t " + entry['jobName']
body = body + message
body = body + "\nCanceled: " + str(len(cancel))
for entry in cancel:
message = "\n\t " + entry['jobName']
body = body + message
body = body + "\nFailures: " + str(len(failures))
for entry in failures:
retired = False
# Create the message entry
message = "\n\t " + entry['jobName']
if entry['jobName'] == 'NDPERS-Applications@physical':
continue
if 'error' in entry['backupRun']:
errorMessage = entry['backupRun']['error']
# We know there are empty protection jobs but we don't need to create a ticket on them; instead lets pause them and then
# add logic to the daily backup audit script (dailyProtectionReview.py) to check the paused jobs for VMs and take action
# if needed
if errorMessage == "Cannot find any eligible backup source for this run":
errorMessage = "Protection group was empty, future runs will be paused by the dailyProtectionReview.py script"
#No need to continue and create a ticket so lets go to the next item
continue
for source in entry['backupRun']['sourceBackupStatus']:
if 'error' in source:
#errorMessage = "Details:"
# Clean up Cohesitys error a bit
if "Exceeded the maximum number of permitted snapshots" in source['error']:
errorMessage = "\n\t\t" + errorMessage + source['source']['name'] + ":\n\t\t\tAn error occurred while saving \
the snapshot: Exceeded the maximum number of permitted snapshots. Check whether or not snapshots on \
these objects are allowed in the VM properties. If snapshots are not allowed, ensure the server is \
exempted from backups according to NDIT Backup Exemption procedures.\n"
else:
errorMessage = "\n\t\t" + source['source']['name'] + ":\n\t\t\t" + source['error']
# Retired in ServiceNow
this_vm = source['source']['name']
cmdbRecord = ticketSystem.getCMDBItemByFQDN(this_vm)
# ServiceNow returns the index as string object so we either have to convert it to int or the check to str
if len(cmdbRecord) > 0:
if int(cmdbRecord[0]['operational_status']) == 6:
retired = True
# Create the incident based off the error message in the job
incident = ticketSystem.submitTicket("svccohesityadm", "Backup Error for: " + entry['jobName'], errorMessage)
incidentID = incident['result']['sys_id']
tickets.append(incident['result']['number'])
if '@SQL' in entry['jobName'] and retired != True:
# Assign the incident to storage
ticketSystem.assignTicketToGroup(incidentID, 'NDIT-Database')
else:
# Assign the incident to storage
ticketSystem.assignTicketToGroup(incidentID, 'NDIT-Computer Systems Storage')
message = message + errorMessage
body = body + message
body = body + "\nWarning: " + str(len(warnings))
for entry in warnings:
message = "\n\t " + entry['jobName']
body = body + message
body = body + "\nHold: " + str(len(holds))
for entry in holds:
message = "\n\t " + entry['jobName']
body = body + message
body = body + "\nMissed: " + str(len(missed))
for entry in missed:
message = "\n\t " + entry['jobName']
body = body + message
body = body + "\nTickets: " + str(len(tickets)) + "\n\t Instance: " + ticketSystem.snInstance
for t in tickets:
message = "\n\t\t" + t
body = body + message
# Send the email to the list of recipients in the local SendEmail function
SendEmail(body, args.cluster)
dashboard.send_automation({'AutomationName': 'Infra-Cohesity', 'Action': 'Maintenance', 'Platform': 'Python-dailyErrors.py', 'Units': 60})
@@ -0,0 +1,646 @@
#!/usr/bin/python
#-------------------------------------------------------------------------------------------------#
# Author: Cliff Cogdill
# Description: This script accomplishes a few things:
# - Reviews all assets in the CMDB to determine if they have been setup in backups.
# - If not, identify them and send create a ticket for investigation.
# - Reviews paused protection jobs to determine if the job is still empty.
# - If not empty, resume the job.
# - Send summary report of unprotected servers and VM jobs to the storage team.
# ------------------------------------------------------------------------------------------------#
import sys,argparse,json,time,yaml,re,smtplib
from email.message import EmailMessage
sys.path.insert(0, './classes/')
import cohesityAPI as cohesity
import sharePointAPI as sharePoint
import automationsAPI as dashboard
#import serviceNowAPI as snow
import nditServiceNow as snow
import logging
#import pymsteams
def GetArgs():
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument('--cluster', '-c', type=str, action='store')
parser.add_argument('--debug', '-d', action='store_true')
parser.add_argument('--excludeTickets', '-x', action='store_true')
parser.add_argument('--refresh', '-r', action='store_true')
parser.add_argument('--type', '-t', type=str, action='store')
parser.add_argument('--vCenter', '-v', type=str, action='store')
parser.add_argument('--help', '-h', action='store_true')
return (parser.parse_args())
def SendEmail(body, cluster):
if debug:
recipients = ['itbackups@nd.gov']
else:
recipients = ['zmeier@nd.gov', 'cecogdill@nd.gov','khellman@nd.gov','mlaverdure@nd.gov','ndheck@nd.gov']
email = EmailMessage()
email['Subject'] = "Backup Review for {0}".format(cluster)
email['From'] = "No-Reply@nd.gov"
email['To'] = ", ".join(recipients)
email.set_content(body)
with smtplib.SMTP('apprelay.nd.gov') as smtp:
smtp.send_message(email)
print("Sent email")
def PrintHelp():
print("\nBasic Usage:")
print("\n python3 protectedItems.py -c cluster1.domain.tld [ -v vCenter.domain.tld ] [ -j protectionJobName ]")
print("\t -d debug: execute in debug mode to expand summary and do not open tickets")
print("\t -x excludeTickets: run against prod serivce now but do not open tickets or changes")
print("\t -r refresh: force source refresh in debug mode")
print("\t -c FQDN of Cohesity cluster address")
print("\t -t type of protection source")
print("\t -h Prints this help message")
#itdTeamsMessage = pymsteams.connectorcard("https://ndgov.webhook.office.com/webhookb2/aad89030-8f69-4a4d-a853-c882cbb01a10@2dea0464-da51-4a88-bae2-b3db94bc0c54/IncomingWebhook/d3c07921419c4920810d0c32558c05e3/edcc1502-1ff0-4c3e-9fe2-1ce3f9f7981a")
args = GetArgs()
if args.help:
PrintHelp()
exit()
logging.basicConfig(filename="log/" + args.cluster + '.log', filemode='w', format='%(asctime)s %(message)s', datefmt='%Y-%m-%d %I:%M:%S %p', level=logging.DEBUG)
# Define empty lists that will hold JSON dictionaries
vmObjects = []
protectedObjects = []
vCenters = []
protectedVM = []
unprotectedVM = []
undocumentedVM = []
populatedJobs = []
populatedPausedJobs = []
emptyJobs = []
# Default to the main cluster on premise if the cluster argument was not specified
if not args.cluster:
args.cluster = "itdmdndpc01.nd.gov"
args.type = "kVMware"
if not args.type:
args.type = "kVMware"
if args.cluster == "itdazdpc01.nd.gov":
args.type = "kAzure"
if args.cluster == "itdazdpc02.nd.gov":
args.type = "kAzure"
logging.info("Creating cluster object for {0} with type {1}".format(args.cluster, args.type))
# Setup the token for cluster API usage
cluster = cohesity.API(args.cluster)
authToken = cluster.GetAuthToken()
cluster.UpdateHeaders(authToken['accessToken'])
# Lookup the AppNames and vCenter for unprotected VMs
#logging.info("Createing cmdb object for SharePoint list")
#cmdb = sharePoint.API()
# When true, tickets will only be created in ServiceNow dev and JSON output may be expanded to stdout
if args.debug:
debug = True
else:
debug = False
if args.excludeTickets:
excludeTickets = True
else:
excludeTickets = False
# Create the connection to ServiceNow
logging.info("Creating ticketSystem object for ServiceNow")
if debug:
ticketSystem = snow.SnowAPI("northdakotadev.service-now.com")
else:
ticketSystem = snow.SnowAPI("northdakota.service-now.com")
if args.type == "kPhysical":
physicalJobs = cluster.GetPhysicalJobs()
sys.exit("Warning: Type not implmented for this script.")
elif args.type == "kSQL":
sys.exit("Warning: Type not implmented for this script.")
elif args.type == "kAzure":
# Get all registered subscription sources so we can use the source ids for other REST calls
logging.info("Collecting all registered Azure sources")
nodes = cluster.GetAZsources()
# Iterate through all the Azure subscriptions to create one large dataset for all VMs
logging.info("Creating dataset for all VMs in our Azure subscriptions")
logging.info("Creating dataset for all protected VMs in our Azure subscriptions")
for node in nodes:
hypervisorId = node['protectionSource']['id']
#Refresh the sources so we ensure we are getting valid information
if debug:
print("Debug enabled, not refreshing source use -r option to override")
else:
cluster.RefreshSource(node['protectionSource']['id'])
if args.refresh:
cluster.RefreshSource(node['protectionSource']['id'])
myObjects = cluster.GetAZObjects(hypervisorId)
myProtectedObjects = cluster.GetProtectedAZobjects(hypervisorId)
# Combine all VM objects into one dictionary
for val in myObjects:
vmObjects.append(val)
# Combine all protected objects into one dictionary
for val in myProtectedObjects:
protectedObjects.append(val)
# Get all the native snapshot protection jobs that exist in the cluster
logging.info("Gathering all current protection jobs from {0}".format(args.cluster))
sourceJobs = cluster.GetAZNativeJobs()
elif args.type == "kVMware":
# Get all registered vCenter sources so we can use the source ids for other REST calls
logging.info("Collecting all registered vmWare sources")
nodes = cluster.GetVMsources()
# Iterate through all the vCenter servers to create one large dataset for all VMs
logging.info("Creating dataset for all VMs in our Azure subscriptions")
logging.info("Creating dataset for all protected VMs in our Azure subscriptions")
for node in nodes:
hypervisorId = node['protectionSource']['id']
#Refresh the sources so we ensure we are getting valid information
cluster.RefreshSource(node['protectionSource']['id'])
myObjects = cluster.GetVMObjects(hypervisorId)
myProtectedObjects = cluster.GetProtectedVMobjects(hypervisorId)
# Combine all VM objects into one dictionary
for val in myObjects:
vmObjects.append(val)
# Combine all protected objects into one dictionary
for val in myProtectedObjects:
protectedObjects.append(val)
# At this point we have two sets of data
# 1. All vms objects whether they are protected or not ( vmObjects )
# 2. All protected vm objects ( protectedObjects )
# Get all the protection jobs that exist for vmware in the cluster
logging.info("Gathering all current protection jobs from {0}".format(args.cluster))
sourceJobs = cluster.GetVMjobs()
if args.type == "kVMware" or args.type == "kAzure":
############################## BEGIN: IDENTIFY EMPTY & POPULATE PAUSED JOBS #######################################
# Loop through the sourceJobs to see if they show up in at least once in the list of protected objects.
# if they don't show up, we can assume the job is created but empty, which is usually the case for
# placeholder VMs
# In certain circumstances, jobs can be defined but exempt from normal processing. These jobs are normally used from time to time for POC work and do not necessarily need to be protected, here we will load them into the exempt jobs array from the exceptions.yml file.
logging.info("Opening exemptions file: vars/exemptions.yml")
with open("./vars/exemptions.yml", "r") as exemptionJobData:
try:
exemptionJobData = yaml.load(exemptionJobData, Loader=yaml.FullLoader)
except:
print("Unable to load exemptions file.")
exemptJobs = []
logging.info("Parsing exemptions YAML")
for i in exemptionJobData:
for e in exemptionJobData[i]['jobs']:
#exemptJobs.append(e.lower())
exemptJobs.append([exemptionJobData[i]['id'],e.lower()])
# Loop through all the jobs for
logging.info("Iterating through all protection jobs")
for sourceJob in sourceJobs:
#Set default control variables for each job
isJobExempt = False
isJobEmpty = True
# The REST GetVMjobs function returns unfiltered jobs, so lets get rid of the deleted ones
if 'isDeleted' in sourceJob:
if sourceJob['isDeleted'] == True:
logging.info("--Job {0} is marked for deletion, skipping review".format(sourceJob['name']))
continue
# Look through the exemptJobs listing
for entry in exemptJobs:
parent = entry[0]
pattern = entry[1]
if parent == sourceJob['parentSourceId']:
if re.search(pattern, sourceJob['name'].lower()):
isJobExempt = True
break
if isJobExempt == True:
if debug == True:
logging.info("--Job {0} has been exempt from backups, skipping review".format(sourceJob['name']))
print("Job {0} is exempt from tickets".format(sourceJob['name']))
continue
# The protection jobs only show tags, they do not show VM ojbects; therefore, to determine if the job is still valid, we have to iterate through all protected objects to see if the job name shows up or not. If it does not show up, then it is assumed to be empty and paused to prevent backup errors. Also, there is usually only one job listed per protected VM; however, there could be cases where a VM is in two or more jobs so we need to check each job listed.
# Sample dataset can be seen by using the following url:
# https://itdmdndpc01.nd.gov/irisservices/api/v1/public/protectionSources/protectedObjects?environment=kVMware&id=3156
logging.info("--Iterating through all protected objects to verify if job {0} is empty".format(sourceJob['name']))
for item in protectedObjects:
for pJob in item['protectionJobs']:
if pJob['name'] == sourceJob['name']:
isJobEmpty = False
continue
# Seperate empty from non-empty jobs
if isJobEmpty:
logging.info("--{0} is empty, pausing future runs".format(sourceJob['name']))
emptyJobs.append(sourceJob)
# Pause the job so we don't generate erroneous errors.
cluster.PauseJob(sourceJob['id'])
else:
# The job is not empty, lets check to see if it is paused so we can create an alert.
# First we have to check if the isPaused key exists, if it has never been set, it will not.
if 'isPaused' in sourceJob:
# Now we check to see if isPaused is set to True
if sourceJob['isPaused']:
logging.info("----{0} is paused but contains VM objects, adding to populatedPausedJobs dataset".format(sourceJob['name']))
populatedPausedJobs.append(sourceJob)
# isPaused might be defined but it is set to False
else:
populatedJobs.append(sourceJob)
# If the job has never been paused, isPaused may not be defined
else:
populatedJobs.append(sourceJob)
############################## END: IDENTIFY EMPTY & POPULATE JOBS #######################################
logging.info("Iteration of all protection jobs is complete")
######################### BEGIN: OPEN TICKETS FOR POPULATED PAUSED JOBS ##################################
# This block of code is duplicate logic from above, the populatedPausedJobs array should not contain exempt jobs, we already checked for that. - CC 20230928
# if debug:
# print("Exempt jobs:\n{0}".format(exemptJobs))
#
# for job in populatedPausedJobs:
# isJobExempt = False
#
# for entry in exemptJobs:
# parent = entry[0]
# pattern = entry[1]
#
# if parent == job['parentSourceId']:
# if re.search(pattern, job['name'].lower()):
# isJobExempt = True
# break
#
# if isJobExempt:
# continue
# else:
logging.info("Opening tickets for paused jobs that contain VM objects")
for job in populatedPausedJobs:
if not excludeTickets:
subject = "Backup job " + job['name'] + " is paused"
message = "This backup job contains VMs and should not be paused per SLA. Either fix the issue and resume the job, exempt the VM(s) from backups, or decommission the VMs."
incident = ticketSystem.submitTicket("svccohesityadm", subject, message)
incidentID = incident['result']['sys_id']
ticketSystem.assignTicketToGroup(incidentID, "NDIT-Cloud Platforms")
logging.info("--{0} created for {1}".format(incident['result']['number'],job['name']))
######################### END: OPEN TICKETS FOR POPULATED PAUSED JOBS ##################################
############################## BEGIN: IDENTIFY UNPROTECTED VMS #################################
# RFE: 002
# Open the list of documented exceptions, in the long term this may be in the ServiceNow CMDB
# if it is, we would have to look it up using the SNOW API
with open("./vars/exemptions.yml", "r") as exemptionData:
try:
exemptionData = yaml.load(exemptionData, Loader=yaml.FullLoader)
except:
print("Unable to load exemptions file.")
exemptions = []
for i in exemptionData:
for e in exemptionData[i]['vms']:
#exemptions.append([exemptionData[i]['id'],e.lower()])
exemptions.append([exemptionData[i]['id'],e])
# Find protected / unprotected vms
logging.info("Iterating through all vmObjects found under sources registered to {0}".format(args.cluster))
for vm in vmObjects:
# We don't need to monitor VMs that are marked as a template VM
if vm['environment'] == "kVMware":
if 'isVmTemplate' in vm['vmWareProtectionSource']:
if vm['vmWareProtectionSource']['isVmTemplate'] == True:
continue
# Assume all VM objects are not protected until we prove otherwise
isProtected = False
# Use flag to determine if VM was found or not
for pVM in protectedObjects:
if vm['name'] == pVM['protectionSource']['name']:
isProtected = True
protectedVM.append(vm)
break
# The Azure naming convention is different from the vmware naming convention, we need to normalize the names for sharepoint loookups
# and exemption lookups
# SharePoint Removal
#if vm['environment'] == "kAzure":
# if "-" in vm['name']:
# nameParts = vm['name'].split("-")
# hostname= cmdb.find_VM(nameParts[1])
# if nameParts[1] == hostname:
# hostname = cmdb.find_VM(vm['name'])
# if hostname == "vm-lcsdev1-1":
# hostname = cmdb.get_Node("vm-lcsdev1dev.centralus.cloudapp.azure.com")
# else:
# hostname = cmdb.find_VM(vm['name'])
# vm.update({"name": hostname})
# This part needs some significant improvement on handling the exceptions. Ideally we would want to ensure the
# hostname that is excluded belongs to the same parent in the exemption file and in Cohesity.
if not isProtected:
isExempted = False
for entry in exemptions:
parent = entry[0]
pattern = entry[1]
if parent == vm['parentId']:
if re.search(pattern, vm['name'].lower()):
isExempted = True
break
else:
continue
if isExempted == False:
unprotectedVM.append(vm)
############################## END: IDENTIFY UNPROTECTED VMS #################################
###################### BEGIN: OPEN TICKETS FOR UNPROTECTED & UNEXEMPTED VMS ########################
# Setup an array to hold unique AppNames
unProtectedApps = []
for vm in unprotectedVM:
#RFE 001: CMDB reference lookups will need to be updated to the SeriveNow CMDB once it is ready for production, this is slated for October 2022.
###### sharepoint #####
#cmdbRecord = cmdb.get_Node(vm['name'])
###### ServiceNow #####
cmdbRecord = ticketSystem.getCMDBItemByFQDN(vm['name'])
vcenterSource = cluster.GetFilteredRequest("/public/protectionSources", "?id=" + str(vm['parentId']))
vcenterName = vcenterSource[0]['protectionSource']['name'].split(".")[0]
if args.type == "kAzure":
if vcenterName == "437b2bfa-850e-4464-b6c2-38a68cda7c69":
vcenterName = "prd01"
policyData = cluster.GetProtectionPolicyByName("ITD-Bronze-PRD")
for pol in policyData:
if pol['name'] == "ITD-Bronze-PRD":
jobPolicy=pol['id']
elif vcenterName == "76297098-764c-43de-8525-c9fda1b237be":
vcenterName = "npd01"
policyData = cluster.GetProtectionPolicyByName("ITD-Bronze-NPD")
for pol in policyData:
if pol['name'] == "ITD-Bronze-NPD":
jobPolicy=pol['id']
elif vcenterName == "e53aa0c7-824d-40a2-b420-4ab77b1051d2":
vcenterName = "infra01"
policyData = cluster.GetProtectionPolicyByName("ITD-Bronze-INFRA")
for pol in policyData:
if pol['name'] == "ITD-Bronze-INFRA":
jobPolicy=pol['id']
elif vcenterName == "cb39dbef-0ecf-434d-a8b9-444cef8f390a":
vcenterName = "cares01"
policyData = cluster.GetProtectionPolicyByName("ITD-Bronze-CARES")
for pol in policyData:
if pol['name'] == "ITD-Bronze-CARES":
jobPolicy=pol['id']
elif vcenterName == "54048952-c2b2-4c65-ab44-157750caa8e6":
vcenterName = "NDIT-PaaS"
policyData = cluster.GetProtectionPolicyByName("ITD-Bronze-NDIT-PaaS")
for pol in policyData:
if pol['name'] == "ITD-Bronze-NDIT-PaaS":
jobPolicy=pol['id']
# Trim off .nd.gov from the name
vcenterName = vcenterName.split(".")[0]
##########################################################################################################################
# # This was part of the SharePoint & ServiceNow cutover. Since we already pull the snow record above now, we don't
# # need to try to look for it again.
#
if len(cmdbRecord) == 0:
# # Handle cases where the record doesn't exist in Sharepoitn yet, but might be going through
# # the build process in ServiceNow
# try:
# print("Trying to find: {}".format(vm['name']))
# cmdbData = ticketSystem.getCMDBItemByFQDN(vm['name'])
#
# print("ServiceNow: {0}, vmware: {1}".format(cmdbData[0]['fqdn'],vm['name']))
#
# if cmdbData[0]['fqdn'] == vm['name']:
# print("Found in Snow CMDB")
# inSnow = True
# except:
# inSnow = False
#
########################################################################################################################
# Open a ticket for unprotected VMs that are not in the CMDB
if not excludeTickets:
undocumentedVM.append(vm['name'] + " on " + vcenterName)
#12/11/2023: Removed tickets for undocumented VMs until the CMDB is fixed.
print("Undocumented VM: {0} on {1}".format(vm['name'],vcenterName))
print("Ticket suppressed")
'''
subject = "Backup job for " + vm['name'] + " on " + vcenterName + " was not found."
message = "This VM was not found in the CMDB and is not being backed up.\nInvestigate why the object is not documented in the vmware list, and not being backed up. If necessary have the backup admins exclude the item in the exemptions.yml file"
12/11/2023: Removed tickets for undocumented VMs until the CMDB is fixed.
incident = ticketSystem.submitTicket("svccohesityadm", subject, message)
jincidentID = incident['result']['sys_id']
ticketSystem.assignTicketToGroup(incidentID, "NDIT-Cloud Platforms")
print("Ticket {} created for {}".format(incident['result']['number'], vm['name']))
'''
continue
else:
continue
########################################################################################################################
# There is a record for the VM in the CMDB so lets construct the appropriate protection group name
else:
#RFE 001: CMDB reference lookups will need to be updated to the SeriveNow CMDB once it is ready for production, this is slated for October 2022.
###### SharePoint #####
#vmAppName = cmdb.get_AppName(cmdbRecord[0]['AppNameId'])
#vmEnvironment = cmdbRecord[0]['Environment']
###### ServiceNow #####
vmAppName = cmdbRecord[0]['u_nd_application_svc']['name']
vmEnvironment = cmdbRecord[0]['environment']
if args.type == "kVMware":
if vmEnvironment == "Production":
jobName = vmAppName + "@" + "PRD-" + vcenterName
policyData = cluster.GetProtectionPolicyByName("ITD-Bronze-PRD")
for pol in policyData:
if pol['name'] == "ITD-Bronze-PRD":
jobPolicy=pol['id']
#jobPolicy="4847477517838800:1610060943809:72255100"
elif vmEnvironment == "Test":
jobName = vmAppName + "@" + "NPD-" + vcenterName
policyData = cluster.GetProtectionPolicyByName("ITD-Bronze-NPD")
for pol in policyData:
if pol['name'] == "ITD-Bronze-NPD":
jobPolicy=pol['id']
#jobPolicy="4847477517838800:1610060943809:72255060"
elif args.type == "kAzure":
jobName = "{0}@{1}".format(vmAppName, vcenterName)
# Don't add the job name to the unprotectedApps array more than once
if jobName in unProtectedApps:
continue
else:
# Verify that the job does not already exist in Cohesity
verify = cluster.GetFilteredRequest("/public/protectionJobs", "?names=" + jobName )
if len(verify) > 0:
for record in verify:
if record['name'] != jobName:
jobExists = False
else:
jobExists = True
break
if (len(verify) == 0) or (jobExists == False):
unProtectedApps.append(jobName)
if args.type == "kVMware":
tagId = cluster.GetVcenterTagId(vcenterSource, vmAppName)
if vmEnvironment == "Test":
excludeTag = cluster.GetVcenterTagId(vcenterSource, "Production")
elif vmEnvironment == "Production":
excludeTag = cluster.GetVcenterTagId(vcenterSource, "Test")
if tagId is not None:
confirm = "yes"
#confirm = input("About to create a protection job for {0}. Are you sure? [yes|no]".format(jobName))
if (confirm.lower() == "yes") and (not excludeTickets):
print("Creating standard change request for {0}.".format(jobName))
changeID = ticketSystem.CreateStandardChange("NDIT-SPS-Cohesity Data Protection", "NDIT-Cloud Platforms", "svccohesityadm", "ndheck", "mlaverdure", "Systems Platform - Systems", "Backup/Restore", "Automated Change - Create protection job {0}".format(jobName), "Initial protection group creation.")
changeSysID = changeID['result']['sys_id']['value']
ticketSystem.scheduleStandardChange(changeSysID)
ticketSystem.implementStandardChange(changeSysID)
ticketSystem.reviewStandardChange(changeSysID)
resp = cluster.CreateVMProtectionJob(jobName, vcenterSource[0]['protectionSource']['id'], tagId, excludeTag,jobPolicy)
postChangeNotes = resp.text
ticketSystem.addStandardChangeNotes(changeSysID, postChangeNotes)
ticketSystem.closeStandardChange(changeSysID, "Successful", "Change Complete")
print("Job {} created, {} complete".format(jobName, changeID['result']['number']['value']))
if args.type == "kAzure":
print("Create backup job for job {0}".format(jobName))
print("Looking up Tag ID for {0}".format(vmAppName))
tagId = cluster.GetAzureTagId(vcenterSource, vmAppName)
if (tagId is not None) and (not excludeTickets):
changeID = ticketSystem.CreateStandardChange("NDIT-SPS-Cohesity Data Protection", "NDIT-Cloud Platforms", "svccohesityadm", "ndheck", "mlaverdure", "Systems Platforms - Systems", "Backup/Restore", "Automated Change - Create protection job {0}".format(jobName), "Initial protection group creation.")
changeSysID = changeID['result']['sys_id']['value']
ticketSystem.scheduleStandardChange(changeSysID)
ticketSystem.implementStandardChange(changeSysID)
ticketSystem.reviewStandardChange(changeSysID)
resp = cluster.CreateAZProtectionJob(jobName, vcenterSource[0]['protectionSource']['id'], tagId)
postChangeNotes = resp.text
ticketSystem.addStandardChangeNotes(changeSysID, postChangeNotes)
ticketSystem.closeStandardChange(changeSysID, "Successful", "Change Complete")
print("Created {0} for {1}".format(changeID,jobName))
else:
print("Tag not found in Azure: {}".format(vmAppName))
subject = "Tag {} was not found in the {} virtualization platform".format(vmAppName, args.type)
message = "The application name tag {0} used to create backup job {1} was not found. The ApplicationName tag for {2} must be a case sensitive match with the Sharepoint record.".format(vmAppName, jobName, vm['name'])
incident = ticketSystem.submitTicket("svccohesityadm", subject, message)
incidentID = incident['result']['sys_id']
ticketSystem.assignTicketToGroup(incidentID, "NDIT-Cloud Platforms")
else:
if not excludeTickets:
subject = "Backup job for " + vm['name'] + " was not found in " + jobName + "."
message = "Assign ticket to vmWare or Storage to investigate vm tagging issues."
incident = ticketSystem.submitTicket("svccohesityadm", subject, message)
incidentID = incident['result']['sys_id']
ticketSystem.assignTicketToGroup(incidentID, "NDIT-Cloud Platforms")
print("Ticket {} created for {}".format(incident['result']['number'], vm['name']))
#itdTeamsMessage.text("Empty Jobs: {0}\nPopulated Jobs: {1}\nPopulated Paused Jobs: {2}\nUnprotected VMs: {3}".format(str(len(emptyJobs)),str(len(populatedJobs)),str(len(populatedPausedJobs)),str(len(unprotectedVM))))
#itdTeamsMessage.send()
body = "Backup Jobs: " + str(len(populatedJobs))
body = body + "\nPaused Jobs: " + str(len(populatedPausedJobs))
body = body + "\nUnprotected VMs: " + str(len(undocumentedVM))
for vm in undocumentedVM:
body = body + "\n\t" + vm
SendEmail(body, args.cluster)
dashboard.send_automation({'AutomationName': 'Infra-Cohesity', 'Action': 'Provisioning', 'Platform': 'Python-dailyProtectionReview.py', 'Units': 120})
if debug:
array1=[]
print("Empty Jobs: " + str(len(emptyJobs)))
for value in emptyJobs:
print("\t" + value['name'])
print("Populated Jobs: " + str(len(populatedJobs)))
print("Paused Jobs: " + str(len(populatedPausedJobs)))
for value in populatedPausedJobs:
print("\t" + value['name'])
print("Unprotected VMs: " + str(len(unprotectedVM)))
for value in unprotectedVM:
array1.append(value['name'] + ", " + str(value['parentId']))
print("Sorted Unprotected VMs")
array1.sort()
for i in array1:
print(str(i))
print("Unprotected Apps")
unProtectedApps.sort()
for i in unProtectedApps:
print(str(i))
+124
View File
@@ -0,0 +1,124 @@
#!/usr/bin/python
#-------------------------------------------------------------------------------------------------------------#
# Author: Cliff Cogdill
# Description: Review the logs for disk alerts and open a servicenow ticket if one exists.
#
#
#-------------------------------------------------------------------------------------------------------------#
import sys,argparse,json,time,smtplib
from email.message import EmailMessage
sys.path.insert(0, './classes/')
import cohesityAPI as cohesity
import serviceNowAPI as snow
import automationsAPI as dashboard
def GetArgs():
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument('--cluster', '-c', type=str, action='store')
parser.add_argument('--debugMode', '-d', action='store_true')
parser.add_argument('--days', '-t', type=int, action='store')
parser.add_argument('--help', '-h', action='store_true')
return (parser.parse_args())
def PrintHelp():
print("\nBasic Usage:")
print("\n python3 diskAlerts.py -c cluster1.domain.tld" )
print("\t -c FQDN of Cohesity cluster address")
print("\t -t time duration to look back in days")
print("\t -h Prints this help message")
def SendEmail(body, cluster_name):
if debugMode:
recipients = ['cecogdill@nd.gov']
else:
recipients = ['zmeier@nd.gov', 'cecogdill@nd.gov']
email = EmailMessage()
email['Subject'] = "Cohesity drive failure on {0}".format(cluster_name)
email['From'] = "No-Reply@nd.gov"
email['To'] = ", ".join(recipients)
email.set_content(body)
with smtplib.SMTP('apprelay.nd.gov') as smtp:
smtp.send_message(email)
print("Sent email")
# Define variables
args = GetArgs()
if args.debugMode:
debugMode = True
else:
debugMode = False
# Check for arguments and act accoringly
if args.help:
PrintHelp()
exit(1)
# Establish a connection to ServiceNow
if debugMode:
ticketSystem = snow.SnowAPI("northdakotadev.service-now.com")
else:
ticketSystem = snow.SnowAPI("northdakota.service-now.com")
# Establish a connection to Cohesity
mycluster = cohesity.API(args.cluster)
authToken = mycluster.GetAuthToken()
mycluster.UpdateHeaders(authToken['accessToken'])
# Set lookback period
if args.days:
d = args.days
if d > 0:
d = d * -1
else:
d = -1
# Get yesterday's start time in unixEpoch:
prevWindow = mycluster.GetRelativeTimestamp(d, 0, 0, 0)
print(prevWindow)
# Pull a list of protection runs from yesterday's backup window.
diskAlerts = mycluster.GetDiskAlerts(prevWindow)
print(diskAlerts)
# Track disk id so we don't open multiple tickets for the same drive
disks = []
for alert in diskAlerts:
alert_name = alert['alertDocument']['alertName']
# Valid alertNames: NewDiskFound, DriveRemoved, DiskOkToRemove, DiskNotHealthy, MarkDiskForRemoval
if alert_name == 'DiskNotHealthy' or alert_name == 'MarkDiskForRemoval':
for index in alert['propertyList']:
if index['key'] == 'disk_id':
disk_num = index['value']
# Ticket has already been opened for this disk
if disk_num in disks:
continue
else:
# Create the incident
incident = ticketSystem.submitTicket("svccohesityadm", "Cohesity Drive Failure: " + args.cluster , alert['alertDocument']['alertCause'])
incidentID = incident['result']['sys_id']
# Assign the incident to storage
ticketSystem.assignTicketToGroup(incidentID, 'NDIT-Cloud Platforms')
message = "Cohesity Drive Failure"
body = "Incident: " + incident['result']['number']
body = body + "\nFollow Procedure:"
body = body + "\n\thttps://northdakota.service-now.com/kb_view.do?sysparm_article=KB0014369"
# Send the email to the list of recipients in the local SendEmail function
SendEmail(body, args.cluster)
for index in alert['propertyList']:
if index['key'] == 'disk_id':
disks.append(index['value'])
dashboard.send_automation({'AutomationName': 'Infra-Cohesity', 'Action': 'Maintenance', 'Platform': 'Python-dailyErrors.py', 'Units': 60})
+55
View File
@@ -0,0 +1,55 @@
#!/usr/bin/python
import sys,argparse,json,time
import time
sys.path.insert(0, './classes/')
import cohesityAPI as cohesity
import automationsAPI as dashboard
# Global variables that will be used across all functions
# Begin Functions
def GetArgs():
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument('--cluster', '-c', type=str, action='store')
parser.add_argument('--help', '-h', action='store_true')
return (parser.parse_args())
def PrintHelp():
print("\nBasic Usage:")
print("\npython3 sqlServerRegistration.py -c cluster.fqdn.tld")
print("\t -c FQDN of Cohesity cluster address")
print("\t -h Prints this help message")
args = GetArgs()
# Validate arguments & assign variables if necessary
if args.help:
PrintHelp()
exit(1)
if not args.cluster:
PrintHelp()
exit(1)
# Connect to the Cohesity cluster
cluster = cohesity.API(args.cluster)
authToken = cluster.GetAuthToken()
cluster.UpdateHeaders(authToken['accessToken'])
# Provide a listing of all backedup vmWare objects. VMs should not be listed twice, requires manual review
itdvmvc2 = cluster.GetFilteredRequest("/public/protectionSources/protectedObjects", "?environment=kVMware&id=1")
for record in itdvmvc2:
if "protectionSource" in record:
if "protectionJobs" in record:
for jobs in record['protectionJobs']:
print(record['protectionSource']['name'] + ", " + jobs['name'])
itdvmvc1 = cluster.GetFilteredRequest("/public/protectionSources/protectedObjects", "?environment=kVMware&id=3156")
for record in itdvmvc1:
if "protectionSource" in record:
if "protectionJobs" in record:
for jobs in record['protectionJobs']:
print(record['protectionSource']['name'] + ", " + jobs['name'])
dashboard.send_automation({'AutomationName': 'Infra-Cohesity', 'Action': 'Maintenance', 'Platform': 'Python-findDuplicates.py', 'Units': 60})
@@ -0,0 +1,96 @@
#!/usr/bin/python
import sys,argparse,json,time
sys.path.insert(0, './classes/')
import cohesityAPI as cohesity
import automationsAPI as dashboard
def GetArgs():
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument('--cluster', '-c', type=str, action='store')
parser.add_argument('--vcenter', '-v', type=str, action='store')
parser.add_argument('--type', '-t', type=str, action='store')
parser.add_argument('--job', '-j', type=str, action='store')
parser.add_argument('--pause', '-p', action='store_true')
parser.add_argument('--resume', '-r', action='store_true')
parser.add_argument('--help', '-h', action='store_true')
return (parser.parse_args())
def PrintHelp():
print("\nBasic Usage:")
print("\n python3 pauseBackups.py -c cluster1.domain.tld -v vCenter.domain.tld [ -t kVMware, kPhysical, kSQL ] [ -p | -r ] [ -j protectionJobName ]")
print("\t -c FQDN of Cohesity cluster address")
print("\t -v FQDN of vCenter Server, used in conjuntion with -t kVMware ")
print("\t -t Cohesity job type, kVMware, kPhysical, kSQL")
print("\t -j Cohesity job name")
print("\t -p Pause jobs that match the specified parameters")
print("\t -r Resume jobs that match the specified parameters")
print("\t -h Prints this help message")
args = GetArgs()
if args.help:
PrintHelp()
exit(1)
if not args.cluster:
sys.exit("Error: Specify Cohesity cluster fqdn with -c parameter.")
elif args.type == "kVMware" and not args.vcenter:
sys.exit("Error: Specify vmware cluster address withe -v parameter when using type of kVMware.")
elif args.vcenter and not args.type:
args.type = "kVMware"
else:
cluster = cohesity.API(args.cluster)
authToken = cluster.GetAuthToken()
cluster.UpdateHeaders(authToken['accessToken'])
if args.type=='kVMware' and args.vcenter:
vmSources = cluster.GetFilteredRequest("/public/protectionSources", "?environments=kVMware")
for source in vmSources:
if source['protectionSource']['name'] == args.vcenter:
vCenter = source
print (vCenter)
vCenterID = source['protectionSource']['id']
break
if args.job:
vmJobs = cluster.GetFilteredRequest("/public/protectionJobs", "?names=" + args.job)
else:
vmJobs = cluster.GetFilteredRequest("/public/protectionJobs", "?environments=kVMware")
print("Attempting to pause all jobs under " + args.vcenter)
uniqueJobs = {job['id'] : job for job in vmJobs}.values()
if args.pause:
pausedJobs = []
for job in uniqueJobs:
if job['parentSourceId'] != vCenterID:
print("not vcenter")
continue
elif "isDeleted" in job:
continue
elif "isPaused" in job:
if job['isPaused']:
pausedJobs.append(job)
continue
print("Pausing: " + job['name'])
resp = cluster.PauseJob(job['id'])
print("The following jobs were previously paused before this operation.")
for pJob in pausedJobs:
print(pJob['name'])
if args.resume:
for job in uniqueJobs:
if job['parentSourceId'] != vCenterID:
continue
elif "isDeleted" in job:
continue
print("Resuming: " + job['name'])
resp = cluster.ResumeJob(job['id'])
dashboard.send_automation({'AutomationName': 'Infra-Cohesity', 'Action': 'Maintenance', 'Platform': 'Python-pauseOrResumeJob.py', 'Units': 60})
+130
View File
@@ -0,0 +1,130 @@
#!/usr/bin/python
import sys,argparse,json,time
import time
sys.path.insert(0, './classes/')
import cohesityAPI as cohesity
import sharePointAPI as sharePoint
import serviceNowAPI as snow
import automationsAPI as dashboard
# Global variables that will be used across all functions
# Begin Functions
def GetArgs():
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument('--cluster', '-c', type=str, action='store')
parser.add_argument('--verify', '-v', action='store_true')
parser.add_argument('--jobName', '-p', type=str, action='store')
parser.add_argument('--newJobName', '-n', type=str, action='store')
parser.add_argument('--unDelete', '-r', action='store_true')
parser.add_argument('--help', '-h', action='store_true')
return (parser.parse_args())
def PrintHelp():
print("\nBasic Usage:")
print("\npython3 sqlServerRegistration.py -c cluster.fqdn.tld")
print("\t -c FQDN of Cohesity cluster address")
print("\t -v force confirmation on change")
print("\t -p Protection job name, use in conjunction with -n")
print("\t -n New Protection job name, use in conjunction with -p")
print("\t -r Restore job from a deleted status, only used with single job operations")
print("\t -h Prints this help message")
args = GetArgs()
# Validate arguments & assign variables if necessary
if args.help:
PrintHelp()
exit(1)
if not args.cluster:
PrintHelp()
exit(1)
if args.jobName:
args.verify = True
if not args.newJobName:
PrintHelp()
exit(1)
# ServiceNow object
ticketSystem = snow.SnowAPI("northdakota.service-now.com")
# Sharepoint Object
cmdb = sharePoint.API()
# Connect to the Cohesity cluster
cluster = cohesity.API(args.cluster)
authToken = cluster.GetAuthToken()
cluster.UpdateHeaders(authToken['accessToken'])
if args.jobName:
vmWareJobs=cluster.GetFilteredRequest("/public/protectionJobs","?names={0}".format(args.jobName))
else:
vmWareJobs=cluster.GetFilteredRequest("/public/protectionJobs","?environments=kVMware&isActive=True")
for job in vmWareJobs:
preChangeNotes = json.dumps(job)
if 'name' in job:
jobName = job['name']
oldName = job['name']
if args.newJobName:
print("\nUpdating job name for {0}".format(jobName))
newName = args.newJobName
jobData = {"name": newName}
job.update(jobData)
if args.unDelete:
print("\nRestoring job {0} from a deleted state.".format(jobName))
jobData = {"isDeleted": False}
job.update(jobData)
if '@PRD-itd' not in jobName and '@NPD-itd' not in jobName:
print("\nUpdating job name for {0}".format(jobName))
x = jobName.split('@')
newName = x[0] + "@PRD-" + x[1]
jobData = {"name": newName}
job.update(jobData)
print("Adding the exclude Test tag")
vcenterSource = cluster.GetFilteredRequest("/public/protectionSources", "?id=" + str(job['parentSourceId']))
testTagId = cluster.GetVcenterTagId(vcenterSource, 'Test')
excludeData = {"excludeVmTagIds": [[testTagId]]}
job.update(excludeData)
print("Updating to the new Policy Id")
policy = cluster.GetProtectionPolicyByName("ITD-Bronze-PRD")
policyData = {"policyId": policy[0]['id']}
job.update(policyData)
if args.verify:
confirm = input("About to update protection job {0}. Are you sure? [yes|no] ".format(oldName))
else:
confirm = "yes"
if confirm.lower() == "yes":
changeID = ticketSystem.CreateStandardChange("NDIT-SPS-Cohesity Data Protection", "NDIT-Computer Systems Storage", "svccohesityadm", "ndheck", "mlaverdure", "Computer Systems", "Backup/Restore", "Automated Change - Cohesity job update for {}".format(oldName), "Split production and non-production servers into multiple groups.")
changeSysID = changeID['result']['sys_id']['value']
ticketSystem.scheduleStandardChange(changeSysID)
ticketSystem.implementStandardChange(changeSysID)
ticketSystem.addStandardChangeNotes(changeSysID, "Original job details\n\n{}".format(preChangeNotes))
ticketSystem.reviewStandardChange(changeSysID)
updateStats = cluster.UpdateVMProtectionJob(job)
if updateStats.status_code != 200 and updateStats.status_code != 201:
postChangeNotes = "Job update failed\n\n" + updateStats.text
ticketSystem.addStandardChangeNotes(changeSysID, postChangeNotes)
ticketSystem.closeStandardChange(changeSysID, "Unsuccessful", "Change Failed")
else:
postChangeNotes = "Updated job name, added the exclude test tag, and updated the policy\n\n" + updateStats.text
ticketSystem.addStandardChangeNotes(changeSysID, postChangeNotes)
ticketSystem.closeStandardChange(changeSysID, "Successful", "Change Complete")
print("Chagne {} complete".format(changeID['result']['number']['value']))
+118
View File
@@ -0,0 +1,118 @@
#!/usr/bin/python
import sys
import argparse
import json
import time
sys.path.insert(0, './classes/')
import cohesityAPI as cohesity
import serviceNowAPI as serviceNow
import sharePointAPI as sharePoint
import automationsAPI as dashboard
def GetArgs():
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument('--server', '-s', type=str, action='store')
parser.add_argument('--protectGroup', '-p', type=str, action='store')
parser.add_argument('--help', '-h', action='store_true')
parser.add_argument('--debug', '-d', action='store_true')
return (parser.parse_args())
def PrintHelp():
print("\nBasic Usage:")
print("\npython3 serverRegistration.py -s hostname.nd.gov -p MyApp@physical")
print("\t -s: Fully qualified domain name of the Server")
print("\t -p: Protection Group the server will be in")
print("\t -h Prints this help message")
args = GetArgs()
if not (args.server and args.protectGroup):
PrintHelp()
exit(1)
# Proceed with registration
if (args.server and args.protectGroup):
try:
mdn = cohesity.API('itdmdndpc01.nd.gov')
mdnToken = mdn.GetAuthToken()
mdn.UpdateHeaders(mdnToken['accessToken'])
newHost = args.server
protectionGroup = args.protectGroup
isPhysicalRegistered = bool()
# Get the physical server objects in Cohesity
physicalServers = mdn.GetFilteredRequest("/public/protectionSources/registrationInfo", "?environments=kPhysical")
for nodes in physicalServers['rootNodes']:
if nodes['rootNode']['name'] == newHost:
print(newHost + " is already registerd as a physical server.")
isPhysicalRegistered = bool("true")
physicalSourceId = nodes['rootNode']['id']
break
if not isPhysicalRegistered:
attempt = mdn.RegisterPhysical(newHost)
print("Registering " + newHost + " as a Physical Server ")
time.sleep(20)
try:
physicalSourceId = attempt['id']
except:
print("\nCould not reach " + newHost + " on port 50051. Please verify the agent is running and that all firewall rules are accounted for.")
exit(1)
# Get the list of protection groups & add the new one if necessary
protectionJobs = mdn.GetFilteredRequest("/public/protectionJobs", "?environments[0]=kPhysical")
uniqueJobs = {job['id'] : job for job in protectionJobs}.values()
jobExists = bool()
isProtected = bool()
jobSources = []
for job in uniqueJobs:
# Source ID cannot be in more then on protection group for SQL
if ('sourceIds' in job) and (physicalSourceId in job['sourceIds'] and ("DELETED" not in job['name']) ):
print(newHost + " is already in " + job['name'] + " and cannot be in multiple groups.")
isProtected = bool("true")
jobExists = bool("true")
break
# The source ID was not found in any other SQL job, but a job name already exsits, so lets collect all sources in the job.
elif (job['name'] == protectionGroup) and ( physicalSourceId not in job['sourceIds']):
jobExists = bool("true")
break
if jobExists == bool('true') and isProtected != bool('true'):
print("Adding host " + newHost + " to protection group " + protectionGroup)
# By default we will protect all local drives
sourceParams = {
"sourceId": physicalSourceId,
"physicalSpecialParameters": {
"filePaths": [
{
"backupFilePath":"$ALL_LOCAL_DRIVES",
"skipNestedVolumes":True
}
],
"usesSkipNestedVolumesVec":True
}
}
job["sourceIds"].append(physicalSourceId)
job["sourceSpecialParameters"].append(sourceParams)
resp = mdn.UpdateProtectionJob(job)
print(resp.content)
if jobExists == bool():
print("Creating protection job for " + protectionGroup + " and adding " + newHost)
################ TO DO ###############
# Defaults from JSON dump of jobs objects
resp = mdn.CreatePhysicalProtectionJob(physicalSourceId, protectionGroup)
print(resp.content)
except OSError as cohesityError:
print('Cohesity Error: ' + cohesityError)
dashboard.send_automation({'AutomationName': 'Infra-Cohesity', 'Action': 'Provisioning', 'Platform': 'Python-registerServer.py', 'Units': 30})
+86
View File
@@ -0,0 +1,86 @@
ansible==5.9.0
ansible-core==2.12.10
argcomplete==2.0.0
bcrypt==3.2.2
Beaker==1.10.0
beautifulsoup4==4.11.0
blivet==3.4.4
blivet-gui==2.3.0
Brlapi==0.8.4
certifi==2023.11.17
cffi==1.15.0
chardet==4.0.0
charset-normalizer==2.0.11
click==8.0.4
cryptography==36.0.0
cupshelpers==1.0
dasbus==1.6
dbus-python==1.2.18
distro==1.6.0
fedora-third-party==0.10
fros==1.1
gpg==1.17.0
humanize==3.13.1
idna==3.3
Jinja2==3.0.3
langtable==0.0.61
libcomps==0.1.18
lxml==4.7.1
Mako==1.1.4
MarkupSafe==2.1.1
nftables==0.1
ntlm-auth==1.5.0
olefile==0.46
packaging==21.3
paramiko==2.12.0
Paste==3.5.0
pathspec==0.10.1
pexpect==4.8.0
pid==2.2.3
Pillow==9.1.0
ply==3.11
productmd==1.33
ptyprocess==0.6.0
pwquality==1.4.4
pyasn1==0.4.8
pycairo==1.21.0
pycparser==2.20
pycrypto==2.6.1
pycups==2.0.1
pycurl==7.45.1
pyenchant==3.2.2
PyGObject==3.42.1
pykickstart==3.36
PyNaCl==1.4.0
pyOpenSSL==21.0.0
pyparsing==2.4.7
pyparted==3.12.0
PySocks==1.7.1
python-augeas==1.1.0
python-dateutil==2.8.1
python-meh==0.50
pytz==2022.7
pyudev==0.22.0
pywinrm==0.4.1
pyxdg==0.27
PyYAML==6.0
regex==2022.10.31
requests==2.27.1
requests-file==1.5.1
requests-ftp==0.3.1
requests-ntlm==1.1.0
resolvelib==0.5.5
rpm==4.17.1
selinux==3.3
sepolicy==3.3
setools==4.4.0
simpleaudio==1.0.4
simpleline==1.9.0
six==1.16.0
sos==4.3
soupsieve==2.3.1
systemd-python==234
Tempita==0.5.2
urllib3==1.26.12
xmltodict==0.12.0
yamllint==1.28.0
@@ -0,0 +1,196 @@
#!/usr/bin/python
"""
Allows for the registration of a SQL Server VM to Cohesity
"""
import sys
import argparse
import json
import time
sys.path.insert(0, './classes/')
import cohesityAPI as cohesity
import itdSnow as snow
import automationsAPI as dashboard
def GetArgs():
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument('--server', '-s', type=str, action='store')
parser.add_argument('--cluster', '-c', type=str, action='store')
parser.add_argument('--protectGroup', '-p', type=str, action='store')
parser.add_argument('--help', '-h', action='store_true')
parser.add_argument('--debug', '-d', action='store_true')
parser.add_argument('--lookupGroup', '-l', action='store_true')
parser.add_argument('--audit', '-a', action='store_true')
return (parser.parse_args())
def PrintHelp():
print("\nBasic Usage:")
print("\npython3 sqlServerRegistration.py -s hostname.nd.gov -p MyApp@SQL")
print("\t -a: Audit SQL objects and re-apply permissions")
print("\t -c: The FQDN of the cluster to register the client to")
print("\t -d: Run the registration in debug mode")
print("\t -l: Lookup appName in Sharepoint and propose Protection Group name")
print("\t -s: Fully qualified domain name of the Virtualized SQL Server")
print("\t -p: Protection Group the server will be in")
print("\t -h Prints this help message")
args = GetArgs()
protectionGroup = ''
sqlHost=''
# Establish a connection to ServiceNow
if args.debug:
ticketSystem = snow.SnowAPI("northdakotadev.service-now.com")
else:
ticketSystem = snow.SnowAPI("northdakota.service-now.com")
if args.help:
PrintHelp()
exit(1)
if not ((args.server and (args.lookupGroup or args.protectGroup)) or (args.audit)):
PrintHelp()
exit(1)
# Get the AppName from the VM List in SharePoint
if args.server and args.lookupGroup:
sqlHost = args.server
cmdb_record = ticketSystem.getCMDBItemByFQDN(sqlHost)
print(json.dumps(cmdb_record,indent=4))
appName = cmdb_record[0]['u_nd_application_svc']['name']
# shpt = sharePoint.API()
# vmRecord = shpt.get_Node(sqlHost)
# appName = shpt.get_AppName(vmRecord[0]['AppNameId'])
appName = appName + "@SQL"
print("\nSuggested protection group name is: " + appName)
menuControl = 0
while menuControl == 0:
contRegistration = input("Would you like to continue? [y/n]")
if contRegistration == 'y' or contRegistration == 'Y':
menuControl = 1
print("\nProceeding with registration.")
protectionGroup = appName
elif contRegistration == 'n' or contRegistration == 'N':
menuControl = 1
print("\nAuto lookup registration has been cancelled.")
print("\nPlease relaunch application with -s & -p parameters to specify a custom protection group.")
exit(1)
else:
contRegistration = input("Would you like to continue? [y/n]")
# Proceed with registration using either the SharePoint lookup value or the user specified value
if (((args.server or sqlHost) and (args.protectGroup or protectionGroup)) or args.audit):
try:
mdn = cohesity.API(args.cluster)
mdnToken = mdn.GetAuthToken()
mdn.UpdateHeaders(mdnToken['accessToken'])
if (not sqlHost) and (not protectionGroup):
sqlHost = args.server
protectionGroup = args.protectGroup
isSQLRegistered = bool()
isPhysicalRegistered = bool()
# Get the VMs and SQL objects in Cohesity
#vms = mdn.GetRequest("/public/protectionSources/virtualMachines")
registeredSQL = mdn.GetFilteredRequest("/public/protectionSources/applicationServers", "?application=kSQL")
physicalServers = mdn.GetFilteredRequest("/public/protectionSources/registrationInfo", "?environments=kPhysical")
for nodes in physicalServers['rootNodes']:
if nodes['rootNode']['name'] == sqlHost:
print(sqlHost + " is already registerd as a physical server.")
isPhysicalRegistered = bool("true")
physicalSourceId = nodes['rootNode']['id']
break
# Check to see if the source is already registered as a SQL Server
for sqlServer in registeredSQL:
if sqlServer['applicationServer']['protectionSource']['name'] == sqlHost:
isSQLRegistered = bool("true")
print(sqlHost + " is already registerd as a SQL server.")
break
if (sqlHost):
if not isPhysicalRegistered:
attempt = mdn.RegisterPhysical(sqlHost)
print("Registering " + sqlHost + " as a Physical Server ")
time.sleep(20)
try:
physicalSourceId = attempt['id']
except:
print("\nCould not reach " + sqlHost + " on port 50051. Please verify the agent is running and that all firewall rules are accounted for.")
exit(1)
if not isSQLRegistered:
print("Registering " + sqlHost + " as a SQL Server ")
attempt = mdn.RegisterSQL(physicalSourceId)
time.sleep(20)
# Verify DBAs have permissions to all SQL objects, including newly registered objects
registeredSQL = mdn.GetFilteredRequest("/public/protectionSources/applicationServers", "?application=kSQL")
# Filter out unique objects
uniqueSQLSources = {sql['applicationServer']['protectionSource']['id'] : sql for sql in registeredSQL}.values()
# Array to hold protection source IDs
sqlProtectionSourceIds = []
for sqlServer in uniqueSQLSources:
sqlProtectionSourceIds.append(sqlServer['applicationServer']['protectionSource']['id'])
# Send protection source IDs for DBA group
dbaGroup = mdn.GetFilteredRequest("/public/groups", "?name=ITD-COHESITY-DBA")
dbaSid = dbaGroup[0]['sid']
print("Sending Request to Cohesity to update DBA permissions")
attempt = mdn.UpdatePermissions(sqlProtectionSourceIds, dbaSid)
# Get the list of protection groups & add the new one if necessary
if (sqlHost):
sqlProtectionJobs = mdn.GetFilteredRequest("/public/protectionJobs", "?environments=kSQL")
uniqueJobs = {job['id'] : job for job in sqlProtectionJobs}.values()
jobExists = bool()
isProtected = bool()
isPaused=bool()
jobSources = []
for job in uniqueJobs:
# Source ID cannot be in more then on protection group for SQL
if ('sourceIds' in job) and (physicalSourceId in job['sourceIds'] and ("DELETED" not in job['name']) ):
print(sqlHost + " is already in " + job['name'] + " and cannot be in multiple groups.")
isProtected = bool("true")
jobExists = bool("true")
break
# The source ID was not found in any other SQL job, but a job name already exsits, so lets collect all sources in the job.
elif (job['name'] == protectionGroup) and ( physicalSourceId not in job['sourceIds']):
jobExists = bool("true")
break
if jobExists == bool('true') and isProtected != bool('true'):
isPaused=bool('true')
job["sourceIds"].append(physicalSourceId)
resp = mdn.UpdateProtectionJob(job)
print(resp.content)
if jobExists == False:
print("Creating protection job for " + protectionGroup + " and adding " + sqlHost)
################ TO DO ###############
# Defaults from JSON dump of jobs objects
if args.cluster == 'itdazdpc02.nd.gov':
resp = mdn.CreateAZSQLProtectionJob(physicalSourceId, protectionGroup)
else:
resp = mdn.CreateSQLProtectionJob(physicalSourceId, protectionGroup)
print(resp.content)
except OSError as cohesityError:
print('Cohesity Error: ' + cohesityError)
dashboard.send_automation({'AutomationName': 'Infra-Cohesity', 'Action': 'Provisioning', 'Platform': 'Python-sqlServerRegistration.py', 'Units': 30})
+55
View File
@@ -0,0 +1,55 @@
#!/usr/bin/python
import sys,argparse,json,time
import time
sys.path.insert(0, './classes/')
import cohesityAPI as cohesity
import sharePointAPI as sharePoint
import serviceNowAPI as snow
import automationsAPI as dashboard
# Global variables that will be used across all functions
# Begin Functions
def GetArgs():
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument('--cluster', '-c', type=str, action='store')
parser.add_argument('--help', '-h', action='store_true')
return (parser.parse_args())
def PrintHelp():
print("\nBasic Usage:")
print("\npython3 sqlServerRegistration.py -c cluster.fqdn.tld")
print("\t -c FQDN of Cohesity cluster address")
print("\t -h Prints this help message")
args = GetArgs()
# Validate arguments & assign variables if necessary
if args.help:
PrintHelp()
exit(1)
if not args.cluster:
PrintHelp()
exit(1)
# ServiceNow object
ticketSystem = snow.SnowAPI("northdakotadev.service-now.com")
# Sharepoint Object
cmdb = sharePoint.API()
# Connect to the Cohesity cluster
cluster = cohesity.API(args.cluster)
authToken = cluster.GetAuthToken()
cluster.UpdateHeaders(authToken['accessToken'])
# Pull information from Cohestiy API
itdvmvc1 = cluster.GetFilteredRequest("/public/protectionSources/protectedObjects", "?environment=kVMware&id=3156")
for record in itdvmvc1:
if "protectionSource" in record:
if "protectionJobs" in record:
for jobs in record['protectionJobs']:
print(record['protectionSource']['name'] + ", " + jobs['name'])
@@ -0,0 +1,93 @@
#!/usr/bin/python
import sys
import argparse
import json
import time
sys.path.insert(0, './classes/')
import cohesityAPI as cohesity
import serviceNowAPI as serviceNow
import sharePointAPI as sharePoint
import automationsAPI as dashboard
def GetArgs():
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument('--server', '-s', type=str, action='store')
parser.add_argument('--protectGroup', '-p', type=str, action='store')
parser.add_argument('--help', '-h', action='store_true')
parser.add_argument('--debug', '-d', action='store_true')
return (parser.parse_args())
def PrintHelp():
print("\nBasic Usage:")
print("\npython3 serverRegistration.py -s hostname.nd.gov -p MyApp@physical")
print("\t -s: Fully qualified domain name of the Server")
print("\t -p: Protection Group the server is in")
print("\t -h Prints this help message")
args = GetArgs()
if args.help:
PrintHelp()
exit()
if args.debug:
debugMode = True
else:
debugMode = False
cluster = cohesity.API('itdmdndpc01.nd.gov')
clusterToken = cluster.GetAuthToken()
cluster.UpdateHeaders(clusterToken['accessToken'])
sourceId = cluster.GetProtectionSourceId(args.server)
pJob = cluster.GetProtectionJobByHost(args.server)
if debugMode:
print("SourceId:\n==========")
print(sourceId)
print("Job Info:\n==========")
print(pJob)
# If the source is not tied to a protection job we will remove it anyway.
if pJob == -1:
print("No job was found, continuing to unregister server.")
removeSourceStat = cluster.RemoveProtectionSource(sourceId)
else:
if sourceId in pJob['sourceIds']:
ids = pJob['sourceIds']
ids.remove(sourceId)
pJob.update({'sourceIds': ids})
# Check to see if there are any specific source parameters, if so ensure we clean up those entries as well.
if 'sourceSpecialParameters' in pJob:
specialParams = pJob['sourceSpecialParameters']
for entry in specialParams:
if entry['sourceId'] == sourceId:
specialParams.remove(entry)
pJob.update({'sourceSpecialParameters': specialParams})
# Send the updated job information back to the cluster for updating
updateJobStat = cluster.UpdateProtectionJob(pJob)
# Ensure the job was updated successfully
if updateJobStat.status_code != 200:
if debugMode:
print(updateJobStat)
print(updateJobStat.content)
sys.exit("Updating the protection job " + pJob['name'] + " failed.\n\nThe source " + args.server + " may be the only source for this job or have special parameters set in the protection job, manual intervention is required to clean up the protection group according to NDIT SLAs.")
# Attempt to remove the source from the registered sources
removeSourceStat = cluster.RemoveProtectionSource(sourceId)
# Ensure the source was removed successfully
if removeSourceStat.status_code == 403:
if debugMode:
print(removeSourceStat)
print(removeSourceStat.content)
sys.exit("Removing " + args.server + " from " + cluster.GetClusterName() + " has failed.")
print(args.server + " has been unregistered from " + cluster.GetClusterName())
dashboard.send_automation({'AutomationName': 'Infra-Cohesity', 'Action': 'DeProvisioning', 'Platform': 'Python-unRegisterServer.py', 'Units': 30})
+107
View File
@@ -0,0 +1,107 @@
#!/usr/bin/python
import sys,argparse,json,time
import time
sys.path.insert(0, './classes/')
import cohesityAPI as cohesity
import sharePointAPI as sharePoint
import serviceNowAPI as snow
import automationsAPI as dashboard
# Global variables that will be used across all functions
# Begin Functions
def GetArgs():
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument('--cluster', '-c', type=str, action='store')
parser.add_argument('--verify', '-v', action='store_true')
parser.add_argument('--help', '-h', action='store_true')
return (parser.parse_args())
def PrintHelp():
print("\nBasic Usage:")
print("\npython3 sqlServerRegistration.py -c cluster.fqdn.tld")
print("\t -c FQDN of Cohesity cluster address")
print("\t -v force confirmation on change")
print("\t -h Prints this help message")
args = GetArgs()
# Validate arguments & assign variables if necessary
if args.help:
PrintHelp()
exit(1)
if not args.cluster:
PrintHelp()
exit(1)
# ServiceNow object
ticketSystem = snow.SnowAPI("northdakota.service-now.com")
# Sharepoint Object
cmdb = sharePoint.API()
# Connect to the Cohesity cluster
cluster = cohesity.API(args.cluster)
authToken = cluster.GetAuthToken()
cluster.UpdateHeaders(authToken['accessToken'])
vmWareJobs=cluster.GetFilteredRequest("/public/protectionJobs","?environments=kAzureNative&isActive=True")
for job in vmWareJobs:
preChangeNotes = json.dumps(job)
if 'name' in job:
jobName = job['name']
if '@cares01' in jobName:
continue
print("\nUpdating policy for {0}".format(jobName))
policyData = {"policyId": "7780085755317378:1694019083558:285"
if '@infra01' in jobName:
continue
print("\nUpdating policy for {0}".format(jobName))
policyData = {"policyId": "7780085755317378:1694019083558:286"}
job.update(policyData)
elif '@npd01' in jobName:
continue
print("\nUpdating policy for {0}".format(jobName))
policyData = {"policyId": "7780085755317378:1694019083558:287"}
job.update(policyData)
elif '@prd01' in jobName:
print("\nUpdating policy for {0}".format(jobName))
policyData = {"policyId": "7780085755317378:1694019083558:288"}
job.update(policyData)
else:
continue
if args.verify:
confirm = input("About to update protection job {0}. Are you sure? [yes|no] ".format(jobName))
else:
confirm = "yes"
if confirm.lower() == "yes":
changeID = ticketSystem.CreateStandardChange("NDIT-SPS-Cohesity Data Protection", "NDIT-Cloud Platforms", "svccohesityadm", "ndheck", "mlaverdure", "Computer Systems", "Backup/Restore", "Automated Change - Cohesity job update for {}".format(jobName), "Update protection policy to enable replication.")
changeSysID = changeID['result']['sys_id']['value']
ticketSystem.scheduleStandardChange(changeSysID)
ticketSystem.implementStandardChange(changeSysID)
ticketSystem.addStandardChangeNotes(changeSysID, "Original job details\n\n{}".format(preChangeNotes))
ticketSystem.reviewStandardChange(changeSysID)
updateStats = cluster.UpdateVMProtectionJob(job)
if updateStats.status_code != 200 and updateStats.status_code != 201:
postChangeNotes = "Job update failed\n\n" + updateStats.text
ticketSystem.addStandardChangeNotes(changeSysID, postChangeNotes)
ticketSystem.closeStandardChange(changeSysID, "Unsuccessful", "Change Failed")
else:
postChangeNotes = "Updated the policy\n\n" + updateStats.text
ticketSystem.addStandardChangeNotes(changeSysID, postChangeNotes)
ticketSystem.closeStandardChange(changeSysID, "Successful", "Change Complete")
print("Change {} complete".format(changeID['result']['number']['value']))
@@ -0,0 +1,196 @@
#!/usr/bin/python
import sys,argparse,json,time
sys.path.insert(0, './classes/')
import cohesityAPI as cohesity
import automationsAPI as dashboard
import nditServiceNow as snow
import random
# Global variables that will be used across all functions
global tagName
tagName = None
# Begin Functions
def GetArgs():
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument('--cluster', '-c', type=str, action='store')
parser.add_argument('--vcenter', '-v', type=str, action='store')
parser.add_argument('--tag', '-t', type=str, action='store')
parser.add_argument('--group', '-g', type=str, action='store')
parser.add_argument('--action', '-a', type=str, action='store')
parser.add_argument('--list', '-l', type=str, action='store')
parser.add_argument('--help', '-h', action='store_true')
return (parser.parse_args())
def PrintHelp():
print("\nBasic Usage:")
print("\npython3 updateProtectionGroupTags.py -c hostname.nd.gov")
print("\t -c FQDN of Cohesity cluster address")
print("\t -g Protection group name")
print("\t -a Action to perform 'add' or 'remove'")
print("\t -l List that needs to be updated, the 'include' or 'exclude'")
print("\t -h Prints this help message")
args = GetArgs()
if args.help:
PrintHelp()
exit(1)
# Validate arguments & assign variables if necessary
if not args.cluster:
sys.exit("Error: Specify a Cohesity cluster fqdn with -c parameter.")
vmObjects = []
protectedObjects = []
# Connect to the Cohesity cluster
cluster = cohesity.API(args.cluster)
authToken = cluster.GetAuthToken()
cluster.UpdateHeaders(authToken['accessToken'])
ticketSystem = snow.SnowAPI("northdakota.service-now.com")
nodes = cluster.GetAZsources()
subscription = input("Which subscription do you want to update? [prd01,npd01,cares01,infra01] ")
if subscription == 'npd01':
subId = 1
plcyId = "7780085755317378:1694019083558:287"
elif subscription == 'infra01':
subId = 28
plcyId = "7780085755317378:1694019083558:286"
elif subscription == 'prd01':
subId = 37
plcyId = "7780085755317378:1694019083558:288"
elif subscription == 'cares01':
subId = 4
plcyId = "7780085755317378:1694019083558:285"
sId = subId
nodes = cluster.GetAZsources()
for node in nodes:
if node['protectionSource']['id'] == subscription:
hypervisorId = node['protectionSource']['id']
cluster.RefreshSource(node['protectionSource']['id'])
myObjects = cluster.GetAZObjects(hypervisorId)
myProtectedObjects = cluster.GetProtectedAZobjects(hypervisorId)
for val in myObjects:
vmObjects.append(val)
for val in myProtectedObjects:
protectedObjects.append(val)
sourceJobs = cluster.GetAZNativeJobs()
for job in sourceJobs:
if job['parentSourceId'] != subId:
continue
print("\nPreparing to update {0}".format(job['name']))
tagName = job['name'].split('@')[0]
subName = job['name'].split('@')[1]
#confirm = input("The tag to protect for this job is {0}. Is that correct? [y/n]".format(tagName))
confirm = 'y'
while confirm != 'y' and confirm != 'n':
confirm = input("Please enter 'y' or 'n': ")
if confirm == 'n':
tagName = input("Enter the correct tag: ")
elif confirm == 'y':
subSource = cluster.GetFilteredRequest("/public/protectionSources", "?id=" + str(sId))
tagId = cluster.GetAzureTagId(subSource, tagName)
changeID = ticketSystem.CreateStandardChange("NDIT-SPS-Cohesity Data Protection", "NDIT-Cloud Platforms", "svccohesityadm", "ndheck", "mlaverdure", "Systems Platforms - Systems", "Backup/Restore", "Automated Change - Update protection job {0}".format(job['name']), "Adjust start time.")
changeSysID = changeID['result']['sys_id']['value']
ticketSystem.scheduleStandardChange(changeSysID)
ticketSystem.implementStandardChange(changeSysID)
ticketSystem.reviewStandardChange(changeSysID)
rmin = [0,15,30,45]
smin = random.choice(rmin)
rhr = [17,18,19,20,21,22,23,0,1,2,3,4,5]
shr = random.choice(rhr)
if(tagId is not None):
data = {
"name": job['name'],
"description": "",
"policyId": plcyId,
"storageDomainId": 36,
"startTime": {
"hour": shr,
"minute": smin,
"timezone": "America/Chicago"
},
"priority": "kMedium",
"sla": [
{
"backupRunType": "kIncremental",
"slaMinutes": 480
},
{
"backupRunType": "kFull",
"slaMinutes": 480
}
],
"isPaused": False,
"abortInBlackoutPeriod": False,
"environment": "kAzure",
"azureParams": {
"protectionType": "kNative",
"nativeProtectionTypeParams":{
"objects": [],
"vmTagIds": [
[
tagId
]
],
#"excludeVmTagIds": False,
"indexingPolicy":{
"enableIndexing": True,
"includePaths": [
"/"
],
"excludePaths": [
"/$Recycle.Bin",
"/Windows",
"/ProgramData",
"/System Volume Information",
"/Users/*/AppData",
"/Recovery",
"/usr",
"/sys",
"/proc",
"/lib",
"/grub",
"/grub2",
"/splunk",
]
},
},
},
"abortInBlackoutPeriod": False
}
job.update(data)
resp = cluster.UpdateAZprotectionJob(job)
postChangeNotes = resp.text
ticketSystem.addStandardChangeNotes(changeSysID, postChangeNotes)
ticketSystem.closeStandardChange(changeSysID, "Successful", "Change Complete")
dashboard.send_automation({'AutomationName': 'Infra-Cohesity', 'Action': 'Maintenance', 'Platform': 'Python-updateProtectionGroupTags.py', 'Units': 10})
@@ -0,0 +1,118 @@
#!/usr/bin/python
import sys,argparse,json,time
sys.path.insert(0, './classes/')
import cohesityAPI as cohesity
def GetArgs():
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument('--cluster', '-c', type=str, action='store')
parser.add_argument('--vcenter', '-v', type=str, action='store')
parser.add_argument('--job', '-j', type=str, action='store')
parser.add_argument('--help', '-h', action='store_true')
return (parser.parse_args())
def PrintHelp():
print("\nBasic Usage:")
print("Set OS environment variables ITD_SHAREPOINT_PASS and ITD_SHAREPOINT_USER")
print("\nExample:")
print(r"$ export COHESITY_USER=\"NDGOV\jDoe\"")
print("$ export COHESITY_PASS=\"1Lik3Jane\"")
print("\n python3 <SCRIPT_NAME> -c cluster1.domain.tld [ -v vCenter.domain.tld ] [ -j protectionJobName ]")
print("\t -c FQDN of Cohesity cluster address")
print("\t -v FQDN of vCenter Server, used in conjuntion with -t kVMware ")
print("\t -j Cohesity job name")
print("\t -h Prints this help message")
args = GetArgs()
if args.help:
PrintHelp()
if not args.cluster:
sys.exit("Error: Specify Cohesity cluster fqdn with -c parameter.")
else:
cluster = cohesity.API(args.cluster)
authToken = cluster.GetAuthToken()
cluster.UpdateHeaders(authToken['accessToken'])
if args.vcenter:
vmSources = cluster.GetFilteredRequest("/public/protectionSources", "?environments=kVMware")
for source in vmSources:
if source['protectionSource']['name'] == args.vcenter:
vCenter = source
vCenterID = source['protectionSource']['id']
break
if args.job:
# Get the details of a single job
vmJobs = cluster.GetFilteredRequest("/public/protectionJobs", "?names=" + args.job)
else:
# Get the details of all jobs under a vCenter
vmJobs = cluster.GetFilteredRequest("/public/protectionJobs", "?environments=kVMware")
uniqueJobs = {job['id'] : job for job in vmJobs}.values()
# Do stuff with the JSON data
# Example:
if args.pause:
pausedJobs = []
for job in uniqueJobs:
if job['parentSourceId'] != vCenterID:
print("not vcenter")
continue
elif "isDeleted" in job:
continue
elif "isPaused" in job:
if job['isPaused']:
pausedJobs.append(job)
continue
print("Pausing: " + job['name'])
resp = cluster.PauseJob(job['id'])
print("The following jobs were previously paused before this operation.")
for pJob in pausedJobs:
print(pJob['name'])
if args.resume:
for job in uniqueJobs:
if job['parentSourceId'] != vCenterID:
continue
elif "isDeleted" in job:
continue
print("Resuming: " + job['name'])
resp = cluster.ResumeJob(job['id'])
elif args.job:
job = cluster.GetFilteredRequest("/public/protectionJobs", "?names=" + args.job)
job[0].update({"indexingPolicy":{
"disableIndexing": False,
"allowPrefixes": [
"/"
],
"denyPrefixes": [
"/$Recycle.Bin",
"/Windows",
"/ProgramData",
"/System Volume Information",
"/Users/*/AppData",
"/Recovery",
"/usr",
"/sys",
"/proc",
"/lib",
"/grub",
"/grub2",
"/opt/splunk",
"/splunk",
]
}})
resp = cluster.UpdateVMProtectionJob(job[0])
@@ -0,0 +1,268 @@
#!/usr/bin/python
import sys,argparse,json,time
sys.path.insert(0, './classes/')
import cohesityAPI as cohesity
import automationsAPI as dashboard
# Global variables that will be used across all functions
global tagid
tagid = None
global tagName
tagName = None
# Begin Functions
def GetArgs():
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument('--cluster', '-c', type=str, action='store')
parser.add_argument('--vcenter', '-v', type=str, action='store')
parser.add_argument('--tag', '-t', type=str, action='store')
parser.add_argument('--group', '-g', type=str, action='store')
parser.add_argument('--action', '-a', type=str, action='store')
parser.add_argument('--list', '-l', type=str, action='store')
parser.add_argument('--help', '-h', action='store_true')
return (parser.parse_args())
def PrintHelp():
print("\nBasic Usage:")
print("\npython3 updateProtectionGroupTags.py -c hostname.nd.gov -p MyApp@SQL")
print("\t -c FQDN of Cohesity cluster address")
print("\t -v FQDN of vCenter Server")
print("\t -t vmWare Tag Name")
print("\t -g Protection group name")
print("\t -a Action to perform 'add' or 'remove'")
print("\t -l List that needs to be updated, the 'include' or 'exclude'")
print("\t -h Prints this help message")
def GetTagID(sources):
globalList = globals()
for source in sources:
if globalList['tagid'] is None:
GetNode(sources)
def GetNode(record):
globalList = globals()
# A non 'node' element was found, check each child key to see if one contains 'kTag'
if "protectionSource" in record:
if "vmWareProtectionSource" in record['protectionSource']:
if record['protectionSource']['vmWareProtectionSource']['type'] == "kTag":
# Tag elements have been found, look for the correct tag and assign the id to the global variable
if record['protectionSource']['vmWareProtectionSource']['name'] == globalList['tagName']:
# Tag was found based on the name
globalList['tagid'] = record['protectionSource']['id']
print('Tag was found with ID: ' + str(record['protectionSource']['id']))
# Recurse through all 'nodes' elements looking for kTag
if "nodes" in record:
for node in record['nodes']:
# The tag hasn't been found, search the next record
if globalList['tagid'] is None:
GetNode(node)
# The tag was found and the global id value was set, stop iterating.
else:
break
def UpdateProtectionGroupExcludes(group, action):
globalList = globals()
isExcluded = False
tagArray = []
if "excludeVmTagIds" in group:
for index in group['excludeVmTagIds']:
for tag in index:
tagArray.append(tag)
if tag == globalList['tagid']:
isExcluded = True
# Add the tag to the exclusion
if action == 'add':
if isExcluded == True:
print(globalList['tagName'] + " tag is already excluded for " + group['name'])
else:
print("Adding exclusion tag '" + globalList['tagName'] + "' to group " + group['name'])
tagArray.append(globalList['tagid'])
# Remove the tag from the exclusion
elif action == 'remove':
if isExcluded == True:
print("Removing exclusion tag '" + globalList['tagName'] + "' from group " + group['name'])
tagArray.remove(globalList['tagid'])
else:
print(globalList['tagName'] + " tag is not excluded for " + group['name'])
return(tagArray)
def UpdateProtectionGroupIncludes(group, action):
globalList = globals()
isIncluded = False
tagArray = []
if "vmTagIds" in group:
for index in group['vmTagIds']:
for tag in index:
tagArray.append(tag)
if tag == globalList['tagid']:
isIncluded = True
# Add the tag to the inclusion
if action == 'add':
if isIncluded == True:
print(globalList['tagName'] + " tag is already included for " + group['name'])
else:
print("Adding inclusion tag '" + globalList['tagName'] + "' to group " + group['name'])
tagArray.append(globalList['tagid'])
# Remove the tag from the inclusion
elif action == 'remove':
if isIncluded == True:
print("Removing inclusion tag '" + globalList['tagName'] + "' from group " + group['name'])
tagArray.remove(globalList['tagid'])
else:
print(globalList['tagName'] + " tag is not included for " + group['name'])
return(tagArray)
# Begin Main
globalList = globals()
sourceFound = False
vCenter = None
vCenterID = None
tagIds = []
args = GetArgs()
if args.help:
PrintHelp()
exit(1)
# Validate arguments & assign variables if necessary
if not args.cluster:
sys.exit("Error: Specify a Cohesity cluster fqdn with -c parameter.")
else:
globalList['tagName'] = args.tag
if not args.vcenter:
sys.exit("Error: Specify a vCenter with -v parameter")
if not args.tag:
sys.exit("Error: Specify a tag with the -t parameter.")
if not args.action:
sys.exit("Error: Specify an action [add|remove] with -a parameter.")
elif args.action != 'add' and args.action != 'remove':
sys.exit("Error: Use only 'add' or 'remove' with -a parameter.")
if not args.list:
sys.exit("Error: Specify a list [include|exclude] with -l parameter.")
elif args.list != 'exclude' and args.list != 'include':
sys.exit("Error: Use only 'include' or 'exclude' with -l parameter.")
# Connect to the Cohesity cluster
cluster = cohesity.API(args.cluster)
authToken = cluster.GetAuthToken()
cluster.UpdateHeaders(authToken['accessToken'])
# Locate the correct vCenter and capture the id, this will be useful to validate child objects
vmSources = cluster.GetFilteredRequest("/public/protectionSources", "?environments=kVMware")
for source in vmSources:
if source['protectionSource']['name'] == args.vcenter:
sourceFound = True
vCenter = source
vCenterID = source['protectionSource']['id']
break
# The vCenter we are looking for was not in the list of kVMware environments, stop processing.
if sourceFound == False:
sys.exit("Error: " + args.vcenter + " is not registered to " + args.cluster)
# Look up the tag id based on the tagName and set a global variable when found
# IMPROVEMENT TO GET AWAY FROM GLOBAL VAR
GetTagID(vCenter)
if globalList['tagid'] is None:
sys.exit("Error: The tag '" + args.tag + "' does not exist in " + vCenter['protectionSource']['name'])
if args.group:
# Replace '@' symbol with '%40' for URL encoding in the REST API
protectionGroup = args.group.replace("@","%40")
vmJobs = cluster.GetFilteredRequest("/public/protectionJobs", "?names=" + protectionGroup)
uniqueJobs = {job['id'] : job for job in vmJobs}.values()
for job in uniqueJobs:
if job['parentSourceId'] != vCenterID:
sys.exit(job['name'] + " does not belong to source " + vCenter['protectionSource']['name'])
else:
if 'isDeleted' in job:
continue
if (args.list == 'exclude'):
excludeTagIds = UpdateProtectionGroupExcludes(job, args.action)
if "excludeVmTagIds" in job:
job['excludeVmTagIds'][0] = excludeTagIds
else:
job.update({"excludeVmTagIds": [ excludeTagIds ]})
else:
includeTagIds = UpdateProtectionGroupIncludes(job, args.action)
if "vmTagIds" in job:
job['vmTagIds'][0] = includeTagIds
else:
job.update({"includeVmTagIds": [ includeTagIds ]})
# Take acion on group(s)
resp = cluster.UpdateVMProtectionJob(job)
print(resp.content)
else:
vmJobs = cluster.GetFilteredRequest("/public/protectionJobs", "?environments=kVMware")
uniqueJobs = {job['id'] : job for job in vmJobs}.values()
for job in uniqueJobs:
if "isPaused" in job:
if job['isPaused'] == True:
continue
# If the job is marked for deletion, skip
if 'isDeleted' in job:
if job['isDeleted'] == True:
continue
# If the job does not belong to this vCetner, skip
if job['parentSourceId'] != vCenterID:
continue
else:
if (args.list == 'exclude'):
excludeTagIds = UpdateProtectionGroupExcludes(job, args.action)
if "excludeVmTagIds" in job:
job['excludeVmTagIds'][0] = excludeTagIds
else:
job.update({"excludeVmTagIds": [ excludeTagIds ]})
else:
includeTagIds = UpdateProtectionGroupIncludes(job, args.action)
if "vmTagIds" in job:
job['vmTagIds'][0] = includeTagIds
else:
job.update({"includeVmTagIds": [ includeTagIds ]})
# Take action on group(s)
resp = cluster.UpdateVMProtectionJob(job)
if args.group:
dashboard.send_automation({'AutomationName': 'Infra-Cohesity', 'Action': 'Maintenance', 'Platform': 'Python-updateProtectionGroupTags.py', 'Units': 10})
else:
dashboard.send_automation({'AutomationName': 'Infra-Cohesity', 'Action': 'Maintenance', 'Platform': 'Python-updateProtectionGroupTags.py', 'Units': 240})
+171
View File
@@ -0,0 +1,171 @@
# All entries must be in lower case, the scripts will normalize all hostnames to lowercase to match.
---
azureNPD01:
id: 1
jobs:
- "none"
vms:
# INC0410358
- "^itd\\S+pamac[0-9]+"
- "^itd\\S+pamac\\S+[0-9]+"
- "^vm-itd\\S+pamac[0-9]+"
- "^vm-itd\\S+pamac\\S+[0-9]+"
# INC0410366
- "sntest"
# INC0572225
- "itdsosblscmtwt1"
templates:
- "[a-z]*"
azurePRD01:
id: 37
jobs:
- "none"
vms:
# INC0410358
- "^itd\\S+pamac[0-9]+"
- "^itd\\S+pamac\\S+[0-9]+"
- "^vm-itd\\S+pamac[0-9]+"
- "^vm-itd\\S+pamac\\S+[0-9]+"
# INC0410366
- "sntest"
templates:
- "[a-z]*"
azureCARES01:
id: 4
jobs:
- "none"
vms:
- "itdmltesting"
# INC0410358
- "^itd\\S+pamac[0-9]+"
- "^itd\\S+pamac\\S+[0-9]+"
- "^vm-itd\\S+pamac[0-9]+"
- "^vm-itd\\S+pamac\\S+[0-9]+"
# INC0410366
- "sntest"
templates:
- "[a-z]*"
azureINFRA01:
id: 28
jobs:
# INC0410358
- "Infra-Networking-PA@infra01"
# INC0199104
- "Infra-Azure-Networking-Infoblox@infra01"
vms:
# INC0410358
- "^itd\\S+pamac[0-9]+"
- "^itd\\S+pamac\\S+[0-9]+"
- "itdinfrapanoramalab-prd-400"
- "^vm-itd\\S+pamac[0-9]+"
- "^vm-itd\\S+pamac\\S+[0-9]+"
- "vm-itdinf01exmac01-prd-001"
- "vm-itdinf01exmac02-prd-001"
- "itdinf01exmac01-prd-001"
- "itdinf01exmac02-prd-001"
- "vm-itdinfra01sharedbackuppamac001-prd-001"
- "vm-itdinfra01sharedbackuppamac002-prd-002"
- "itdinfra01sharedbackuppamac001-prd-001"
- "itdinfra01sharedbackuppamac002-prd-002"
# INC0199104
- "^vm-ns[0-9]+-prd-[0-9]+"
- "ns13.ns.nd.gov"
- "ns14.ns.nd.gov"
# INC0422813, INC0422812
- "(^vm?|^itd?)\\S+infra\\S+srx\\S+"
# INC0422811
- "itdnettestingsrx"
# Documented exception, these are the cohesity servers themselves
- "sa[0-9]*"
# INC0410366
- "sntest"
templates:
- "[a-z]*"
azurePAAS01:
id: 12226
jobs:
- "Infra-Networking-PA@NDIT-PaaS"
vms:
- "^vm-itd\\S+pamac[0-9]+"
- "^vm-itd\\S+pamac\\S+us[0-9]+"
templates:
- "[a-z]*"
itdvmvc2:
id: 1
jobs:
- "ITD-POC-zmeier@itdvmvc2"
vms:
# INC0410366
- "clone"
- "delete"
- "broken"
- "itdzmtest"
- "sntest"
# RITM0413140
- "^itdnessus[0-9]+"
templates:
- "[a-z]*"
itdvmvc1:
id: 3156
jobs:
# INC0428498
- "itd-poc-zmeier@itdvmvc1"
- "ITD-POC-zmeier@itdvmvc1"
# INC0468249
- "NDPERS-Applications@physical"
sql:
# DBA's are using RedGate on this server
- "itdsql16p3.nd.gov"
vms:
# RITM0413140
- "^itdnessus[0-9]+"
# INC0199104
- "infoblox-egmv1.ns.nd.gov"
- "infoblox-gmv1.ns.nd.gov"
- "vm-ns4.ndnic.com-prd-400"
# INC0152611
- "itdavayarec.nd.gov"
- "itdbiscmsvra.nd.gov"
- "itdbiscmsvrb.nd.gov"
- "itdwfoaes4.nd.gov"
# INC0410383
- "itdcaldera1"
- "itdcuckoo1"
- "itdcape1"
- "itdcape2"
- "itdremnux"
- "itdflarevm"
# INC0488924
- "itdremworkstation.nd.gov"
- "itdremworkstation"
# INC0410366
- "sntest"
- "clone"
- "delete"
- "broken"
- "itdzmtest"
# INC0488902 / 20250310 (vantis)
- "admin_vm"
- "controller0"
- "controller1"
- "controller2"
- "worker0"
- "worker1"
- "worker2"
- "worker3"
- "security"
- "bastion"
- "admin"
# INC0848221
- "Windows Server 2025 Standard 24H2.6"
templates:
- "[a-z]*"
itdvmvct1:
id: 8557
jobs:
- "[a-z]*"
vms:
- "[a-z]*"
- "sntest"
templates:
- "[a-z]*"
@@ -0,0 +1,126 @@
#!/usr/bin/python
import sys,argparse,json,time
import time
sys.path.insert(0, './classes/')
import cohesityAPI as cohesity
def GetArgs():
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument('--source', '-s', type=str, action='store')
parser.add_argument('--replica', '-r', type=str, action='store')
parser.add_argument('--delete', '-del', type=str, action='store')
return (parser.parse_args())
args = GetArgs()
# Connect to the Cohesity cluster
source_cluster = cohesity.API(args.source)
source_token = source_cluster.GetAuthToken()
source_cluster.UpdateHeaders(source_token['accessToken'])
source_jobs = source_cluster.GetRequest("/public/protectionJobs")
# Connect to the Replica cluster
replica_cluster = cohesity.API(args.replica)
replica_token = replica_cluster.GetAuthToken()
replica_cluster.UpdateHeaders(replica_token['accessToken'])
replica_jobs = replica_cluster.GetRequest("/public/protectionJobs")
deleted_on_source=[]
deleted_on_remote=[]
source_only=[]
remote_only=[]
legal_holds_on_deleted_jobs=[]
for s_job in source_jobs:
if 'isDeleted' in s_job:
if s_job['isDeleted'] == True:
deleted_on_source.append(s_job)
# Verify there aren't any orphaned snapshots due to legal holds
p = s_job['policyId'].split(":")
policy = str(p[0])+":"+str(p[1])+":"+str(s_job['id'])
run_data = source_cluster.GetRequestV2("/data-protect/protection-groups/"+policy+"/runs?numRuns=1000")
runs = run_data['runs']
for run in runs:
if run['onLegalHold']:
url = "https://{0}/protection/group/run/backup/{1}/{2}".format(args.source,run['protectionGroupId'],run['id'])
legal_holds_on_deleted_jobs.append(url)
continue
is_on_both = False
issource_only = False
isremote_only = False
for r_job in replica_jobs:
if s_job['name'] == r_job['name']:
is_on_both = True
if is_on_both:
continue
else:
source_only.append(s_job)
for r_job in replica_jobs:
is_on_both = False
issource_only = False
isremote_only = False
if 'isDeleted' in r_job:
if r_job['isDeleted'] == True:
deleted_on_replica.append(r_job)
# Verify there aren't any orphaned snapshots due to legal holds
p = r_job['policyId'].split(":")
policy = str(p[0])+":"+str(p[1])+":"+str(r_job['id'])
run_data = replica_cluster.GetRequestV2("/data-protect/protection-groups/"+policy+"/runs?numRuns=1000")
runs = run_data['runs']
for run in runs:
if run['onLegalHold']:
url = "https://{0}/protection/group/run/backup/{1}/{2}".format(args.replica,run['protectionGroupId'],run['id'])
legal_holds_on_deleted_jobs.append(url)
continue
for s_job in source_jobs:
if s_job['name'] == r_job['name']:
is_on_both = True
if is_on_both:
continue
else:
remote_only.append(r_job)
print("\nSource Only:")
for src_val in source_only:
#resp = source_cluster.DeleteJob("/public/protectionJobs/"+str(value['id']))
#print(resp.content)
print(src_val['name'])
print("\nRemote Only:")
for repl_val in remote_only:
print(repl_val['name'])
if (args.delete):
print("Deleting " + repl_val['name'] + " on replica")
resp = replica_cluster.DeleteJob("/public/protectionJobs/"+str(value['id']))
print(resp.content)
print("\nDeleted in Source:")
for del_val in deleted_on_source:
print(del_val['name'])
print("\nDeleted on Replica:")
for del_val in deleted_on_source:
print(del_val['name'])
print("\nDeleted Jobs with legal holds on snapshots:")
for lh in legal_holds_on_deleted_jobs:
print(json.dumps(lh,indent=2))
+139
View File
@@ -0,0 +1,139 @@
#
# Module manifest for module 'PSGet_ITDAzureRM'
#
# Generated by: zmeier
#
# Generated on: 2/18/2019
#
@{
# Script module or binary module file associated with this manifest.
RootModule = 'ITDAzureRM.psm1'
# Version number of this module.
ModuleVersion = '0.4.0'
# Supported PSEditions
# CompatiblePSEditions = @()
# ID used to uniquely identify this module
GUID = 'dec17c9a-f0ec-439f-ac81-bc24a4815115'
# Author of this module
Author = 'zmeier','cdfelchle'
# Company or vendor of this module
CompanyName = 'Unknown'
# Copyright statement for this module
Copyright = '(c) 2019 zmeier. All rights reserved.'
# Description of the functionality provided by this module
Description = 'Functions for Azure Resource Manager administrative tasks'
# Minimum version of the Windows PowerShell engine required by this module
# PowerShellVersion = ''
# Name of the Windows PowerShell host required by this module
# PowerShellHostName = ''
# Minimum version of the Windows PowerShell host required by this module
# PowerShellHostVersion = ''
# Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
# DotNetFrameworkVersion = ''
# Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
# CLRVersion = ''
# Processor architecture (None, X86, Amd64) required by this module
# ProcessorArchitecture = ''
# Modules that must be imported into the global environment prior to importing this module
# RequiredModules = @()
# Assemblies that must be loaded prior to importing this module
# RequiredAssemblies = @()
# Script files (.ps1) that are run in the caller's environment prior to importing this module.
# ScriptsToProcess = @()
# Type files (.ps1xml) to be loaded when importing this module
# TypesToProcess = @()
# Format files (.ps1xml) to be loaded when importing this module
# FormatsToProcess = @()
# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess
# NestedModules = @()
# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
FunctionsToExport = 'Set-ITDAzureRMResourceGroupAssignment', 'Get-ITDAzureRMIPs',
'Set-ITDAzureRMResourceGroupTags',
'Set-ITDAzureRMResourceGroupTagsRefresh',
'New-ITDAzureRMPolicyDefinition',
'Find-ITDAzureRMUntaggedResources', 'New-ITDAzureRMVM',
'Remove-ITDAzureRMResourceGroupTags', 'Add-ITDAzureRMNewDataDisk',
'New-ITDAzureVM', 'New-ITDAzureRMResourceGroup', 'Find-ITDAzureRMVM',
'Set-ITDAzureRmVMBackup'
# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export.
CmdletsToExport = '*'
# Variables to export from this module
VariablesToExport = '*'
# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export.
AliasesToExport = '*'
# DSC resources to export from this module
# DscResourcesToExport = @()
# List of all modules packaged with this module
# ModuleList = @()
# List of all files packaged with this module
# FileList = @()
# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell.
PrivateData = @{
PSData = @{
# Tags applied to this module. These help with module discovery in online galleries.
# Tags = @()
# A URL to the license for this module.
# LicenseUri = ''
# A URL to the main website for this project.
# ProjectUri = ''
# A URL to an icon representing this module.
# IconUri = ''
# ReleaseNotes of this module
# ReleaseNotes = ''
# Prerelease string of this module
# Prerelease = ''
# Flag to indicate whether the module requires explicit user acceptance for install/update/save
# RequireLicenseAcceptance = False
# External dependent modules of this module
# ExternalModuleDependencies = ''
} # End of PSData hashtable
} # End of PrivateData hashtable
# HelpInfo URI of this module
# HelpInfoURI = ''
# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix.
# DefaultCommandPrefix = ''
}
File diff suppressed because it is too large Load Diff
+102
View File
@@ -0,0 +1,102 @@
#20190204 - 2nd comment, this one in master branch
#7th comment
# copy disk example
<#
set-azurermcontext -Subscription npd01
$disk = Get-AzureRmDisk -ResourceGroupName rg-doh-intranetconnections-tst -DiskName vm-itddohict1-app-tst-001
set-azurermcontext -Subscription prd01
$newdisk = New-AzureRmDiskConfig -SourceResourceId $disk.Id -Location centralus -CreateOption Copy
New-AzureRmDisk -ResourceGroupName rg-doh-intranetconnections-prd -DiskName vm-itddohicp1-app-prd-001 -Disk $newdisk
#>
$computername="itdadfsldt1"
#Provide the subscription Id
$subscriptionId = '76297098-764c-43de-8525-c9fda1b237be'
#Provide the name of your resource group
$resourceGroupName ='rg-infra-adfs-tst'
Set-AzureRmContext -Subscription infra01
$disk = Get-AzureRmDisk -ResourceGroupName $resourceGroupName -DiskName "dev_sda-vm_itdadfsldt1_tst"
Set-AzureRmContext -Subscription npd01
$newdisk = New-AzureRmDiskConfig -SourceResourceId $disk.Id -Location centralus -CreateOption Copy
New-AzureRmDisk -ResourceGroupName $resourceGroupName -DiskName vm-$computername-os-tst -Disk $newdisk
#Provide the name of the OS disk that will be created using the snapshot
$osDiskName = "vm-$computername-os-tst"
#Provide the name of the virtual machine
$virtualMachineName = "vm-$computername-tst"
$nicName = "nic-$computername-tst-101"
#Provide the size of the virtual machine
#e.g. Standard_DS3
#Get all the vm sizes in a region using below script:
#e.g. Get-AzureRmVMSize -Location westus
$virtualMachineSize = 'Standard_A1'
#Set the context to the subscription Id where Managed Disk will be created
#Select-AzureRmSubscription -SubscriptionId $SubscriptionId
Set-AzureRmContext -Subscription npd01
<#
$resourceGroupName="rg-doh-intranetconnections-prd"
$osDiskName="vm-itddohicp1-os-prd"
$virtualMachineName="vm-itddohicp1-prd"
$virtualMachineSize="Standard_B2ms"
$nicName="nic-itddohicp1-prd"
#>
$disk = Get-AzureRmDisk -ResourceGroupName $resourceGroupName -DiskName $osDiskName
#Initialize virtual machine configuration
$VirtualMachine = New-AzureRmVMConfig -VMName $virtualMachineName -VMSize $virtualMachineSize
#Use the Managed Disk Resource Id to attach it to the virtual machine. Please change the OS type to linux if OS disk has linux OS
$VirtualMachine = Set-AzureRmVMOSDisk -VM $VirtualMachine -ManagedDiskId $disk.Id -CreateOption Attach -Linux
$nic = Get-AzureRmNetworkInterface -ResourceGroupName $resourcegroupname -Name $nicName
$VirtualMachine = Add-AzureRmVMNetworkInterface -VM $VirtualMachine -Id $nic.Id
#Create the virtual machine with Managed Disk
New-AzureRmVM -VM $VirtualMachine -ResourceGroupName $resourceGroupName -Location centralus
Set-AzureRmContext -Subscription infra01
$disks = Get-AzureRmDisk -ResourceGroupName rg-infra-adfs-tst -ov npddisks
Set-AzureRmContext -Subscription npd01
ForEach($d in $disks)
{
$newdisk=$null
$computername=$null
$computername = $d.name.split('_')[2]
If($d.name -like "*sda*")
{
$newname = "vm-$computername-os-tst"
}
If($d.name -like "*sdb*")
{
$newname = "vm-$computername-app-tst-101"
$newdisk = New-AzureRmDiskConfig -SourceResourceId $d.Id -Location centralus -createoption Copy
New-AzureRmDisk -ResourceGroupName rg-infra-adfs-tst -DiskName $NewName -Disk $newdisk
}
}
<#
$disk = Get-AzureRmDisk -ResourceGroupName rg-infra-adfs-tst -DiskName dev_sdb-vm_itdadfsintldt1_tst
set-azurermcontext -Subscription npd01
$newdisk = New-AzureRmDiskConfig -SourceResourceId $disk.Id -Location centralus -CreateOption Copy
New-AzureRmDisk -ResourceGroupName rg-infra-adfs-tst -DiskName vm-itdadfsldt1-app-tst-001 -Disk $newdisk
#>
+20
View File
@@ -0,0 +1,20 @@
# Introduction
TODO: Give a short introduction of your project. Let this section explain the objectives or the motivation behind this project.
# Getting Started
TODO: Guide users through getting your code up and running on their own system. In this section you can talk about:
1. Installation process
2. Software dependencies
3. Latest releases
4. API references
# Build and Test
TODO: Describe and show how to build your code and run the tests.
# Contribute
TODO: Explain how other users and developers can contribute to make your code better.
If you want to learn more about creating good readme files then refer the following [guidelines](https://docs.microsoft.com/en-us/azure/devops/repos/git/create-a-readme?view=azure-devops). You can also seek inspiration from the below readme files:
- [ASP.NET Core](https://github.com/aspnet/Home)
- [Visual Studio Code](https://github.com/Microsoft/vscode)
- [Chakra Core](https://github.com/Microsoft/ChakraCore)
+12
View File
@@ -0,0 +1,12 @@
---
- name: zm test
hosts: itdscmt1.nd.gov
tasks:
- name: run command
win_command: netstat
- name: Run PowerShell script with parameter
ansible.windows.win_powershell:
script: |
New-Item D:\{{ ComputerName }}.txt
Write-Output "{{ ComputerName }}"
Binary file not shown.