update
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
*.swp
|
||||
*.pyc
|
||||
classes/__pycache__
|
||||
log/*
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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']
|
||||
@@ -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})
|
||||
@@ -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))
|
||||
@@ -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})
|
||||
@@ -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})
|
||||
@@ -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']))
|
||||
@@ -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})
|
||||
@@ -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})
|
||||
@@ -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})
|
||||
@@ -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})
|
||||
@@ -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))
|
||||
Reference in New Issue
Block a user