Add cloud and patching demos (#9)

Add demos for cloud provisioning and patching
This commit is contained in:
willtome
2022-03-29 14:01:28 -05:00
committed by GitHub
parent d162dcdb50
commit 8e56c5bbf6
88 changed files with 3273 additions and 18 deletions

1
.gitignore vendored
View File

@@ -6,3 +6,4 @@ choose_demo_example_azure.yml
choose_demo_example_aws.yml
.ansible.cfg
*.gz

2
ansible.cfg Normal file
View File

@@ -0,0 +1,2 @@
[defaults]
collections_paths=./collections

34
cloud/README.md Normal file
View File

@@ -0,0 +1,34 @@
# Ansible Cloud Demos
## Setup
> These steps may differ if you in your environment
### Add AWS Credentials
1) Add AWS Access and Secret key to the AWS Credential created by the setup job
### Add Workshop Credential Password
1) Add the password used to login to Controller. This allows you to connect to Windows Servers provisioned with Create VM job. Required until [RFE](https://github.com/ansible/workshops/issues/1597]) is complete
### Remove Inventory Variables
1) Remove Workshop Inventory variables on the Details page of the inventory. Required until [RFE](https://github.com/ansible/workshops/issues/1597]) is complete
### Getting your Puiblic Key for Create Infra Job
1) Connect to the command line of your Controller server. This is easiest to do by opening the VS Code Web Editor from the landing page where you found the Controller login details.
2) Open a Terminal Window in the VS Code Web Editor.
3) SSH to one of your linux nodes (eg. `ssh node1`). This should log you into the node as `ec2-user`
4) `cat .ssh/authorized_keys` and copy the key listed including the `ssh-rsa` prefix
## Demos
### Cloud / Create Infra
The Create Infra job builds cloud infrastructure based on the provider definition in the included `demo.cloud` collection.
### Cloud / Create VM
The Create VM job builds a VM in the given provider based on the included `demo.cloud` collection. VM blueprints define variables for each provider that override the collection roles.

View File

@@ -1,6 +1,6 @@
---
vm_blueprint_providers:
- aws
aws_instance_filter: 'Windows_Server-2019-English-Core-Base*'
aws_image_filter: 'Windows_Server-2019-English-Core-Base*'
aws_instance_size: t3.medium
aws_userdata_template: aws_windows_userdata

15
cloud/destroy_vm.yml Normal file
View File

@@ -0,0 +1,15 @@
---
- hosts: "{{ HOSTS }}"
gather_facts: no
tasks:
- name: list systems to be destroyed
pause:
seconds: 30
prompt: "{{ inventory_hostname }} will be DESTROYED in 30 seconds. Cancel the job to Abort."
- name: destroy vm
include_role:
name: "demo.cloud.aws"
tasks_from: destroy_vm
when: "'cloud_aws' in group_names"

View File

@@ -0,0 +1,24 @@
---
#######
# AWS VARS
#######
aws_vpc_name: ansible
aws_vpc_prefix: demo
aws_vpc_cidr_block: 10.0.0.0/16
aws_subnet_cidr: 10.0.1.0/24
aws_region: us-east-1
aws_vm_name: "{{ vm_name }}"
aws_vm_owner: "{{ vm_owner }}"
aws_blueprint: "{{ vm_blueprint }}"
#aws_image_filter: "{{ omit }}"
#aws_instance_size: "{{ omit }}"
#aws_image_architecture: "{{ omit }}"
#aws_image_owners: "{{ omit }} "
aws_userdata_template: default
aws_keypair_name: "{{ aws_vpc_name }}-{{ aws_vpc_prefix }}-demo-key"
aws_securitygroup_name: "{{ aws_vpc_name }}-{{ aws_vpc_prefix }}-sec-group"
aws_env_tag: prod
aws_purpose_tag: ansible_demo
aws_ansiblegroup_tag: cloud
aws_ec2_wait: true

View File

@@ -0,0 +1,118 @@
---
- name: AWS | CREATE INFRA | vpc
amazon.aws.ec2_vpc_net:
state: present
name: "{{ aws_vpc_name }}-{{ aws_vpc_prefix }}-vpc"
cidr_block: "{{ aws_vpc_cidr_block }}"
tenancy: default
region: "{{ aws_region }}"
tags:
owner: "{{ aws_vpc_name }}"
purpose: "{{ aws_purpose_tag }}"
register: aws_vpc
- name: AWS | CREATE INFRA | internet gateway
amazon.aws.ec2_vpc_igw:
state: present
vpc_id: "{{ aws_vpc.vpc.id }}"
region: "{{ aws_region }}"
tags:
Name: "{{ aws_vpc_name }}-{{aws_vpc_prefix }}-vpc-igw"
owner: "{{ aws_vpc_name }}"
purpose: "{{ aws_purpose_tag }}"
register: aws_gateway
- name: Create security group internal
amazon.aws.ec2_group:
state: present
name: "{{ aws_vpc_name }}-{{aws_vpc_prefix }}-sec-group"
region: "{{ aws_region }}"
description: Inbound WinRM and RDP, http for demo servers and internal AD ports
rules:
- proto: tcp
ports:
- 80 # HTTP
- 443 # HTTPS
- 22 # SSH
- 5986 # WinRM
- 3389 # RDP
cidr_ip: 0.0.0.0/0
- proto: icmp
to_port: -1
from_port: -1
cidr_ip: 0.0.0.0/0
- proto: tcp
ports:
- 80 # HTTP
- 5986 # WinRM
- 3389 # RDP
- 53 # DNS
- 88 # Kerberos Authentication
- 135 # RPC
- 139 # Netlogon
- 389 # LDAP
- 445 # SMB
- 464 # Kerberos Authentication
- 5432 # PostgreSQL
- 636 # LDAPS (LDAP over TLS)
- 873 # Rsync
- 3268-3269 # Global Catalog
- 1024-65535 # Ephemeral RPC ports
cidr_ip: 10.0.0.0/16
- proto: udp
ports:
- 53 # DNS
- 88 # Kerberos Authentication
- 123 # NTP
- 137-138 # Netlogon
- 389 # LDAP
- 445 # SMB
- 464 # Kerberos Authentication
- 1024-65535 # Ephemeral RPC ports
cidr_ip: 10.0.0.0/16
rules_egress:
- proto: -1
cidr_ip: 0.0.0.0/0
vpc_id: "{{ aws_vpc.vpc.id }}"
tags:
Name: "{{ aws_vpc_name }}-{{aws_vpc_prefix }}-sec-group"
owner: "{{ aws_vpc_name }}"
purpose: "{{ aws_purpose_tag }}"
- name: Create a subnet on the VPC
amazon.aws.ec2_vpc_subnet:
state: present
vpc_id: "{{ aws_vpc.vpc.id }}"
cidr: "{{ aws_subnet_cidr }}"
region: "{{ aws_region }}"
map_public: yes
tags:
Name: "{{ aws_vpc_name }}-{{aws_vpc_prefix }}-subnet"
owner: "{{ aws_vpc_name }}"
purpose: "{{ aws_purpose_tag }}"
register: aws_subnet
- name: Create a subnet route table
amazon.aws.ec2_vpc_route_table:
state: present
vpc_id: "{{ aws_vpc.vpc.id }}"
region: "{{ aws_region }}"
subnets:
- "{{ aws_subnet.subnet.id }}"
routes:
- dest: 0.0.0.0/0
gateway_id: "{{ aws_gateway.gateway_id }}"
tags:
Name: "{{ aws_vpc_name }}-{{aws_vpc_prefix }}-vpc-rtbl"
owner: "{{ aws_vpc_name }}"
purpose: "{{ aws_purpose_tag }}"
- name: Create AWS keypair
amazon.aws.ec2_key:
name: "{{ aws_vpc_name }}-{{aws_vpc_prefix }}-demo-key"
region: "{{ aws_region }}"
key_material: "{{ aws_public_key }}"
state: present
tags:
owner: "{{ aws_vpc_name }}"
purpose: "{{ aws_purpose_tag }}"

View File

@@ -0,0 +1,47 @@
---
- name: AWS | CREATE VM | get subnet info
amazon.aws.ec2_vpc_subnet_info:
region: "{{ aws_region }}"
filters:
"tag:Name": "{{ aws_vpc_name }}-{{ aws_vpc_prefix }}-subnet"
register: aws_subnet
- name: AWS | CREATE VM | save subnet id
set_fact:
aws_subnet_id: "{{ aws_subnet.subnets|map(attribute='id')| list | last }}"
- name: AWS| CREATE VM | find ami
amazon.aws.ec2_ami_info:
region: "{{ aws_region }}"
owners: "{{ aws_image_owners | default(omit)}}"
filters:
name: "{{ aws_image_filter }}"
architecture: "{{ aws_image_architecture | default(omit) }}"
register: amis
- name: AWS| CREATE VM | save ami
set_fact:
aws_instance_ami: >
{{ amis.images | selectattr('name', 'defined') | sort(attribute='creation_date') | last }}
- name: AWS| CREATE VM | create instance
amazon.aws.ec2_instance:
network:
assign_public_ip: yes
key_name: "{{ aws_keypair_name }}"
instance_type: "{{ aws_instance_size }}"
image_id: "{{ aws_instance_ami.image_id }}"
region: "{{ aws_region }}"
security_group: "{{ aws_securitygroup_name }}"
tags:
blueprint: "{{ aws_blueprint }}"
purpose: "{{ aws_purpose_tag }}"
env: "{{ aws_env_tag }}"
ansible_group: "{{ aws_ansiblegroup_tag }}"
owner: "{{ aws_vm_owner }}"
info: "This instance was built by Red Hat Product Demos"
Name: "{{ aws_vm_name }}"
wait: "{{ aws_ec2_wait }}"
vpc_subnet_id: "{{ aws_subnet_id }}"
user_data: "{{ lookup('template', aws_userdata_template+'.j2', template_vars=dict(aws_vm_name=vm_name)) }}"
register: aws_vm_output

View File

@@ -0,0 +1,7 @@
---
- name: Destroy VM
amazon.aws.ec2_instance:
state: absent
instance_ids: "{{ instance_id }}"
region: "{{ placement.region }}"
delegate_to: localhost

View File

@@ -0,0 +1,29 @@
<powershell>
# Disable .Net Optimization Service
Get-ScheduledTask *ngen* | Disable-ScheduledTask
# Disable Windows Auto Updates
# https://docs.aws.amazon.com/AWSEC2/latest/WindowsGuide/troubleshooting-windows-instances.html#high-cpu-issue
reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update" /v AUOptions /t REG_DWORD /d 1 /f
net stop wuauserv
net start wuauserv
# Remove policies stopping us from enabling WinRM
reg delete "HKLM\SOFTWARE\Policies\Microsoft\Windows\WinRM\Service" /v AllowBasic /f
reg delete "HKLM\SOFTWARE\Policies\Microsoft\Windows\WinRM\Service" /v AllowUnencryptedTraffic /f
reg delete "HKLM\SOFTWARE\Policies\Microsoft\Windows\WinRM\Service" /v DisableRunAs /f
# Disable Windows Defender Monitoring
Set-MpPreference -DisableRealtimeMonitoring $true
# Enable WinRM
Invoke-WebRequest -Uri https://raw.githubusercontent.com/ansible/ansible/devel/examples/scripts/ConfigureRemotingForAnsible.ps1 -OutFile C:\ConfigureRemotingForAnsible.ps1
C:\ConfigureRemotingForAnsible.ps1 -ForceNewSSLCert -EnableCredSSP
# add ec2-user
$Password = ConvertTo-SecureString {{ ansible_password }} -AsPlainText -Force
New-LocalUser -Name "ec2-user" -Description "Ansible Service Account" -Password $Password
Add-LocalGroupMember -Group "Administrators" -Member "ec2-user"
Rename-Computer -NewName {{ aws_vm_name }} -Force -Restart
</powershell>

View File

@@ -0,0 +1,9 @@
---
##############
# Azure Vars
##############
az_region:
az_rg_name: ansible
az_rg_prefix: demo
az_vnet_cidr_block: 10.0.0.0/16
az_subnet_cidr: 10.0.1.0/24

View File

@@ -0,0 +1,76 @@
---
- name: AZURE | CREATE INFRA | resource group
azure.azcollection.azure_rm_resourcegroup:
name: "{{ az_rg_name }}-{{ az_rg_prefix }}-rg"
location: "{{ az_region }}"
- name: AZURE | CREATE INFRA | virtual network
azure.azcollection.azure_rm_virtualnetwork:
resource_group: "{{ az_rg_name }}-{{ az_rg_prefix }}-rg"
name: "{{ az_rg_name }}-{{ az_rg_prefix }}-vnet"
address_prefixes: "{{ az_vnet_cidr }}"
- name: AZURE | CREATE INFRA | subnet
azure.azcollection.azure_rm_subnet:
resource_group: "{{ az_rg_name }}-{{ az_rg_prefix }}-rg"
name: "{{ az_rg_name }}-{{ az_rg_prefix }}-subnet }}"
address_prefix: "{{ az_subnet_cidr }}"
virtual_network: "{{ az_rg_name }}-{{ az_rg_prefix }}-vnet"
- name: AZURE | CREATE INFRA | security group
azure.azcollection.azure_rm_securitygroup:
resource_group: "{{ az_rg_name }}-{{ az_rg_prefix }}-rg"
name: "{{ az_rg_name }}-{{ az_rg_prefix }}-sec-group"
rules:
- name: External
protocol: Tcp
destination_port_range:
- 80 # HTTP
- 443 # HTTPS
- 5986 # WinRM
- 3389 # RDP
access: Allow
priority: 1001
direction: Inbound
- name: Ping
protocol: Icmp
access: Allow
priority: 1002
direction: Inbound
- name: Internal TCP
protocol: Tcp
destination_port_range:
- 80 # HTTP
- 5986 # WinRM
- 3389 # RDP
- 53 # DNS
- 88 # Kerberos Authentication
- 135 # RPC
- 139 # Netlogon
- 389 # LDAP
- 445 # SMB
- 464 # Kerberos Authentication
- 5432 # PostgreSQL
- 636 # LDAPS (LDAP over TLS)
- 873 # Rsync
- 3268-3269 # Global Catalog
- 1024-65535 # Ephemeral RPC ports
access: Allow
priority: 1003
direction: Inbound
source_address_prefix: "{{ az_vnet_cidr_block }}"
- name: Internal UDP
protocol: Udp
destination_port_range:
- 53 # DNS
- 88 # Kerberos Authentication
- 123 # NTP
- 137-138 # Netlogon
- 389 # LDAP
- 445 # SMB
- 464 # Kerberos Authentication
- 1024-65535 # Ephemeral RPC ports
access: Allow
priority: 1004
direction: Inbound
source_address_prefix: "{{ az_vnet_cidr_block }}"

View File

@@ -0,0 +1,97 @@
#!/usr/bin/env python
from ansible.module_utils.basic import * # noqa
DOCUMENTATION = '''
---
module: scan_packages
short_description: Return installed packages information as fact data
description:
- Return information about installed packages as fact data
'''
EXAMPLES = '''
# Example fact output:
# host | success >> {
# "ansible_facts": {
# "packages": [
# {
# "version": "1.0.6-5",
# "source": "apt",
# "arch": "amd64",
# "name": "libbz2-1.0"
# },
# {
# "version": "2.7.1-4ubuntu1",
# "source": "apt",
# "arch": "amd64",
# "name": "patch"
# },
# {
# "version": "4.8.2-19ubuntu1",
# "source": "apt",
# "arch": "amd64",
# "name": "gcc-4.8-base"
# }
# ]
'''
def rpm_package_list():
import rpm
trans_set = rpm.TransactionSet()
installed_packages = []
for package in trans_set.dbMatch():
package_details = {
'name':package[rpm.RPMTAG_NAME],
'version':package[rpm.RPMTAG_VERSION],
'release':package[rpm.RPMTAG_RELEASE],
'epoch':package[rpm.RPMTAG_EPOCH],
'arch':package[rpm.RPMTAG_ARCH],
'source':'rpm' }
if installed_packages == []:
installed_packages = [package_details]
else:
installed_packages.append(package_details)
return installed_packages
def deb_package_list():
import apt
apt_cache = apt.Cache()
installed_packages = []
apt_installed_packages = [pk for pk in apt_cache.keys() if apt_cache[pk].is_installed]
for package in apt_installed_packages:
ac_pkg = apt_cache[package].installed
package_details = {
'name':package,
'version':ac_pkg.version,
'arch':ac_pkg.architecture,
'source':'apt'}
if installed_packages == []:
installed_packages = [package_details]
else:
installed_packages.append(package_details)
return installed_packages
def main():
module = AnsibleModule(
argument_spec = dict(os_family=dict(required=True))
)
ans_os = module.params['os_family']
if ans_os in ('RedHat', 'Suse', 'openSUSE Leap'):
packages = rpm_package_list()
elif ans_os == 'Debian':
packages = deb_package_list()
else:
packages = None
if packages is not None:
results = dict(ansible_facts=dict(packages=packages))
else:
results = dict(skipped=True, msg="Unsupported Distribution")
module.exit_json(**results)
main()

View File

@@ -0,0 +1,186 @@
#!/usr/bin/env python
import re
from ansible.module_utils.basic import * # noqa
DOCUMENTATION = '''
---
module: scan_services
short_description: Return service state information as fact data
description:
- Return service state information as fact data for various service management utilities
'''
EXAMPLES = '''
- monit: scan_services
# Example fact output:
# host | success >> {
# "ansible_facts": {
# "services": {
# "network": {
# "source": "sysv",
# "state": "running",
# "name": "network"
# },
# "arp-ethers.service": {
# "source": "systemd",
# "state": "stopped",
# "name": "arp-ethers.service"
# }
# }
# }
'''
class BaseService(object):
def __init__(self, module):
self.module = module
self.incomplete_warning = False
class ServiceScanService(BaseService):
def gather_services(self):
services = {}
service_path = self.module.get_bin_path("service")
if service_path is None:
return None
initctl_path = self.module.get_bin_path("initctl")
chkconfig_path = self.module.get_bin_path("chkconfig")
# sysvinit
if service_path is not None and chkconfig_path is None:
rc, stdout, stderr = self.module.run_command("%s --status-all 2>&1 | grep -E \"\\[ (\\+|\\-) \\]\"" % service_path, use_unsafe_shell=True)
for line in stdout.split("\n"):
line_data = line.split()
if len(line_data) < 4:
continue # Skipping because we expected more data
service_name = " ".join(line_data[3:])
if line_data[1] == "+":
service_state = "running"
else:
service_state = "stopped"
services[service_name] = {"name": service_name, "state": service_state, "source": "sysv"}
# Upstart
if initctl_path is not None and chkconfig_path is None:
p = re.compile('^\s?(?P<name>.*)\s(?P<goal>\w+)\/(?P<state>\w+)(\,\sprocess\s(?P<pid>[0-9]+))?\s*$')
rc, stdout, stderr = self.module.run_command("%s list" % initctl_path)
real_stdout = stdout.replace("\r","")
for line in real_stdout.split("\n"):
m = p.match(line)
if not m:
continue
service_name = m.group('name')
service_goal = m.group('goal')
service_state = m.group('state')
if m.group('pid'):
pid = m.group('pid')
else:
pid = None # NOQA
payload = {"name": service_name, "state": service_state, "goal": service_goal, "source": "upstart"}
services[service_name] = payload
# RH sysvinit
elif chkconfig_path is not None:
#print '%s --status-all | grep -E "is (running|stopped)"' % service_path
p = re.compile(
'(?P<service>.*?)\s+[0-9]:(?P<rl0>on|off)\s+[0-9]:(?P<rl1>on|off)\s+[0-9]:(?P<rl2>on|off)\s+'
'[0-9]:(?P<rl3>on|off)\s+[0-9]:(?P<rl4>on|off)\s+[0-9]:(?P<rl5>on|off)\s+[0-9]:(?P<rl6>on|off)')
rc, stdout, stderr = self.module.run_command('%s' % chkconfig_path, use_unsafe_shell=True)
# Check for special cases where stdout does not fit pattern
match_any = False
for line in stdout.split('\n'):
if p.match(line):
match_any = True
if not match_any:
p_simple = re.compile('(?P<service>.*?)\s+(?P<rl0>on|off)')
match_any = False
for line in stdout.split('\n'):
if p_simple.match(line):
match_any = True
if match_any:
# Try extra flags " -l --allservices" needed for SLES11
rc, stdout, stderr = self.module.run_command('%s -l --allservices' % chkconfig_path, use_unsafe_shell=True)
elif '--list' in stderr:
# Extra flag needed for RHEL5
rc, stdout, stderr = self.module.run_command('%s --list' % chkconfig_path, use_unsafe_shell=True)
for line in stdout.split('\n'):
m = p.match(line)
if m:
service_name = m.group('service')
service_state = 'stopped'
if m.group('rl3') == 'on':
rc, stdout, stderr = self.module.run_command('%s %s status' % (service_path, service_name), use_unsafe_shell=True)
service_state = rc
if rc in (0,):
service_state = 'running'
#elif rc in (1,3):
else:
if 'root' in stderr or 'permission' in stderr.lower() or 'not in sudoers' in stderr.lower():
self.incomplete_warning = True
continue
else:
service_state = 'stopped'
service_data = {"name": service_name, "state": service_state, "source": "sysv"}
services[service_name] = service_data
return services
class SystemctlScanService(BaseService):
def systemd_enabled(self):
# Check if init is the systemd command, using comm as cmdline could be symlink
try:
f = open('/proc/1/comm', 'r')
except IOError:
# If comm doesn't exist, old kernel, no systemd
return False
for line in f:
if 'systemd' in line:
return True
return False
def gather_services(self):
services = {}
if not self.systemd_enabled():
return None
systemctl_path = self.module.get_bin_path("systemctl", opt_dirs=["/usr/bin", "/usr/local/bin"])
if systemctl_path is None:
return None
rc, stdout, stderr = self.module.run_command("%s list-unit-files --type=service | tail -n +2 | head -n -2" % systemctl_path, use_unsafe_shell=True)
for line in stdout.split("\n"):
line_data = line.split()
if len(line_data) != 2:
continue
if line_data[1] == "enabled":
state_val = "running"
else:
state_val = "stopped"
services[line_data[0]] = {"name": line_data[0], "state": state_val, "source": "systemd"}
return services
def main():
module = AnsibleModule(argument_spec = dict())
service_modules = (ServiceScanService, SystemctlScanService)
all_services = {}
incomplete_warning = False
for svc_module in service_modules:
svcmod = svc_module(module)
svc = svcmod.gather_services()
if svc is not None:
all_services.update(svc)
if svcmod.incomplete_warning:
incomplete_warning = True
if len(all_services) == 0:
results = dict(skipped=True, msg="Failed to find any services. Sometimes this is due to insufficient privileges.")
else:
results = dict(ansible_facts=dict(services=all_services))
if incomplete_warning:
results['msg'] = "WARNING: Could not find status for all services. Sometimes this is due to insufficient privileges."
module.exit_json(**results)
main()

View File

@@ -0,0 +1,66 @@
#!powershell
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
# WANT_JSON
# POWERSHELL_COMMON
$uninstall_native_path = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
$uninstall_wow6432_path = "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
if ([System.IntPtr]::Size -eq 4) {
# This is a 32-bit Windows system, so we only check for 32-bit programs, which will be
# at the native registry location.
[PSObject []]$packages = Get-ChildItem -Path $uninstall_native_path |
Get-ItemProperty |
Select-Object -Property @{Name="name"; Expression={$_."DisplayName"}},
@{Name="version"; Expression={$_."DisplayVersion"}},
@{Name="publisher"; Expression={$_."Publisher"}},
@{Name="arch"; Expression={ "Win32" }} |
Where-Object { $_.name }
} else {
# This is a 64-bit Windows system, so we check for 64-bit programs in the native
# registry location, and also for 32-bit programs under Wow6432Node.
[PSObject []]$packages = Get-ChildItem -Path $uninstall_native_path |
Get-ItemProperty |
Select-Object -Property @{Name="name"; Expression={$_."DisplayName"}},
@{Name="version"; Expression={$_."DisplayVersion"}},
@{Name="publisher"; Expression={$_."Publisher"}},
@{Name="arch"; Expression={ "Win64" }} |
Where-Object { $_.name }
$packages += Get-ChildItem -Path $uninstall_wow6432_path |
Get-ItemProperty |
Select-Object -Property @{Name="name"; Expression={$_."DisplayName"}},
@{Name="version"; Expression={$_."DisplayVersion"}},
@{Name="publisher"; Expression={$_."Publisher"}},
@{Name="arch"; Expression={ "Win32" }} |
Where-Object { $_.name }
}
$result = New-Object psobject @{
ansible_facts = New-Object psobject @{
packages = $packages
}
changed = $false
}
Exit-Json $result;

View File

@@ -0,0 +1,31 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
DOCUMENTATION = '''
---
module: win_scan_packages
short_description: Return Package state information as fact data
description:
- Return Package state information as fact data for various Packages
'''
EXAMPLES = '''
- monit: win_scan_packages
# Example fact output:
# host | success >> {
# "ansible_facts": {
# "packages": [
{
"name": "Mozilla Firefox 76.0.1 (x64 en-US)",
"version": "76.0.1",
"publisher": "Mozilla",
"arch": "Win64"
},
{
"name": "Mozilla Maintenance Service",
"version": "76.0.1",
"publisher": "Mozilla",
"arch": "Win64"
},
# }
'''

View File

@@ -0,0 +1,30 @@
#!powershell
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
# WANT_JSON
# POWERSHELL_COMMON
$result = New-Object psobject @{
ansible_facts = New-Object psobject @{
services = Get-Service |
Select-Object -Property @{Name="name"; Expression={$_."DisplayName"}},
@{Name="win_svc_name"; Expression={$_."Name"}},
@{Name="state"; Expression={$_."Status".ToString().ToLower()}}
}
changed = $false
}
Exit-Json $result;

View File

@@ -0,0 +1,34 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
DOCUMENTATION = '''
---
module: win_scan_services
short_description: Return service state information as fact data
description:
- Return service state information as fact data for various service management utilities
'''
EXAMPLES = '''
- monit: win_scan_services
# Example fact output:
# host | success >> {
# "ansible_facts": {
# "services": [
{
"name": "AllJoyn Router Service",
"win_svc_name": "AJRouter",
"state": "stopped"
},
{
"name": "Application Layer Gateway Service",
"win_svc_name": "ALG",
"state": "stopped"
},
{
"name": "Application Host Helper Service",
"win_svc_name": "AppHostSvc",
"state": "running"
},
# }
'''

View File

@@ -0,0 +1,36 @@
build_report_network
========
Installs Apache and creates a report based on facts from network devices
Requirements
------------
Must run on Apache server
Role Variables / Configuration
--------------
N/A
Dependencies
------------
N/A
Example Playbook
----------------
The role can be used to create an html report on any number of Linux hosts using any number of network devices
```
---
- hosts: all
tasks:
- name: Run Network Report
import_role:
name: shadowman.reports.build_report_network
```

View File

@@ -0,0 +1,202 @@
p.hostname {
color: #000000;
font-weight: bolder;
font-size: large;
margin: auto;
width: 50%;
}
#subtable {
background: #ebebeb;
margin: 0px;
width: 100%;
}
#subtable tbody tr td {
padding: 5px 5px 5px 5px;
}
#subtable thead th {
padding: 5px;
}
* {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
font-family: "Open Sans", "Helvetica";
}
a {
color: #ffffff;
}
p {
color: #ffffff;
}
h1 {
text-align: center;
color: #ffffff;
}
body {
background:#353a40;
padding: 0px;
margin: 0px;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
table {
border-collapse: separate;
background:#fff;
@include border-radius(5px);
@include box-shadow(0px 0px 5px rgba(0,0,0,0.3));
}
.main_net_table {
margin:50px auto;
}
thead {
@include border-radius(5px);
}
thead th {
font-size:16px;
font-weight:400;
color:#fff;
@include text-shadow(1px 1px 0px rgba(0,0,0,0.5));
text-align:left;
padding:20px;
border-top:1px solid #858d99;
background: #353a40;
&:first-child {
@include border-top-left-radius(5px);
}
&:last-child {
@include border-top-right-radius(5px);
}
}
tbody tr td {
font-weight:400;
color:#5f6062;
font-size:13px;
padding:20px 20px 20px 20px;
border-bottom:1px solid #e0e0e0;
}
tbody tr:nth-child(2n) {
background:#f0f3f5;
}
tbody tr:last-child td {
border-bottom:none;
&:first-child {
@include border-bottom-left-radius(5px);
}
&:last-child {
@include border-bottom-right-radius(5px);
}
}
td {
vertical-align: top;
}
span.highlight {
background-color: yellow;
}
.expandclass {
color: #5f6062;
}
.content{
display:none;
margin: 10px;
}
header {
width: 100%;
position: initial;
float: initial;
padding: 0;
margin: 0;
border-radius: 0;
height: 88px;
background-color: #171717;
}
.header-container {
margin: 0 auto;
width: 100%;
height: 100%;
max-width: 1170px;
padding: 0;
float: initial;
display: flex;
align-items: center;
}
.header-logo {
width: 137px;
border: 0;
margin: 0;
margin-left: 15px;
}
.header-link {
margin-left: 40px;
text-decoration: none;
cursor: pointer;
text-transform: uppercase;
font-size: 15px;
font-family: 'Red Hat Text';
font-weight: 500;
}
.header-link:hover {
text-shadow: 0 0 0.02px white;
text-decoration: none;
}
table.net_info td {
padding: 5px;
}
p.expandclass:hover {
text-decoration: underline;
color: #EE0000;
cursor: pointer;
}
.summary_info {
}
.ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active, a.ui-button:active, .ui-button:active, .ui-button.ui-state-active:hover {
border: 1px solid #5F0000;
background: #EE0000;
}
div#net_content {
padding: 0px;
height: auto !important;
}
img.router_image {
vertical-align: middle;
padding: 0px 10px 10px 10px;
width: 50px;
}
table.net_info {
width: 100%;
}
p.internal_label {
color: #000000;
}

View File

@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Logos" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="930.2px" height="350px" viewBox="0 0 930.2 350" style="enable-background:new 0 0 930.2 350;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
.st1{fill:#EE0000;}
</style>
<title>Logo-Red_Hat-Ansible_Automation_Platform-A-Reverse-RGB</title>
<path class="st0" d="M383.3,228.5h18.8L446,335.7h-17.5l-12.4-31.4h-48l-12.6,31.4h-16.7L383.3,228.5z M410.9,291l-18.7-47l-18.7,47
H410.9z"/>
<path class="st0" d="M455.2,257.7h15.3v7.8c6.2-6.2,14.7-9.6,23.5-9.3c17.9,0,30.5,12.4,30.5,30.5v49h-15.3v-46.5
c0-12.3-7.5-19.8-19.3-19.8c-7.8-0.3-15.1,3.6-19.3,10.1v56.1h-15.3V257.7z"/>
<path class="st0" d="M543,315.5c8.1,6.4,16.7,9.8,25.4,9.8c11,0,18.7-4.8,18.7-11.7c0-5.5-4-8.7-12.6-10l-14.1-2
c-15.5-2.3-23.3-9.5-23.3-21.6c0-14.1,12.3-23.6,30.5-23.6c11.3-0.1,22.3,3.4,31.5,9.9l-7.8,10.1c-8.6-5.7-16.4-8.1-24.7-8.1
c-9.3,0-15.6,4.3-15.6,10.6c0,5.7,3.7,8.4,12.9,9.8l14.1,2c15.5,2.3,23.6,9.7,23.6,21.7c0,14-14.1,24.5-32.6,24.5
c-13.5,0-25.6-4-34.2-11.5L543,315.5z"/>
<path class="st0" d="M611.6,235.6c0-5.2,4.1-9.4,9.3-9.5c0,0,0,0,0,0c5.2-0.2,9.7,3.9,9.9,9.1c0.2,5.2-3.9,9.7-9.1,9.9
c-0.2,0-0.5,0-0.7,0C615.8,245.1,611.6,240.9,611.6,235.6C611.6,235.7,611.6,235.7,611.6,235.6z M628.6,335.7h-15.3v-78h15.3V335.7z
"/>
<path class="st0" d="M685.5,336.9c-8.5,0-16.8-2.7-23.6-7.8v6.6h-15.2V228.5l15.3-3.4v40c6.6-5.6,15.1-8.7,23.7-8.6
c22.1,0,39.4,17.7,39.4,40.1C725.2,319.1,707.9,336.9,685.5,336.9z M662,279.2v35.2c4.9,5.7,13,9.2,21.8,9.2
c15,0,26.4-11.5,26.4-26.8c0-15.3-11.5-27-26.4-27C674.9,269.8,667.1,273.2,662,279.2z"/>
<path class="st0" d="M755,335.7h-15.3V228.5l15.3-3.4V335.7z"/>
<path class="st0" d="M810.5,337.1c-23,0-40.9-17.7-40.9-40.4c0-22.5,17.2-40.1,39.1-40.1c21.5,0,37.7,17.8,37.7,40.8v4.4h-61.6
c2,13,13.2,22.5,26.4,22.4c7.2,0.2,14.2-2.3,19.8-6.8l9.8,9.7C832.1,333.7,821.5,337.4,810.5,337.1z M784.9,290.2h46.3
c-2.3-11.9-11.5-20.8-22.8-20.8C796.5,269.4,787.2,277.8,784.9,290.2z"/>
<path class="st1" d="M202.8,137.5c18.4,0,45.1-3.8,45.1-25.7c0.1-1.7-0.1-3.4-0.5-5l-11-47.7c-2.5-10.5-4.8-15.2-23.2-24.5
c-14.3-7.3-45.5-19.4-54.7-19.4c-8.6,0-11.1,11.1-21.3,11.1c-9.8,0-17.1-8.3-26.4-8.3c-8.8,0-14.6,6-19,18.4c0,0-12.4,34.9-14,40
c-0.3,0.9-0.4,1.9-0.4,2.9C77.6,92.9,131.1,137.5,202.8,137.5 M250.8,120.7c2.5,12.1,2.5,13.3,2.5,14.9c0,20.6-23.2,32.1-53.7,32.1
c-69,0-129.3-40.3-129.3-67c0-3.7,0.8-7.4,2.2-10.8c-24.8,1.3-56.9,5.7-56.9,34c0,46.4,109.9,103.5,196.9,103.5
c66.7,0,83.5-30.2,83.5-54C296.1,154.6,279.9,133.4,250.8,120.7"/>
<path d="M250.7,120.7c2.5,12.1,2.5,13.3,2.5,14.9c0,20.6-23.2,32.1-53.7,32.1c-69,0-129.3-40.3-129.3-67c0-3.7,0.8-7.4,2.2-10.8
l5.4-13.3c-0.3,0.9-0.4,1.9-0.4,2.8c0,13.6,53.5,58.1,125.2,58.1c18.4,0,45.1-3.8,45.1-25.7c0.1-1.7-0.1-3.4-0.5-5L250.7,120.7z"/>
<path class="st0" d="M869.1,151.2c0,17.5,10.5,26,29.7,26c5.9-0.1,11.8-1,17.5-2.5v-20.3c-3.7,1.2-7.5,1.7-11.3,1.7
c-7.9,0-10.8-2.5-10.8-9.9v-31.1h22.9V94.2h-22.9V67.7l-25,5.4v21.1h-16.6v20.9h16.6L869.1,151.2z M791,151.7
c0-5.4,5.4-8.1,13.6-8.1c5,0,10,0.7,14.9,1.9V156c-4.8,2.6-10.2,3.9-15.6,3.9C795.9,159.9,791.1,156.8,791,151.7 M798.7,177.5
c8.8,0,16-1.9,22.6-6.3v5h24.8v-52.5c0-20-13.5-30.9-35.9-30.9c-12.6,0-25,2.9-38.3,9l9,18.4c9.6-4,17.7-6.5,24.8-6.5
c10.3,0,15.6,4,15.6,12.2v4c-6.1-1.6-12.3-2.4-18.6-2.3c-21.1,0-33.8,8.8-33.8,24.6C768.9,166.6,780.4,177.6,798.7,177.5
M662.5,176.2h26.7v-42.5h44.6v42.5h26.7V67.7h-26.6v41.7h-44.6V67.7h-26.7L662.5,176.2z M561,135.1c0-11.8,9.3-20.8,21.5-20.8
c6.4-0.1,12.6,2.1,17.4,6.4v28.6c-4.7,4.4-10.9,6.7-17.4,6.5C570.5,155.8,561,146.8,561,135.1 M600.2,176.1H625V62.3l-25,5.4v30.8
c-6.4-3.6-13.6-5.5-20.9-5.4c-23.9,0-42.6,18.4-42.6,42c-0.3,23,18.1,41.9,41.1,42.2c0.2,0,0.5,0,0.7,0c7.9,0,15.6-2.5,22-7.1V176.1
z M486.5,113.2c7.9,0,14.6,5.1,17.2,13h-34.2C471.9,118,478.2,113.2,486.5,113.2 M444.2,135.2c0,23.9,19.5,42.5,44.6,42.5
c13.8,0,23.9-3.7,34.3-12.4l-16.6-14.7c-3.9,4-9.6,6.2-16.4,6.2c-8.8,0.2-16.8-4.9-20.2-13h58.4v-6.2c0-26-17.5-44.8-41.4-44.8
c-23.2-0.4-42.4,18.2-42.7,41.5C444.2,134.6,444.2,134.9,444.2,135.2 M400.9,90.5c8.8,0,13.8,5.6,13.8,12.2s-5,12.2-13.8,12.2h-26.3
V90.5H400.9z M347.9,176.2h26.7v-39.5h20.3l20.5,39.5h29.7l-23.9-43.4c12.4-5,20.5-17.1,20.4-30.5c0-19.5-15.3-34.5-38.3-34.5H348
L347.9,176.2z"/>
</svg>

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

View File

@@ -0,0 +1,24 @@
- name: create HTML report
ansible.builtin.template:
src: report.j2
dest: "{{ file_path }}/network.html"
- name: copy CSS over
ansible.builtin.copy:
src: "css"
dest: "{{ file_path }}"
directory_mode: true
- name: copy logos over
ansible.builtin.copy:
src: "{{ item }}"
dest: "{{ file_path }}"
directory_mode: true
loop:
- "webpage_logo.png"
- "redhat-ansible-logo.svg"
- "router.png"
- name: display link to inventory report
ansible.builtin.debug:
msg: "Please go to http://{{ ansible_host }}/network.html"

View File

@@ -0,0 +1,31 @@
<! INTERNAL TABLE FOR BGP --!>
<div id="accordion">
<div class="ui-accordion ui-widget ui-helper-reset" role="tablist">
<h3 class="ui-accordion-header ui-corner-top ui-state-default ui-accordion-icons ui-accordion-header-collapsed ui-corner-all" role="tab" id="ui-id-3" aria-controls="ui-id-4" aria-selected="false" aria-expanded="false" tabindex="0">BGP Global Info</h3>
<div class="net_content ui-accordion-content ui-corner-bottom ui-helper-reset ui-widget-content" id="ui-id-4" aria-labelledby="ui-id-3" role="tabpanel" aria-hidden="true" style="display: none; height: 194px;">
{% if hostvars[network_switch]['ansible_network_resources']['bgp_global'] is defined and hostvars[network_switch]['ansible_network_resources']['bgp_global']|length > 0 %}
<table id="subtable">
<thead>
<tr>
<th>ASN</th>
<th>Router ID</th>
</tr>
</thead>
<tbody>
{% for bgpinfo in hostvars[network_switch]['ansible_network_resources']['bgp_global'] %}
<tr>
<td>{{bgpinfo['as_number']}}</td>
<td>{{bgpinfo['router_id']|default("Not Configured")}}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% elif hostvars[network_switch]['ansible_network_resources']['bgp_global'] is defined and hostvars[network_switch]['ansible_network_resources']['bgp_global']|length == 0 %}
BGP is not configured on this device
{% else %}
No BGP information available
{% endif %}
</div>
</div>
</div>
<! END INTERNAL TABLE FOR BGP --!>

View File

@@ -0,0 +1,15 @@
<div class="wrapper">
<header>
<div class="header-container">
<a href="https://ansible.com">
<img
class="header-logo"
src="redhat-ansible-logo.svg"
title="Red Hat Ansible"
alt="Red Hat Ansible"
/>
</a>
</div>
</header>

View File

@@ -0,0 +1,41 @@
<! INTERNAL TABLE FOR INTERFACES --!>
<div id="accordion">
<div class="ui-accordion ui-widget ui-helper-reset" role="tablist">
<h3 class="ui-accordion-header ui-corner-top ui-state-default ui-accordion-icons ui-accordion-header-collapsed ui-corner-all" role="tab" id="ui-id-3" aria-controls="ui-id-4" aria-selected="false" aria-expanded="false" tabindex="0">
Interfaces - MTU/Duplex/Speed
</h3>
<div class="net_content ui-accordion-content ui-corner-bottom ui-helper-reset ui-widget-content" id="ui-id-4" aria-labelledby="ui-id-3" role="tabpanel" aria-hidden="true" style="display: none; height: 194px;">
{% if hostvars[network_switch]['ansible_network_resources']['interfaces'] is defined and hostvars[network_switch]['ansible_network_resources']['interfaces']|length > 0 %}
<table id="subtable">
<thead>
<tr>
<th>Interface Name</th>
<th>Description</th>
<th>Duplex</th>
<th>Enabled</th>
<th>MTU</th>
<th>Speed</th>
</tr>
</thead>
<tbody>
{% for interface in hostvars[network_switch]['ansible_network_resources']['interfaces'] %}
<tr>
<td>{{interface['name']}}</td>
<td>{{interface['description']|default("none")}}</td>
<td>{{interface['duplex']|default("default")}}</td>
<td>{{interface['enabled']}}</td>
<td>{{interface['mtu']|default("default")}}</td>
<td>{{interface['speed']|default("default")}}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% elif hostvars[network_switch]['ansible_network_resources']['interfaces'] is defined and hostvars[network_switch]['ansible_network_resources']['interfaces']|length == 0 %}
No interfaces configured on this device
{% else %}
No Interface information available
{% endif %}
</div>
</div>
</div>
<! END INTERNAL TABLE FOR INTERFACES --!>

View File

@@ -0,0 +1,37 @@
<! INTERNAL TABLE FOR l2_interfaces --!>
<div id="accordion">
<div class="ui-accordion ui-widget ui-helper-reset" role="tablist">
<h3 class="ui-accordion-header ui-corner-top ui-state-default ui-accordion-icons ui-accordion-header-collapsed ui-corner-all" role="tab" id="ui-id-3" aria-controls="ui-id-4" aria-selected="false" aria-expanded="false" tabindex="0">L2 Interfaces - Trunk/Access Ports</h3>
<div class="net_content ui-accordion-content ui-corner-bottom ui-helper-reset ui-widget-content" id="ui-id-4" aria-labelledby="ui-id-3" role="tabpanel" aria-hidden="true" style="display: none; height: 194px;">
{% if hostvars[network_switch]['ansible_network_resources']['l2_interfaces'] is defined and hostvars[network_switch]['ansible_network_resources']['l2_interfaces']|length > 0 %}
<table id="subtable">
<thead>
<tr>
<th>Name</th>
<th>Mode</th>
<th>Access VLAN</th>
<th>Trunk Native VLAN</th>
<th>Trunk Allowed VLANs</th>
</tr>
</thead>
<tbody>
{% for l2_interface in hostvars[network_switch]['ansible_network_resources']['l2_interfaces'] %}
<tr>
<td>{{l2_interface['name']}}</td>
<td>{{l2_interface['mode']|default("Not Configured")}}</td>
<td>{{l2_interface['access']['vlan']|default("Not Configured")}}</td>
<td>{{l2_interface['trunk']['native_vlan']|default("Not Configured")}}</td>
<td>{{l2_interface['trunk']['trunk_allowed_vlans']|default("Not Configured")}}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% elif hostvars[network_switch]['ansible_network_resources']['l2_interfaces'] is defined and hostvars[network_switch]['ansible_network_resources']['l2_interfaces']|length == 0 %}
L2 information is not configured on this device
{% else %}
No L2 information available
{% endif %}
</div>
</div>
</div>
<! END INTERNAL TABLE FOR l2_interfaces --!>

View File

@@ -0,0 +1,58 @@
<! INTERNAL TABLE FOR L3_INTERFACES --!>
<div id="accordion">
<div class="ui-accordion ui-widget ui-helper-reset" role="tablist">
<h3 class="ui-accordion-header ui-corner-top ui-state-default ui-accordion-icons ui-accordion-header-collapsed ui-corner-all" role="tab" id="ui-id-3" aria-controls="ui-id-4" aria-selected="false" aria-expanded="false" tabindex="0">L3 Interfaces - IP Addresses</h3>
<div class="net_content ui-accordion-content ui-corner-bottom ui-helper-reset ui-widget-content" id="ui-id-4" aria-labelledby="ui-id-3" role="tabpanel" aria-hidden="true" style="display: none; height: 194px;">
{% if hostvars[network_switch]['ansible_network_resources']['l3_interfaces'] is defined and hostvars[network_switch]['ansible_network_resources']['l3_interfaces']|length > 0 %}
<table id="subtable">
<thead>
<tr>
<th>Interface Name</th>
<th>IPv4</th>
<th>IPv6</th>
</tr>
</thead>
<tbody>
{% for interface in hostvars[network_switch]['ansible_network_resources']['l3_interfaces'] %}
<tr>
<td>{{interface['name']}}</td>
<! INTERNAL IPv4 LOOP FOR L3_INTERFACES --!>
<td>
{% if interface.ipv4 is defined %}
{% for address in interface.ipv4 %}
{% if address['address'] is defined %}
{{address['address']}}
{% else %}
dhcp
{% endif %}
{% if address['secondary'] is defined %}
secondary
{% endif %}
{% if loop.length > 1 and not loop.last %}<br>{% endif %}
{% endfor %}
{% endif %}
</td>
<! END IPv4 INTERNAL LOOP FOR L3_INTERFACES --!>
<! INTERNAL IPv6 LOOP FOR L3_INTERFACES --!>
<td>
{% if interface.ipv6 is defined %}
{% for v6address in interface.ipv6 %}
{{v6address['address']}}
{% if loop.length > 1 and not loop.last %}<br>{% endif %}
{% endfor %}
{% endif %}
</td>
<! END INTERNAL LOOP FOR L3_INTERFACES --!>
</tr>
{% endfor %}
</tbody>
</table>
{% elif hostvars[network_switch]['ansible_network_resources']['l3_interfaces'] is defined and hostvars[network_switch]['ansible_network_resources']['l3_interfaces']|length == 0 %}
L3 information is not configured on this device
{% else %}
No L3 information available
{% endif %}
</div>
</div>
</div>
<! END INTERNAL TABLE FOR L3_INTERFACES --!>

View File

@@ -0,0 +1,29 @@
<! INTERNAL TABLE FOR LACP --!>
<div id="accordion">
<div class="ui-accordion ui-widget ui-helper-reset" role="tablist">
<h3 class="ui-accordion-header ui-corner-top ui-state-default ui-accordion-icons ui-accordion-header-collapsed ui-corner-all" role="tab" id="ui-id-3" aria-controls="ui-id-4" aria-selected="false" aria-expanded="false" tabindex="0">LACP</h3>
<div class="net_content ui-accordion-content ui-corner-bottom ui-helper-reset ui-widget-content" id="ui-id-4" aria-labelledby="ui-id-3" role="tabpanel" aria-hidden="true" style="display: none; height: 194px;">
{% if hostvars[network_switch]['ansible_network_resources']['lacp'] is defined and hostvars[network_switch]['ansible_network_resources']['lacp'].keys()|length > 0 %}
<table id="subtable">
<thead>
<tr>
<th>System Priority</th>
</tr>
</thead>
<tbody>
<tr>
{% if hostvars[network_switch]['ansible_network_resources']['lacp']['system'] is defined %}
<td> {{hostvars[network_switch]['ansible_network_resources']['lacp']['system']['priority']}}</td>
{% endif %}
</tr>
</tbody>
</table>
{% elif hostvars[network_switch]['ansible_network_resources']['lacp'] is defined and hostvars[network_switch]['ansible_network_resources']['lacp']|length == 0 %}
LACP is not configured on this device
{% else %}
No LACP information available
{% endif %}
</div>
</div>
</div>
<! END INTERNAL TABLE FOR LACP --!>

View File

@@ -0,0 +1,33 @@
<! INTERNAL TABLE FOR lldp_interfaces --!>
<div id="accordion">
<div class="ui-accordion ui-widget ui-helper-reset" role="tablist">
<h3 class="ui-accordion-header ui-corner-top ui-state-default ui-accordion-icons ui-accordion-header-collapsed ui-corner-all" role="tab" id="ui-id-3" aria-controls="ui-id-4" aria-selected="false" aria-expanded="false" tabindex="0">LLDP Interfaces</h3>
<div class="net_content ui-accordion-content ui-corner-bottom ui-helper-reset ui-widget-content" id="ui-id-4" aria-labelledby="ui-id-3" role="tabpanel" aria-hidden="true" style="display: none; height: 194px;">
{% if hostvars[network_switch]['ansible_network_resources']['lldp_interfaces'] is defined and hostvars[network_switch]['ansible_network_resources']['lldp_interfaces']|length > 0 %}
<table id="subtable">
<thead>
<tr>
<th>vlan_id</th>
<th>Name</th>
<th>state</th>
</tr>
</thead>
<tbody>
{% for interface in hostvars[network_switch]['ansible_network_resources']['lldp_interfaces'] %}
<tr>
<td>{{interface['name']}}</td>
<td>{{interface['receive']|default("Not Configured")}}</td>
<td>{{interface['transmit']|default("Not Configured")}}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% elif hostvars[network_switch]['ansible_network_resources']['lldp_interfaces'] is defined and hostvars[network_switch]['ansible_network_resources']['lldp_interfaces'].keys()|length == 0 %}
LLDP is not configured on this device
{% else %}
No LLDP information available
{% endif %}
</div>
</div>
</div>
<! END INTERNAL TABLE FOR lldp_interfaces --!>

View File

@@ -0,0 +1,29 @@
<! INTERNAL TABLE FOR OSPF --!>
<div id="accordion">
<div class="ui-accordion ui-widget ui-helper-reset" role="tablist">
<h3 class="ui-accordion-header ui-corner-top ui-state-default ui-accordion-icons ui-accordion-header-collapsed ui-corner-all" role="tab" id="ui-id-3" aria-controls="ui-id-4" aria-selected="false" aria-expanded="false" tabindex="0">OSPF Global Info</h3>
<div class="net_content ui-accordion-content ui-corner-bottom ui-helper-reset ui-widget-content" id="ui-id-4" aria-labelledby="ui-id-3" role="tabpanel" aria-hidden="true" style="display: none; height: 194px;">
{% if hostvars[network_switch]['ansible_network_resources']['ospfv2'] is defined and hostvars[network_switch]['ansible_network_resources']['ospfv2']|length > 0 %}
<table id="subtable">
<thead>
<tr>
<th>Process ID</th>
<th>Router ID</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>{{ hostvars[network_switch]['ansible_network_resources']['ospfv2']['parameters']['router_id'] }}</td>
</tr>
</tbody>
</table>
{% elif hostvars[network_switch]['ansible_network_resources']['ospfv2'] is defined and hostvars[network_switch]['ansible_network_resources']['ospfv2']|length == 0 %}
OSPF is not configured on this device
{% else %}
No OSPF information available
{% endif %}
</div>
</div>
</div>
<! END INTERNAL TABLE FOR OSPF --!>

View File

@@ -0,0 +1,111 @@
<!DOCTYPE html>
<html>
<head>
<title> Ansible Network Automation Report </title>
<link rel="stylesheet" type="text/css" href="//fonts.googleapis.com/css?family=Open+Sans" />
<link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
<link rel="stylesheet" href="css/new.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<script>
$(function() {
$( "#accordion > div" ).accordion({
header: "h3",
active: false,
collapsible: true
});
});
</script>
<script>
(function(document) {
'use strict';
var TableFilter = (function(myArray) {
var search_input;
function _onInputSearch(e) {
search_input = e.target;
var tables = document.getElementsByClassName(search_input.getAttribute('data-table'));
myArray.forEach.call(tables, function(table) {
myArray.forEach.call(table.tBodies, function(tbody) {
myArray.forEach.call(tbody.rows, function(row) {
var text_content = row.textContent.toLowerCase();
var search_val = search_input.value.toLowerCase();
row.style.display = text_content.indexOf(search_val) > -1 ? '' : 'none';
});
});
});
}
return {
init: function() {
var inputs = document.getElementsByClassName('search-input');
myArray.forEach.call(inputs, function(input) {
input.oninput = _onInputSearch;
});
}
};
})(Array.prototype);
document.addEventListener('readystatechange', function() {
if (document.readyState === 'complete') {
TableFilter.init();
}
});
})(document);
</script>
</head>
<body>
<div class="wrapper">
{% include 'header.j2' %}
<section>
<center>
<h1>Ansible Network Automation Report</h1>
<h3><input type="search" placeholder="Search..." class="form-control search-input" data-table="main_net_table"/>
</center>
<table class="table table-striped mt32 main_net_table">
<thead>
<tr>
<th>Network Device</th>
<th>Layer 1</th>
<th>Layer 2</th>
<th>Layer 3</th>
</tr>
</thead>
<tbody>
{% for network_switch in groups['tag_Router']|sort %}
<tr>
<td class="summary_info">
<div id="hostname">
<p class="hostname">
<img class="router_image" src="router.png"> {{ hostvars[network_switch]['ansible_net_hostname'].split('.')[0] }}</p>
</div>
{% include 'summary.j2' %}
</td>
<td>
{% include 'interfaces.j2' %}
</td>
<td>
{% include 'vlans.j2' %}
{% include 'lldp_interfaces.j2' %}
{% include 'l2_interfaces.j2' %}
</td>
<td>
{% include 'l3_interfaces.j2' %}
{% include 'lacp.j2' %}
{% include 'bgp.j2' %}
{% include 'ospf.j2' %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
<center><p>Created with</p><br><img src="webpage_logo.png" width="300">
</center>
</section>
</div>
</body>
</html>

View File

@@ -0,0 +1,25 @@
<div id="net_info_div">
<table class="net_info">
<tbody>
<tr>
<td>Platform</td>
<td class="sub_net_info">{{hostvars[network_switch]['ansible_net_system']}}</td>
</tr>
<tr>
<td>Code Version</td>
<td class="sub_net_info">{{hostvars[network_switch]['ansible_net_version']}}</td>
</tr>
<tr>
<td>Model</td>
<td class="sub_net_info">{{hostvars[network_switch]['ansible_net_model']|default("N/A")}}</td>
</tr>
<tr>
<td>Serial Number</td>
<td class="sub_net_info">{{hostvars[network_switch]['ansible_net_serialnum']}}</td>
</tr>
<tr>
<td>Transport</td>
<td class="sub_net_info">{{hostvars[network_switch]['ansible_net_api']}}</td>
</tbody>
</table>
</div>

View File

@@ -0,0 +1,33 @@
<! INTERNAL TABLE FOR VLANS --!>
<div id="accordion">
<div class="ui-accordion ui-widget ui-helper-reset" role="tablist">
<h3 class="ui-accordion-header ui-corner-top ui-state-default ui-accordion-icons ui-accordion-header-collapsed ui-corner-all" role="tab" id="ui-id-3" aria-controls="ui-id-4" aria-selected="false" aria-expanded="false" tabindex="0">VLANs</h3>
<div class="net_content ui-accordion-content ui-corner-bottom ui-helper-reset ui-widget-content" id="ui-id-4" aria-labelledby="ui-id-3" role="tabpanel" aria-hidden="true" style="display: none; height: 194px;">
{% if hostvars[network_switch]['ansible_network_resources']['vlans'] is defined and hostvars[network_switch]['ansible_network_resources']['vlans']|length > 0 %}
<table id="subtable">
<thead>
<tr>
<th>vlan_id</th>
<th>Name</th>
<th>state</th>
</tr>
</thead>
<tbody>
{% for vlan in hostvars[network_switch]['ansible_network_resources']['vlans'] %}
<tr>
<td>{{vlan['vlan_id']}}</td>
<td>{{vlan['name']|default("none")}}</td>
<td>{{vlan['state']|default("default")}}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% elif hostvars[network_switch]['ansible_network_resources']['vlans'] is defined and hostvars[network_switch]['ansible_network_resources']['vlans']|length == 0 %}
VLANs are not configured on this device
{% else %}
No VLAN information available
{% endif %}
</div>
</div>
</div>
<! END INTERNAL TABLE FOR VLANS --!>

View File

@@ -0,0 +1 @@
file_path: /var/www/html

View File

@@ -0,0 +1,36 @@
build_report_windows
========
Installs Apache and creates a report based on facts from Windows services and packages modules
Requirements
------------
Must run on Apache server
Role Variables / Configuration
--------------
N/A
Dependencies
------------
N/A
Example Playbook
----------------
The role can be used to create an html report on any number of Linux hosts using any number of Windows servers about their services and packages installed
```
---
- hosts: all
tasks:
- name: Run Windows Report
import_role:
name: shadowman.reports.build_report_windows
```

View File

@@ -0,0 +1,2 @@
---
detailedreport: True

View File

@@ -0,0 +1,202 @@
p.hostname {
color: #000000;
font-weight: bolder;
font-size: large;
margin: auto;
width: 50%;
}
#subtable {
background: #ebebeb;
margin: 0px;
width: 100%;
}
#subtable tbody tr td {
padding: 5px 5px 5px 5px;
}
#subtable thead th {
padding: 5px;
}
* {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
font-family: "Open Sans", "Helvetica";
}
a {
color: #ffffff;
}
p {
color: #ffffff;
}
h1 {
text-align: center;
color: #ffffff;
}
body {
background:#353a40;
padding: 0px;
margin: 0px;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
table {
border-collapse: separate;
background:#fff;
@include border-radius(5px);
@include box-shadow(0px 0px 5px rgba(0,0,0,0.3));
}
.main_net_table {
margin:50px auto;
}
thead {
@include border-radius(5px);
}
thead th {
font-size:16px;
font-weight:400;
color:#fff;
@include text-shadow(1px 1px 0px rgba(0,0,0,0.5));
text-align:left;
padding:20px;
border-top:1px solid #858d99;
background: #353a40;
&:first-child {
@include border-top-left-radius(5px);
}
&:last-child {
@include border-top-right-radius(5px);
}
}
tbody tr td {
font-weight:400;
color:#5f6062;
font-size:13px;
padding:20px 20px 20px 20px;
border-bottom:1px solid #e0e0e0;
}
tbody tr:nth-child(2n) {
background:#f0f3f5;
}
tbody tr:last-child td {
border-bottom:none;
&:first-child {
@include border-bottom-left-radius(5px);
}
&:last-child {
@include border-bottom-right-radius(5px);
}
}
td {
vertical-align: top;
}
span.highlight {
background-color: yellow;
}
.expandclass {
color: #5f6062;
}
.content{
display:none;
margin: 10px;
}
header {
width: 100%;
position: initial;
float: initial;
padding: 0;
margin: 0;
border-radius: 0;
height: 88px;
background-color: #171717;
}
.header-container {
margin: 0 auto;
width: 100%;
height: 100%;
max-width: 1170px;
padding: 0;
float: initial;
display: flex;
align-items: center;
}
.header-logo {
width: 137px;
border: 0;
margin: 0;
margin-left: 15px;
}
.header-link {
margin-left: 40px;
text-decoration: none;
cursor: pointer;
text-transform: uppercase;
font-size: 15px;
font-family: 'Red Hat Text';
font-weight: 500;
}
.header-link:hover {
text-shadow: 0 0 0.02px white;
text-decoration: none;
}
table.net_info td {
padding: 5px;
}
p.expandclass:hover {
text-decoration: underline;
color: #EE0000;
cursor: pointer;
}
.summary_info {
}
.ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active, a.ui-button:active, .ui-button:active, .ui-button.ui-state-active:hover {
border: 1px solid #5F0000;
background: #EE0000;
}
div#net_content {
padding: 0px;
height: auto !important;
}
img.router_image {
vertical-align: middle;
padding: 0px 10px 10px 10px;
width: 50px;
}
table.net_info {
width: 100%;
}
p.internal_label {
color: #000000;
}

View File

@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Logos" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="930.2px" height="350px" viewBox="0 0 930.2 350" style="enable-background:new 0 0 930.2 350;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
.st1{fill:#EE0000;}
</style>
<title>Logo-Red_Hat-Ansible_Automation_Platform-A-Reverse-RGB</title>
<path class="st0" d="M383.3,228.5h18.8L446,335.7h-17.5l-12.4-31.4h-48l-12.6,31.4h-16.7L383.3,228.5z M410.9,291l-18.7-47l-18.7,47
H410.9z"/>
<path class="st0" d="M455.2,257.7h15.3v7.8c6.2-6.2,14.7-9.6,23.5-9.3c17.9,0,30.5,12.4,30.5,30.5v49h-15.3v-46.5
c0-12.3-7.5-19.8-19.3-19.8c-7.8-0.3-15.1,3.6-19.3,10.1v56.1h-15.3V257.7z"/>
<path class="st0" d="M543,315.5c8.1,6.4,16.7,9.8,25.4,9.8c11,0,18.7-4.8,18.7-11.7c0-5.5-4-8.7-12.6-10l-14.1-2
c-15.5-2.3-23.3-9.5-23.3-21.6c0-14.1,12.3-23.6,30.5-23.6c11.3-0.1,22.3,3.4,31.5,9.9l-7.8,10.1c-8.6-5.7-16.4-8.1-24.7-8.1
c-9.3,0-15.6,4.3-15.6,10.6c0,5.7,3.7,8.4,12.9,9.8l14.1,2c15.5,2.3,23.6,9.7,23.6,21.7c0,14-14.1,24.5-32.6,24.5
c-13.5,0-25.6-4-34.2-11.5L543,315.5z"/>
<path class="st0" d="M611.6,235.6c0-5.2,4.1-9.4,9.3-9.5c0,0,0,0,0,0c5.2-0.2,9.7,3.9,9.9,9.1c0.2,5.2-3.9,9.7-9.1,9.9
c-0.2,0-0.5,0-0.7,0C615.8,245.1,611.6,240.9,611.6,235.6C611.6,235.7,611.6,235.7,611.6,235.6z M628.6,335.7h-15.3v-78h15.3V335.7z
"/>
<path class="st0" d="M685.5,336.9c-8.5,0-16.8-2.7-23.6-7.8v6.6h-15.2V228.5l15.3-3.4v40c6.6-5.6,15.1-8.7,23.7-8.6
c22.1,0,39.4,17.7,39.4,40.1C725.2,319.1,707.9,336.9,685.5,336.9z M662,279.2v35.2c4.9,5.7,13,9.2,21.8,9.2
c15,0,26.4-11.5,26.4-26.8c0-15.3-11.5-27-26.4-27C674.9,269.8,667.1,273.2,662,279.2z"/>
<path class="st0" d="M755,335.7h-15.3V228.5l15.3-3.4V335.7z"/>
<path class="st0" d="M810.5,337.1c-23,0-40.9-17.7-40.9-40.4c0-22.5,17.2-40.1,39.1-40.1c21.5,0,37.7,17.8,37.7,40.8v4.4h-61.6
c2,13,13.2,22.5,26.4,22.4c7.2,0.2,14.2-2.3,19.8-6.8l9.8,9.7C832.1,333.7,821.5,337.4,810.5,337.1z M784.9,290.2h46.3
c-2.3-11.9-11.5-20.8-22.8-20.8C796.5,269.4,787.2,277.8,784.9,290.2z"/>
<path class="st1" d="M202.8,137.5c18.4,0,45.1-3.8,45.1-25.7c0.1-1.7-0.1-3.4-0.5-5l-11-47.7c-2.5-10.5-4.8-15.2-23.2-24.5
c-14.3-7.3-45.5-19.4-54.7-19.4c-8.6,0-11.1,11.1-21.3,11.1c-9.8,0-17.1-8.3-26.4-8.3c-8.8,0-14.6,6-19,18.4c0,0-12.4,34.9-14,40
c-0.3,0.9-0.4,1.9-0.4,2.9C77.6,92.9,131.1,137.5,202.8,137.5 M250.8,120.7c2.5,12.1,2.5,13.3,2.5,14.9c0,20.6-23.2,32.1-53.7,32.1
c-69,0-129.3-40.3-129.3-67c0-3.7,0.8-7.4,2.2-10.8c-24.8,1.3-56.9,5.7-56.9,34c0,46.4,109.9,103.5,196.9,103.5
c66.7,0,83.5-30.2,83.5-54C296.1,154.6,279.9,133.4,250.8,120.7"/>
<path d="M250.7,120.7c2.5,12.1,2.5,13.3,2.5,14.9c0,20.6-23.2,32.1-53.7,32.1c-69,0-129.3-40.3-129.3-67c0-3.7,0.8-7.4,2.2-10.8
l5.4-13.3c-0.3,0.9-0.4,1.9-0.4,2.8c0,13.6,53.5,58.1,125.2,58.1c18.4,0,45.1-3.8,45.1-25.7c0.1-1.7-0.1-3.4-0.5-5L250.7,120.7z"/>
<path class="st0" d="M869.1,151.2c0,17.5,10.5,26,29.7,26c5.9-0.1,11.8-1,17.5-2.5v-20.3c-3.7,1.2-7.5,1.7-11.3,1.7
c-7.9,0-10.8-2.5-10.8-9.9v-31.1h22.9V94.2h-22.9V67.7l-25,5.4v21.1h-16.6v20.9h16.6L869.1,151.2z M791,151.7
c0-5.4,5.4-8.1,13.6-8.1c5,0,10,0.7,14.9,1.9V156c-4.8,2.6-10.2,3.9-15.6,3.9C795.9,159.9,791.1,156.8,791,151.7 M798.7,177.5
c8.8,0,16-1.9,22.6-6.3v5h24.8v-52.5c0-20-13.5-30.9-35.9-30.9c-12.6,0-25,2.9-38.3,9l9,18.4c9.6-4,17.7-6.5,24.8-6.5
c10.3,0,15.6,4,15.6,12.2v4c-6.1-1.6-12.3-2.4-18.6-2.3c-21.1,0-33.8,8.8-33.8,24.6C768.9,166.6,780.4,177.6,798.7,177.5
M662.5,176.2h26.7v-42.5h44.6v42.5h26.7V67.7h-26.6v41.7h-44.6V67.7h-26.7L662.5,176.2z M561,135.1c0-11.8,9.3-20.8,21.5-20.8
c6.4-0.1,12.6,2.1,17.4,6.4v28.6c-4.7,4.4-10.9,6.7-17.4,6.5C570.5,155.8,561,146.8,561,135.1 M600.2,176.1H625V62.3l-25,5.4v30.8
c-6.4-3.6-13.6-5.5-20.9-5.4c-23.9,0-42.6,18.4-42.6,42c-0.3,23,18.1,41.9,41.1,42.2c0.2,0,0.5,0,0.7,0c7.9,0,15.6-2.5,22-7.1V176.1
z M486.5,113.2c7.9,0,14.6,5.1,17.2,13h-34.2C471.9,118,478.2,113.2,486.5,113.2 M444.2,135.2c0,23.9,19.5,42.5,44.6,42.5
c13.8,0,23.9-3.7,34.3-12.4l-16.6-14.7c-3.9,4-9.6,6.2-16.4,6.2c-8.8,0.2-16.8-4.9-20.2-13h58.4v-6.2c0-26-17.5-44.8-41.4-44.8
c-23.2-0.4-42.4,18.2-42.7,41.5C444.2,134.6,444.2,134.9,444.2,135.2 M400.9,90.5c8.8,0,13.8,5.6,13.8,12.2s-5,12.2-13.8,12.2h-26.3
V90.5H400.9z M347.9,176.2h26.7v-39.5h20.3l20.5,39.5h29.7l-23.9-43.4c12.4-5,20.5-17.1,20.4-30.5c0-19.5-15.3-34.5-38.3-34.5H348
L347.9,176.2z"/>
</svg>

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

View File

@@ -0,0 +1,24 @@
- name: create HTML report
ansible.builtin.template:
src: report.j2
dest: "{{ file_path }}/windows.html"
- name: copy CSS over
ansible.builtin.copy:
src: "css"
dest: "{{ file_path }}"
directory_mode: true
- name: copy logos over
ansible.builtin.copy:
src: "{{ item }}"
dest: "{{ file_path }}"
directory_mode: true
loop:
- "webpage_logo.png"
- "redhat-ansible-logo.svg"
- "server.png"
- name: display link to inventory report
ansible.builtin.debug:
msg: "Please go to http://{{ ansible_host }}/windows.html"

View File

@@ -0,0 +1,15 @@
<div class="wrapper">
<header>
<div class="header-container">
<a href="https://ansible.com">
<img
class="header-logo"
src="redhat-ansible-logo.svg"
title="Red Hat Ansible"
alt="Red Hat Ansible"
/>
</a>
</div>
</header>

View File

@@ -0,0 +1,29 @@
<! INTERNAL TABLE FOR PACKAGES --!>
<div id="accordion">
<div class="ui-accordion ui-widget ui-helper-reset" role="tablist">
<h3 class="ui-accordion-header ui-corner-top ui-state-default ui-accordion-icons ui-accordion-header-collapsed ui-corner-all" role="tab" id="ui-id-3" aria-controls="ui-id-4" aria-selected="false" aria-expanded="false" tabindex="0">Package Facts</h3>
<div class="net_content ui-accordion-content ui-corner-bottom ui-helper-reset ui-widget-content" id="ui-id-4" aria-labelledby="ui-id-3" role="tabpanel" aria-hidden="true" style="display: none; height: 194px;">
<table id="subtable" class="sortable">
<thead>
<tr>
<th>Package Name</th>
<th>Version</th>
<th>Publisher</th>
</tr>
</thead>
<tbody>
{% if hostvars[windows_host]['packages'] is defined %}
{% for package in hostvars[windows_host]['packages'] %}
<tr>
<td>{{package['name']}}</td>
<td>{{package['version']}}</td>
<td>{{package['publisher']}}</td>
</tr>
{% endfor %}
{% endif %}
</tbody>
</table>
</div>
</div>
</div>
<! END INTERNAL TABLE FOR PACKAGES --!>

View File

@@ -0,0 +1,101 @@
<!DOCTYPE html>
<html>
<head>
<title> Ansible Windows Automation Report </title>
<link rel="stylesheet" type="text/css" href="//fonts.googleapis.com/css?family=Open+Sans" />
<link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
<link rel="stylesheet" href="css/new.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<script src="https://www.kryogenix.org/code/browser/sorttable/sorttable.js"></script>
<script>
$(function() {
$( "#accordion > div" ).accordion({
header: "h3",
active: false,
collapsible: true
});
});
</script>
<script>
(function(document) {
'use strict';
var TableFilter = (function(myArray) {
var search_input;
function _onInputSearch(e) {
search_input = e.target;
var tables = document.getElementsByClassName(search_input.getAttribute('data-table'));
myArray.forEach.call(tables, function(table) {
myArray.forEach.call(table.tBodies, function(tbody) {
myArray.forEach.call(tbody.rows, function(row) {
var text_content = row.textContent.toLowerCase();
var search_val = search_input.value.toLowerCase();
row.style.display = text_content.indexOf(search_val) > -1 ? '' : 'none';
});
});
});
}
return {
init: function() {
var inputs = document.getElementsByClassName('search-input');
myArray.forEach.call(inputs, function(input) {
input.oninput = _onInputSearch;
});
}
};
})(Array.prototype);
document.addEventListener('readystatechange', function() {
if (document.readyState === 'complete') {
TableFilter.init();
}
});
})(document);
</script>
</head>
<body>
<div class="wrapper">
{% include 'header.j2' %}
<section>
<center>
<h1>Ansible Windows Automation Report</h1>
<h3><input type="search" placeholder="Search..." class="form-control search-input" data-table="main_net_table"/>
</center>
<table class="table table-striped mt32 main_net_table">
<thead>
<tr>
<th>Windows Device</th>
<th>Operating System</th>
<th>Operating System Kernel Version</th>
</tr>
</thead>
<tbody>
{% for windows_host in groups['tag_Windows']|sort %}
<tr>
<td class="summary_info">
<div id="hostname">
<p class="hostname">
<img class="router_image" src="server.png"> {{ hostvars[windows_host]['inventory_hostname'].split('.')[0] }}</p>
</div>
{% if detailedreport == 'True' %}
{% include 'packages.j2' %}
{% include 'services.j2' %}
{% endif %}
</td>
<td>{{hostvars[windows_host]['ansible_os_family']|default("none")}}</td>
<td>{{hostvars[windows_host]['ansible_distribution']|default("none")}}</td>
</tr>
{% endfor %}
</tbody>
</table>
<center><p>Created with</p><br><img src="webpage_logo.png" width="300">
</center>
</section>
</div>
</body>
</html>

View File

@@ -0,0 +1,29 @@
<! INTERNAL TABLE FOR SERVICES --!>
<div id="accordion">
<div class="ui-accordion ui-widget ui-helper-reset" role="tablist">
<h3 class="ui-accordion-header ui-corner-top ui-state-default ui-accordion-icons ui-accordion-header-collapsed ui-corner-all" role="tab" id="ui-id-3" aria-controls="ui-id-4" aria-selected="false" aria-expanded="false" tabindex="0">Services Facts</h3>
<div class="net_content ui-accordion-content ui-corner-bottom ui-helper-reset ui-widget-content" id="ui-id-4" aria-labelledby="ui-id-3" role="tabpanel" aria-hidden="true" style="display: none; height: 194px;">
<table id="subtable" class="sortable">
<thead>
<tr>
<th>Display Name</th>
<th>Windows Services Name</th>
<th>State</th>
</tr>
</thead>
<tbody>
{% if hostvars[windows_host]['services'] is defined %}
{% for service in hostvars[windows_host]['services'] %}
<tr>
<td>{{service['name']}}</td>
<td>{{service['win_svc_name']}}</td>
<td>{{service['state']}}</td>
</tr>
{% endfor %}
{% endif %}
</tbody>
</table>
</div>
</div>
</div>
<! END INTERNAL TABLE FOR SERVICES --!>

View File

@@ -0,0 +1 @@
file_path: /var/www/html

View File

@@ -0,0 +1,36 @@
build_report_windows_patch
========
Installs Apache and creates a report based on facts from Windows update job
Requirements
------------
Must run on Apache server
Role Variables / Configuration
--------------
N/A
Dependencies
------------
N/A
Example Playbook
----------------
The role can be used to create an html patching report on any number of Linux hosts using any number of Windows servers
```
---
- hosts: all
tasks:
- name: Run Windows Patch Report
import_role:
name: shadowman.reports.build_report_windows_patch
```

View File

@@ -0,0 +1,3 @@
EMAIL_FROM: tower@shadowman.dev
to_emails: alex@shadowman.dev,tower@shadowman.dev
EMAIL_TO: "{{ to_emails.split(',') }}"

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

View File

@@ -0,0 +1,111 @@
p.hostname {
color: #000000;
font-weight: bolder;
font-size: large;
}
#subtable {
background: #ebebeb;
margin: 0px;
}
#subtable tbody tr td {
padding: 5px 5px 5px 5px;
}
#subtable thead th {
padding: 5px;
}
* {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
font-family: "Open Sans", "Helvetica";
}
a {
color: #ffffff;
}
p {
color: #ffffff;
}
h1 {
text-align: center;
color: #ffffff;
}
body {
background:#353a40;
}
table {
border-collapse: separate;
background:#fff;
@include border-radius(5px);
margin:50px auto;
@include box-shadow(0px 0px 5px rgba(0,0,0,0.3));
}
thead {
@include border-radius(5px);
}
thead th {
font-family: 'Patua One', monospace;
font-size:16px;
font-weight:400;
color:#fff;
@include text-shadow(1px 1px 0px rgba(0,0,0,0.5));
text-align:left;
padding:20px;
border-top:1px solid #858d99;
background: #353a40;
&:first-child {
@include border-top-left-radius(5px);
}
&:last-child {
@include border-top-right-radius(5px);
}
}
tbody tr td {
font-family: 'Open Sans', sans-serif;
font-weight:400;
color:#5f6062;
font-size:13px;
padding:20px 20px 20px 20px;
border-bottom:1px solid #e0e0e0;
}
tbody tr:nth-child(2n) {
background:#f0f3f5;
}
tbody tr:last-child td {
border-bottom:none;
&:first-child {
@include border-bottom-left-radius(5px);
}
&:last-child {
@include border-bottom-right-radius(5px);
}
}
span.highlight {
background-color: yellow;
}
.expandclass {
color: #5f6062;
}
.content{
display:none;
margin: 10px;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View File

@@ -0,0 +1,38 @@
- name: Create HTML report
ansible.builtin.template:
src: report.j2
dest: "{{ file_path }}/windowspatch.html"
check_mode: no
- name: Copy CSS over
ansible.builtin.copy:
src: "css"
dest: "{{ file_path }}"
directory_mode: true
check_mode: no
- name: Copy logo over
ansible.builtin.copy:
src: "webpage_logo.png"
dest: "{{ file_path }}"
directory_mode: true
check_mode: no
- name: Display link to Patch report
ansible.builtin.debug:
msg: "Please go to http://{{ ansible_host }}/windowspatch.html"
- name: Send Report via E-mail
community.general.mail:
host: "{{ EMAIL_HOST }}"
username: "{{ EMAIL_USERNAME }}"
password: "{{ EMAIL_PASSWORD }}"
port: "{{ EMAIL_PORT }}"
subject: "Windows Patching Report"
body: "{{ lookup('template', 'report.j2') }}"
from: "{{ EMAIL_FROM }}"
to: "{{ EMAIL_TO }}"
subtype: html
delegate_to: localhost
become: false
check_mode: no

View File

@@ -0,0 +1,115 @@
<!DOCTYPE html>
<html>
<head>
<title> Windows Patch Report </title>
</head>
<body>
<center>
<h1>Ansible Windows Patching Report</h1>
<style>
@media print {
.noprint {
display: none !important;
}
}
</style>
<div class="noprint">
<button type="button" onclick="tableToCSV()">Download CSV</button>
<input type="button" value="Print" onClick="window.print()">
</div>
</center>
<table border = "1" cellpadding = "5" cellspacing = "5">
<thead>
<tr>
<th>Hostname</th>
<th>Operating System</th>
<th>Required Updates</th>
</tr>
</thead>
<tbody>
{% for windows_host in groups['tag_Windows']|sort %}
<tr>
<td>{{hostvars[windows_host]['inventory_hostname']}}</td>
<td>{{hostvars[windows_host]['ansible_distribution']|default("none")}}</td>
<td>
<ul>
{% if hostvars[windows_host].patchresult.updates is defined and hostvars[windows_host].patchresult.found_update_count|int > 0 %}
{% for update in hostvars[windows_host].patchresult.updates %}
{% set updatenum = hostvars[windows_host].patchresult.updates[update] %}
<li>{{updatenum.title}}</li>
{% endfor %}
{% else %}
<li>Compliant</li>
{% endif %}
</ul>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% for host in ansible_play_hosts %}
<center><p>Created with Ansible on {{hostvars[host].ansible_date_time.iso8601}}</p></center>
{% endfor %}
<script type="text/javascript">
function tableToCSV() {
// Variable to store the final csv data
var csv_data = [];
// Get each row data
var rows = document.getElementsByTagName('tr');
for (var i = 0; i < rows.length; i++) {
// Get each column data
var cols = rows[i].querySelectorAll('td,th');
// Stores each csv row data
var csvrow = [];
for (var j = 0; j < (cols.length); j++) {
// Get the text data of each cell of
// a row and push it to csvrow
if ( j == cols.length-1 && i==0){}
else{
csvrow.push(cols[j].textContent.replace(/,/g, " "));
}
}
csv_data.push(csvrow.join(","));
}
// combine each row data with new line character
csv_data = csv_data.join('\n');
// Call this function to download csv file
downloadCSVFile(csv_data);
}
function downloadCSVFile(csv_data) {
// Create CSV file object and feed our
// csv_data into it
CSVFile = new Blob([csv_data], { type: "text/csv" });
// Create to temporary link to initiate
// download process
var temp_link = document.createElement('a');
var todayDate = new Date().toISOString().slice(0, 10);
// Download csv file
temp_link.download = "windowspatching-" + todayDate + ".csv";
var url = window.URL.createObjectURL(CSVFile);
temp_link.href = url;
// This link should not be displayed
temp_link.style.display = "none";
document.body.appendChild(temp_link);
// Automatically click the link to trigger download
temp_link.click();
document.body.removeChild(temp_link);
}
</script>
</body>
</html>

View File

@@ -0,0 +1 @@
file_path: /var/www/html

View File

@@ -0,0 +1,3 @@
---
exclude_packages: []
allow_reboot: true

View File

@@ -0,0 +1,38 @@
---
- name: Scan packages
demo.patching.scan_packages:
os_family: "{{ ansible_os_family }}"
check_mode: no
- name: Scan services
demo.patching.scan_services:
check_mode: no
- name: upgrade packages (yum)
yum:
name: '*'
state: latest
exclude: "{{ exclude_packages }}"
when: ansible_pkg_mgr == "yum"
register: patchingresult_yum
- name: upgrade packages (dnf)
ansible.builtin.dnf:
name: '*'
state: latest
exclude: "{{ exclude_packages }}"
when: ansible_pkg_mgr == "dnf"
register: patchingresult_dnf
- name: Check to see if we need a reboot
ansible.builtin.command: needs-restarting -r
register: result
changed_when: result.rc == 1
failed_when: result.rc > 1
check_mode: no
- name: Reboot Server if Necessary
ansible.builtin.reboot:
when:
- result.rc == 1
- allow_reboot == true

View File

@@ -0,0 +1,36 @@
build_report_linux
========
Installs Apache and creates a report based on facts from Linux services and packages modules
Requirements
------------
Must run on Apache server
Role Variables / Configuration
--------------
N/A
Dependencies
------------
N/A
Example Playbook
----------------
The role can be used to create an html report on any number of Linux hosts using any number of Linux servers about their services and packages installed
```
---
- hosts: all
tasks:
- name: Run Linux Report
import_role:
name: shadowman.reports.build_report_linux
```

View File

@@ -0,0 +1,2 @@
---
detailedreport: True

View File

@@ -0,0 +1,202 @@
p.hostname {
color: #000000;
font-weight: bolder;
font-size: large;
margin: auto;
width: 50%;
}
#subtable {
background: #ebebeb;
margin: 0px;
width: 100%;
}
#subtable tbody tr td {
padding: 5px 5px 5px 5px;
}
#subtable thead th {
padding: 5px;
}
* {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
font-family: "Open Sans", "Helvetica";
}
a {
color: #ffffff;
}
p {
color: #ffffff;
}
h1 {
text-align: center;
color: #ffffff;
}
body {
background:#353a40;
padding: 0px;
margin: 0px;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
table {
border-collapse: separate;
background:#fff;
@include border-radius(5px);
@include box-shadow(0px 0px 5px rgba(0,0,0,0.3));
}
.main_net_table {
margin:50px auto;
}
thead {
@include border-radius(5px);
}
thead th {
font-size:16px;
font-weight:400;
color:#fff;
@include text-shadow(1px 1px 0px rgba(0,0,0,0.5));
text-align:left;
padding:20px;
border-top:1px solid #858d99;
background: #353a40;
&:first-child {
@include border-top-left-radius(5px);
}
&:last-child {
@include border-top-right-radius(5px);
}
}
tbody tr td {
font-weight:400;
color:#5f6062;
font-size:13px;
padding:20px 20px 20px 20px;
border-bottom:1px solid #e0e0e0;
}
tbody tr:nth-child(2n) {
background:#f0f3f5;
}
tbody tr:last-child td {
border-bottom:none;
&:first-child {
@include border-bottom-left-radius(5px);
}
&:last-child {
@include border-bottom-right-radius(5px);
}
}
td {
vertical-align: top;
}
span.highlight {
background-color: yellow;
}
.expandclass {
color: #5f6062;
}
.content{
display:none;
margin: 10px;
}
header {
width: 100%;
position: initial;
float: initial;
padding: 0;
margin: 0;
border-radius: 0;
height: 88px;
background-color: #171717;
}
.header-container {
margin: 0 auto;
width: 100%;
height: 100%;
max-width: 1170px;
padding: 0;
float: initial;
display: flex;
align-items: center;
}
.header-logo {
width: 137px;
border: 0;
margin: 0;
margin-left: 15px;
}
.header-link {
margin-left: 40px;
text-decoration: none;
cursor: pointer;
text-transform: uppercase;
font-size: 15px;
font-family: 'Red Hat Text';
font-weight: 500;
}
.header-link:hover {
text-shadow: 0 0 0.02px white;
text-decoration: none;
}
table.net_info td {
padding: 5px;
}
p.expandclass:hover {
text-decoration: underline;
color: #EE0000;
cursor: pointer;
}
.summary_info {
}
.ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active, a.ui-button:active, .ui-button:active, .ui-button.ui-state-active:hover {
border: 1px solid #5F0000;
background: #EE0000;
}
div#net_content {
padding: 0px;
height: auto !important;
}
img.router_image {
vertical-align: middle;
padding: 0px 10px 10px 10px;
width: 50px;
}
table.net_info {
width: 100%;
}
p.internal_label {
color: #000000;
}

View File

@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Logos" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="930.2px" height="350px" viewBox="0 0 930.2 350" style="enable-background:new 0 0 930.2 350;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
.st1{fill:#EE0000;}
</style>
<title>Logo-Red_Hat-Ansible_Automation_Platform-A-Reverse-RGB</title>
<path class="st0" d="M383.3,228.5h18.8L446,335.7h-17.5l-12.4-31.4h-48l-12.6,31.4h-16.7L383.3,228.5z M410.9,291l-18.7-47l-18.7,47
H410.9z"/>
<path class="st0" d="M455.2,257.7h15.3v7.8c6.2-6.2,14.7-9.6,23.5-9.3c17.9,0,30.5,12.4,30.5,30.5v49h-15.3v-46.5
c0-12.3-7.5-19.8-19.3-19.8c-7.8-0.3-15.1,3.6-19.3,10.1v56.1h-15.3V257.7z"/>
<path class="st0" d="M543,315.5c8.1,6.4,16.7,9.8,25.4,9.8c11,0,18.7-4.8,18.7-11.7c0-5.5-4-8.7-12.6-10l-14.1-2
c-15.5-2.3-23.3-9.5-23.3-21.6c0-14.1,12.3-23.6,30.5-23.6c11.3-0.1,22.3,3.4,31.5,9.9l-7.8,10.1c-8.6-5.7-16.4-8.1-24.7-8.1
c-9.3,0-15.6,4.3-15.6,10.6c0,5.7,3.7,8.4,12.9,9.8l14.1,2c15.5,2.3,23.6,9.7,23.6,21.7c0,14-14.1,24.5-32.6,24.5
c-13.5,0-25.6-4-34.2-11.5L543,315.5z"/>
<path class="st0" d="M611.6,235.6c0-5.2,4.1-9.4,9.3-9.5c0,0,0,0,0,0c5.2-0.2,9.7,3.9,9.9,9.1c0.2,5.2-3.9,9.7-9.1,9.9
c-0.2,0-0.5,0-0.7,0C615.8,245.1,611.6,240.9,611.6,235.6C611.6,235.7,611.6,235.7,611.6,235.6z M628.6,335.7h-15.3v-78h15.3V335.7z
"/>
<path class="st0" d="M685.5,336.9c-8.5,0-16.8-2.7-23.6-7.8v6.6h-15.2V228.5l15.3-3.4v40c6.6-5.6,15.1-8.7,23.7-8.6
c22.1,0,39.4,17.7,39.4,40.1C725.2,319.1,707.9,336.9,685.5,336.9z M662,279.2v35.2c4.9,5.7,13,9.2,21.8,9.2
c15,0,26.4-11.5,26.4-26.8c0-15.3-11.5-27-26.4-27C674.9,269.8,667.1,273.2,662,279.2z"/>
<path class="st0" d="M755,335.7h-15.3V228.5l15.3-3.4V335.7z"/>
<path class="st0" d="M810.5,337.1c-23,0-40.9-17.7-40.9-40.4c0-22.5,17.2-40.1,39.1-40.1c21.5,0,37.7,17.8,37.7,40.8v4.4h-61.6
c2,13,13.2,22.5,26.4,22.4c7.2,0.2,14.2-2.3,19.8-6.8l9.8,9.7C832.1,333.7,821.5,337.4,810.5,337.1z M784.9,290.2h46.3
c-2.3-11.9-11.5-20.8-22.8-20.8C796.5,269.4,787.2,277.8,784.9,290.2z"/>
<path class="st1" d="M202.8,137.5c18.4,0,45.1-3.8,45.1-25.7c0.1-1.7-0.1-3.4-0.5-5l-11-47.7c-2.5-10.5-4.8-15.2-23.2-24.5
c-14.3-7.3-45.5-19.4-54.7-19.4c-8.6,0-11.1,11.1-21.3,11.1c-9.8,0-17.1-8.3-26.4-8.3c-8.8,0-14.6,6-19,18.4c0,0-12.4,34.9-14,40
c-0.3,0.9-0.4,1.9-0.4,2.9C77.6,92.9,131.1,137.5,202.8,137.5 M250.8,120.7c2.5,12.1,2.5,13.3,2.5,14.9c0,20.6-23.2,32.1-53.7,32.1
c-69,0-129.3-40.3-129.3-67c0-3.7,0.8-7.4,2.2-10.8c-24.8,1.3-56.9,5.7-56.9,34c0,46.4,109.9,103.5,196.9,103.5
c66.7,0,83.5-30.2,83.5-54C296.1,154.6,279.9,133.4,250.8,120.7"/>
<path d="M250.7,120.7c2.5,12.1,2.5,13.3,2.5,14.9c0,20.6-23.2,32.1-53.7,32.1c-69,0-129.3-40.3-129.3-67c0-3.7,0.8-7.4,2.2-10.8
l5.4-13.3c-0.3,0.9-0.4,1.9-0.4,2.8c0,13.6,53.5,58.1,125.2,58.1c18.4,0,45.1-3.8,45.1-25.7c0.1-1.7-0.1-3.4-0.5-5L250.7,120.7z"/>
<path class="st0" d="M869.1,151.2c0,17.5,10.5,26,29.7,26c5.9-0.1,11.8-1,17.5-2.5v-20.3c-3.7,1.2-7.5,1.7-11.3,1.7
c-7.9,0-10.8-2.5-10.8-9.9v-31.1h22.9V94.2h-22.9V67.7l-25,5.4v21.1h-16.6v20.9h16.6L869.1,151.2z M791,151.7
c0-5.4,5.4-8.1,13.6-8.1c5,0,10,0.7,14.9,1.9V156c-4.8,2.6-10.2,3.9-15.6,3.9C795.9,159.9,791.1,156.8,791,151.7 M798.7,177.5
c8.8,0,16-1.9,22.6-6.3v5h24.8v-52.5c0-20-13.5-30.9-35.9-30.9c-12.6,0-25,2.9-38.3,9l9,18.4c9.6-4,17.7-6.5,24.8-6.5
c10.3,0,15.6,4,15.6,12.2v4c-6.1-1.6-12.3-2.4-18.6-2.3c-21.1,0-33.8,8.8-33.8,24.6C768.9,166.6,780.4,177.6,798.7,177.5
M662.5,176.2h26.7v-42.5h44.6v42.5h26.7V67.7h-26.6v41.7h-44.6V67.7h-26.7L662.5,176.2z M561,135.1c0-11.8,9.3-20.8,21.5-20.8
c6.4-0.1,12.6,2.1,17.4,6.4v28.6c-4.7,4.4-10.9,6.7-17.4,6.5C570.5,155.8,561,146.8,561,135.1 M600.2,176.1H625V62.3l-25,5.4v30.8
c-6.4-3.6-13.6-5.5-20.9-5.4c-23.9,0-42.6,18.4-42.6,42c-0.3,23,18.1,41.9,41.1,42.2c0.2,0,0.5,0,0.7,0c7.9,0,15.6-2.5,22-7.1V176.1
z M486.5,113.2c7.9,0,14.6,5.1,17.2,13h-34.2C471.9,118,478.2,113.2,486.5,113.2 M444.2,135.2c0,23.9,19.5,42.5,44.6,42.5
c13.8,0,23.9-3.7,34.3-12.4l-16.6-14.7c-3.9,4-9.6,6.2-16.4,6.2c-8.8,0.2-16.8-4.9-20.2-13h58.4v-6.2c0-26-17.5-44.8-41.4-44.8
c-23.2-0.4-42.4,18.2-42.7,41.5C444.2,134.6,444.2,134.9,444.2,135.2 M400.9,90.5c8.8,0,13.8,5.6,13.8,12.2s-5,12.2-13.8,12.2h-26.3
V90.5H400.9z M347.9,176.2h26.7v-39.5h20.3l20.5,39.5h29.7l-23.9-43.4c12.4-5,20.5-17.1,20.4-30.5c0-19.5-15.3-34.5-38.3-34.5H348
L347.9,176.2z"/>
</svg>

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

View File

@@ -0,0 +1,27 @@
- name: create HTML report
ansible.builtin.template:
src: report.j2
dest: "{{ file_path }}/linux.html"
check_mode: no
- name: copy CSS over
ansible.builtin.copy:
src: "css"
dest: "{{ file_path }}"
directory_mode: true
check_mode: no
- name: copy logos over
ansible.builtin.copy:
src: "{{ item }}"
dest: "{{ file_path }}"
directory_mode: true
loop:
- "webpage_logo.png"
- "redhat-ansible-logo.svg"
- "server.png"
check_mode: no
- name: display link to inventory report
ansible.builtin.debug:
msg: "Please go to http://{{ ansible_host }}/reports/linux.html"

View File

@@ -0,0 +1,15 @@
<div class="wrapper">
<header>
<div class="header-container">
<a href="https://ansible.com">
<img
class="header-logo"
src="redhat-ansible-logo.svg"
title="Red Hat Ansible"
alt="Red Hat Ansible"
/>
</a>
</div>
</header>

View File

@@ -0,0 +1,31 @@
<! INTERNAL TABLE FOR PACKAGES --!>
<div id="accordion">
<div class="ui-accordion ui-widget ui-helper-reset" role="tablist">
<h3 class="ui-accordion-header ui-corner-top ui-state-default ui-accordion-icons ui-accordion-header-collapsed ui-corner-all" role="tab" id="ui-id-3" aria-controls="ui-id-4" aria-selected="false" aria-expanded="false" tabindex="0">Package Facts</h3>
<div class="net_content ui-accordion-content ui-corner-bottom ui-helper-reset ui-widget-content" id="ui-id-4" aria-labelledby="ui-id-3" role="tabpanel" aria-hidden="true" style="display: none; height: 194px;">
<table id="subtable" class="sortable">
<thead>
<tr>
<th>Package Name</th>
<th>source</th>
<th>release</th>
<th>version</th>
</tr>
</thead>
<tbody>
{% if hostvars[linux_host]['packages'] is defined %}
{% for package in hostvars[linux_host]['packages'] %}
<tr>
<td>{{package['name']}}</td>
<td>{{package['source']}}</td>
<td>{{package['release']}}</td>
<td>{{package['version']}}</td>
</tr>
{% endfor %}
{% endif %}
</tbody>
</table>
</div>
</div>
</div>
<! END INTERNAL TABLE FOR PACKAGES --!>

View File

@@ -0,0 +1,105 @@
<!DOCTYPE html>
<html>
<head>
<title> Ansible Linux Automation Report </title>
<link rel="stylesheet" type="text/css" href="//fonts.googleapis.com/css?family=Open+Sans" />
<link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
<link rel="stylesheet" href="css/new.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<script src="https://www.kryogenix.org/code/browser/sorttable/sorttable.js"></script>
<script>
$(function() {
$( "#accordion > div" ).accordion({
header: "h3",
active: false,
collapsible: true
});
});
</script>
<script>
(function(document) {
'use strict';
var TableFilter = (function(myArray) {
var search_input;
function _onInputSearch(e) {
search_input = e.target;
var tables = document.getElementsByClassName(search_input.getAttribute('data-table'));
myArray.forEach.call(tables, function(table) {
myArray.forEach.call(table.tBodies, function(tbody) {
myArray.forEach.call(tbody.rows, function(row) {
var text_content = row.textContent.toLowerCase();
var search_val = search_input.value.toLowerCase();
row.style.display = text_content.indexOf(search_val) > -1 ? '' : 'none';
});
});
});
}
return {
init: function() {
var inputs = document.getElementsByClassName('search-input');
myArray.forEach.call(inputs, function(input) {
input.oninput = _onInputSearch;
});
}
};
})(Array.prototype);
document.addEventListener('readystatechange', function() {
if (document.readyState === 'complete') {
TableFilter.init();
}
});
})(document);
</script>
</head>
<body>
<div class="wrapper">
{% include 'header.j2' %}
<section>
<center>
<h1>Ansible Linux Automation Report</h1>
<h3><input type="search" placeholder="Search..." class="form-control search-input" data-table="main_net_table"/>
</center>
<table class="table table-striped mt32 main_net_table">
<thead>
<tr>
<th>Linux Device</th>
<th>Package Manager</th>
<th>Operating System</th>
<th>Operating System Version</th>
<th>Operating System Kernel Version</th>
</tr>
</thead>
<tbody>
{% for linux_host in ansible_play_hosts |sort %}
<tr>
<td class="summary_info">
<div id="hostname">
<p class="hostname">
<img class="router_image" src="server.png"> {{ hostvars[linux_host]['inventory_hostname'].split('.')[0] }}</p>
</div>
{% if detailedreport == 'True' %}
{% include 'packages.j2' %}
{% include 'services.j2' %}
{% endif %}
</td>
<td>{{hostvars[linux_host]['ansible_pkg_mgr']|default("none")}}</td>
<td>{{hostvars[linux_host]['ansible_os_family']|default("none")}}</td>
<td>{{hostvars[linux_host]['ansible_distribution_version']|default("none")}}</td>
<td>{{hostvars[linux_host]['ansible_kernel']|default("none")}}</td>
</tr>
{% endfor %}
</tbody>
</table>
<center><p>Created with</p><br><img src="webpage_logo.png" width="300">
</center>
</section>
</div>
</body>
</html>

View File

@@ -0,0 +1,30 @@
<! INTERNAL TABLE FOR SERVICES --!>
<div id="accordion">
<div class="ui-accordion ui-widget ui-helper-reset" role="tablist">
<h3 class="ui-accordion-header ui-corner-top ui-state-default ui-accordion-icons ui-accordion-header-collapsed ui-corner-all" role="tab" id="ui-id-3" aria-controls="ui-id-4" aria-selected="false" aria-expanded="false" tabindex="0">Services Facts</h3>
<div class="net_content ui-accordion-content ui-corner-bottom ui-helper-reset ui-widget-content" id="ui-id-4" aria-labelledby="ui-id-3" role="tabpanel" aria-hidden="true" style="display: none; height: 194px;">
<table id="subtable" class="sortable">
<thead>
<tr>
<th>Service Name</th>
<th>State</th>
<th>Source</th>
</tr>
</thead>
<tbody>
{% if hostvars[linux_host]['services'] is defined %}
{% for servicesname in hostvars[linux_host]['services']|sort %}
{% set service = hostvars[linux_host]['services'][servicesname] %}
<tr>
<td>{{service['name']}}</td>
<td>{{service['state']}}</td>
<td>{{service['source']}}</td>
</tr>
{% endfor %}
{% endif %}
</tbody>
</table>
</div>
</div>
</div>
<! END INTERNAL TABLE FOR SERVICES --!>

View File

@@ -0,0 +1 @@
file_path: /var/www/html/reports

View File

@@ -0,0 +1,36 @@
build_report_linux_patch
========
Installs Apache and creates a report based on facts from Linux patching
Requirements
------------
Must run on Apache server
Role Variables / Configuration
--------------
N/A
Dependencies
------------
N/A
Example Playbook
----------------
The role can be used to create an html report on any number of Linux hosts using any number of Linux servers about their patching results(yum and dnf)
```
---
- hosts: all
tasks:
- name: Run Windows Report
import_role:
name: shadowman.reports.build_report_linux_patch
```

View File

@@ -0,0 +1,3 @@
EMAIL_FROM: tower@shadowman.dev
to_emails: alex@shadowman.dev,tower@shadowman.dev
EMAIL_TO: "{{ to_emails.split(',') }}"

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

View File

@@ -0,0 +1,111 @@
p.hostname {
color: #000000;
font-weight: bolder;
font-size: large;
}
#subtable {
background: #ebebeb;
margin: 0px;
}
#subtable tbody tr td {
padding: 5px 5px 5px 5px;
}
#subtable thead th {
padding: 5px;
}
* {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
font-family: "Open Sans", "Helvetica";
}
a {
color: #ffffff;
}
p {
color: #ffffff;
}
h1 {
text-align: center;
color: #ffffff;
}
body {
background:#353a40;
}
table {
border-collapse: separate;
background:#fff;
@include border-radius(5px);
margin:50px auto;
@include box-shadow(0px 0px 5px rgba(0,0,0,0.3));
}
thead {
@include border-radius(5px);
}
thead th {
font-family: 'Patua One', monospace;
font-size:16px;
font-weight:400;
color:#fff;
@include text-shadow(1px 1px 0px rgba(0,0,0,0.5));
text-align:left;
padding:20px;
border-top:1px solid #858d99;
background: #353a40;
&:first-child {
@include border-top-left-radius(5px);
}
&:last-child {
@include border-top-right-radius(5px);
}
}
tbody tr td {
font-family: 'Open Sans', sans-serif;
font-weight:400;
color:#5f6062;
font-size:13px;
padding:20px 20px 20px 20px;
border-bottom:1px solid #e0e0e0;
}
tbody tr:nth-child(2n) {
background:#f0f3f5;
}
tbody tr:last-child td {
border-bottom:none;
&:first-child {
@include border-bottom-left-radius(5px);
}
&:last-child {
@include border-bottom-right-radius(5px);
}
}
span.highlight {
background-color: yellow;
}
.expandclass {
color: #5f6062;
}
.content{
display:none;
margin: 10px;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View File

@@ -0,0 +1,38 @@
- name: Create HTML report
ansible.builtin.template:
src: report.j2
dest: "{{ file_path }}/linuxpatch.html"
check_mode: no
- name: Copy CSS over
ansible.builtin.copy:
src: "css"
dest: "{{ file_path }}"
directory_mode: true
check_mode: no
- name: Copy logo over
ansible.builtin.copy:
src: "webpage_logo.png"
dest: "{{ file_path }}"
directory_mode: true
check_mode: no
- name: Display link to Linux patch report
ansible.builtin.debug:
msg: "Please go to http://{{ ansible_host }}/reports/linuxpatch.html"
#- name: Send Report via E-mail
# community.general.mail:
# host: "{{ EMAIL_HOST }}"
# username: "{{ EMAIL_USERNAME }}"
# password: "{{ EMAIL_PASSWORD }}"
# port: "{{ EMAIL_PORT }}"
# subject: "Linux Patching Report"
# body: "{{ lookup('template', 'report.j2') }}"
# from: "{{ EMAIL_FROM }}"
# to: "{{ EMAIL_TO }}"
# subtype: html
# delegate_to: localhost
# become: false
# check_mode: no

View File

@@ -0,0 +1,120 @@
<!DOCTYPE html>
<html>
<head>
<title> Linux Patch Report </title>
</head>
<body>
<center>
<h1>Ansible Linux Patching Report</h1>
<style>
@media print {
.noprint {
display: none !important;
}
}
</style>
<div class="noprint">
<button type="button" onclick="tableToCSV()">Download CSV</button>
<input type="button" value="Print" onClick="window.print()">
</div>
</center>
<table border = "1" cellpadding = "5" cellspacing = "5">
<thead>
<tr>
<th>Hostname</th>
<th>Operating System</th>
<th>Operating System Version</th>
<th>Required Updates</th>
</tr>
</thead>
<tbody>
{% for linux_host in ansible_play_hosts |sort %}
<tr>
<td>{{hostvars[linux_host]['inventory_hostname']}}</td>
<td>{{hostvars[linux_host]['ansible_os_family']|default("none")}}</td>
<td>{{hostvars[linux_host]['ansible_distribution_version']|default("none")}}</td>
<td>
<ul>
{% if hostvars[linux_host].patchingresult_yum.changed|default("false",true) == true %}
{% for packagename in hostvars[linux_host].patchingresult_yum.changes.updated|sort %}
<li> {{ packagename[0] }} - {{ packagename[1] }} </li>
{% endfor %}
{% elif hostvars[linux_host].patchingresult_dnf.changed|default("false",true) == true %}
{% for packagename in hostvars[linux_host].patchingresult_dnf.results|sort %}
<li> {{ packagename }} </li>
{% endfor %}
{% elif hostvars[linux_host].patchingresult_dnf.changed is undefined %}
<li> Patching Failed </li>
{% elif hostvars[linux_host].patchingresult_yum.changed is undefined %}
<li> Patching Failed </li>
{% else %}
<li> Compliant </li>
{% endif %}
</ul>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<center><p>Created with Ansible on {{hostvars[inventory_hostname].ansible_date_time.iso8601}}</p></center>
<script type="text/javascript">
function tableToCSV() {
// Variable to store the final csv data
var csv_data = [];
// Get each row data
var rows = document.getElementsByTagName('tr');
for (var i = 0; i < rows.length; i++) {
// Get each column data
var cols = rows[i].querySelectorAll('td,th');
// Stores each csv row data
var csvrow = [];
for (var j = 0; j < (cols.length); j++) {
// Get the text data of each cell of
// a row and push it to csvrow
if ( j == cols.length-1 && i==0){}
else{
csvrow.push(cols[j].textContent.replace(/,/g, " "));
}
}
csv_data.push(csvrow.join(","));
}
// combine each row data with new line character
csv_data = csv_data.join('\n');
// Call this function to download csv file
downloadCSVFile(csv_data);
}
function downloadCSVFile(csv_data) {
// Create CSV file object and feed our
// csv_data into it
CSVFile = new Blob([csv_data], { type: "text/csv" });
// Create to temporary link to initiate
// download process
var temp_link = document.createElement('a');
var todayDate = new Date().toISOString().slice(0, 10);
// Download csv file
temp_link.download = "linuxpatching-" + todayDate + ".csv";
var url = window.URL.createObjectURL(CSVFile);
temp_link.href = url;
// This link should not be displayed
temp_link.style.display = "none";
document.body.appendChild(temp_link);
// Automatically click the link to trigger download
temp_link.click();
document.body.removeChild(temp_link);
}
</script>
</body>
</html>

View File

@@ -0,0 +1 @@
file_path: /var/www/html/reports

View File

@@ -1,20 +1,38 @@
---
- name: Apply Non-kernel Updates
hosts: "{{ HOSTS }}"
become: true
- hosts: "{{ HOSTS }}"
become: yes
vars:
report_server: node1
tasks:
- name: upgrade all packages except kernel
yum:
name: '*'
state: latest
exclude: kernel*
tags: all
- include_role:
name: demo.patching.patch_linux
- name: upgrade all packages security related except kernel
yum:
name: '*'
state: latest
security: true
exclude: kernel*
tags: security
- block:
- yum:
name: httpd
state: latest
check_mode: no
- file:
path: /var/www/html/reports/
state: directory
check_mode: no
- copy:
dest: /var/www/html/reports/.htaccess
content: Options +Indexes
check_mode: no
- service:
name: httpd
state: started
check_mode: no
- include_role:
name: demo.patching.report_linux
- include_role:
name: demo.patching.report_linux_patching
delegate_to: "{{ report_server }}"
run_once: yes

38
linux/patching_report.yml Normal file
View File

@@ -0,0 +1,38 @@
---
- hosts: "{{ HOSTS }}"
become: yes
vars:
report_server: node1
tasks:
- include_role:
name: demo.patching.patch_linux
- block:
- yum:
name: httpd
state: latest
check_mode: no
- file:
path: /var/www/html/reports/
state: directory
check_mode: no
- copy:
dest: /var/www/html/reports/.htaccess
content: Options +Indexes
check_mode: no
- service:
name: httpd
state: started
check_mode: no
- include_role:
name: demo.patching.report_linux
- include_role:
name: demo.patching.report_linux_patching
delegate_to: "{{ report_server }}"
run_once: yes

View File

@@ -134,6 +134,8 @@ controller_templates:
playbook: "linux/patching.yml"
execution_environment: Default execution environment
use_fact_cache: true
job_type: check
ask_job_type_on_launch: yes
credentials:
- "Workshop Credential"
survey_enabled: true