diff --git a/.gitignore b/.gitignore
index 22bd86f..73bcc10 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,3 +6,4 @@ choose_demo_example_azure.yml
choose_demo_example_aws.yml
.ansible.cfg
*.gz
+
diff --git a/ansible.cfg b/ansible.cfg
new file mode 100644
index 0000000..469af5f
--- /dev/null
+++ b/ansible.cfg
@@ -0,0 +1,2 @@
+[defaults]
+collections_paths=./collections
diff --git a/cloud/README.md b/cloud/README.md
new file mode 100644
index 0000000..699e28c
--- /dev/null
+++ b/cloud/README.md
@@ -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.
\ No newline at end of file
diff --git a/cloud/blueprints/windows.yml b/cloud/blueprints/windows.yml
index 7e4730d..2c000f5 100644
--- a/cloud/blueprints/windows.yml
+++ b/cloud/blueprints/windows.yml
@@ -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
\ No newline at end of file
diff --git a/cloud/destroy_vm.yml b/cloud/destroy_vm.yml
new file mode 100644
index 0000000..183b221
--- /dev/null
+++ b/cloud/destroy_vm.yml
@@ -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"
\ No newline at end of file
diff --git a/collections/ansible_collections/demo/cloud/README.md b/collections/ansible_collections/demo/cloud/README.md
new file mode 100644
index 0000000..e69de29
diff --git a/collections/ansible_collections/demo/cloud/roles/aws/defaults/main.yml b/collections/ansible_collections/demo/cloud/roles/aws/defaults/main.yml
new file mode 100644
index 0000000..997346e
--- /dev/null
+++ b/collections/ansible_collections/demo/cloud/roles/aws/defaults/main.yml
@@ -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
+
diff --git a/collections/ansible_collections/demo/cloud/roles/aws/tasks/create_infra.yml b/collections/ansible_collections/demo/cloud/roles/aws/tasks/create_infra.yml
new file mode 100644
index 0000000..510aed5
--- /dev/null
+++ b/collections/ansible_collections/demo/cloud/roles/aws/tasks/create_infra.yml
@@ -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 }}"
\ No newline at end of file
diff --git a/collections/ansible_collections/demo/cloud/roles/aws/tasks/create_vm.yml b/collections/ansible_collections/demo/cloud/roles/aws/tasks/create_vm.yml
new file mode 100644
index 0000000..3e03226
--- /dev/null
+++ b/collections/ansible_collections/demo/cloud/roles/aws/tasks/create_vm.yml
@@ -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
\ No newline at end of file
diff --git a/collections/ansible_collections/demo/cloud/roles/aws/tasks/destroy_vm.yml b/collections/ansible_collections/demo/cloud/roles/aws/tasks/destroy_vm.yml
new file mode 100644
index 0000000..7b5093b
--- /dev/null
+++ b/collections/ansible_collections/demo/cloud/roles/aws/tasks/destroy_vm.yml
@@ -0,0 +1,7 @@
+---
+- name: Destroy VM
+ amazon.aws.ec2_instance:
+ state: absent
+ instance_ids: "{{ instance_id }}"
+ region: "{{ placement.region }}"
+ delegate_to: localhost
\ No newline at end of file
diff --git a/collections/ansible_collections/demo/cloud/roles/aws/templates/aws_windows_userdata.j2 b/collections/ansible_collections/demo/cloud/roles/aws/templates/aws_windows_userdata.j2
new file mode 100644
index 0000000..c4671f2
--- /dev/null
+++ b/collections/ansible_collections/demo/cloud/roles/aws/templates/aws_windows_userdata.j2
@@ -0,0 +1,29 @@
+
+# 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
+
\ No newline at end of file
diff --git a/collections/ansible_collections/demo/cloud/roles/aws/templates/default.j2 b/collections/ansible_collections/demo/cloud/roles/aws/templates/default.j2
new file mode 100644
index 0000000..e69de29
diff --git a/collections/ansible_collections/demo/cloud/roles/azure/defaults/main.yml b/collections/ansible_collections/demo/cloud/roles/azure/defaults/main.yml
new file mode 100644
index 0000000..e38e0e3
--- /dev/null
+++ b/collections/ansible_collections/demo/cloud/roles/azure/defaults/main.yml
@@ -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
\ No newline at end of file
diff --git a/collections/ansible_collections/demo/cloud/roles/azure/tasks/create_infra.yml b/collections/ansible_collections/demo/cloud/roles/azure/tasks/create_infra.yml
new file mode 100644
index 0000000..cc655e3
--- /dev/null
+++ b/collections/ansible_collections/demo/cloud/roles/azure/tasks/create_infra.yml
@@ -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 }}"
\ No newline at end of file
diff --git a/collections/ansible_collections/demo/cloud/roles/azure/tasks/create_vm.yml b/collections/ansible_collections/demo/cloud/roles/azure/tasks/create_vm.yml
new file mode 100644
index 0000000..e69de29
diff --git a/collections/ansible_collections/demo/patching/plugins/modules/scan_packages.py b/collections/ansible_collections/demo/patching/plugins/modules/scan_packages.py
new file mode 100644
index 0000000..039e5b2
--- /dev/null
+++ b/collections/ansible_collections/demo/patching/plugins/modules/scan_packages.py
@@ -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()
\ No newline at end of file
diff --git a/collections/ansible_collections/demo/patching/plugins/modules/scan_services.py b/collections/ansible_collections/demo/patching/plugins/modules/scan_services.py
new file mode 100644
index 0000000..c4de33c
--- /dev/null
+++ b/collections/ansible_collections/demo/patching/plugins/modules/scan_services.py
@@ -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.*)\s(?P\w+)\/(?P\w+)(\,\sprocess\s(?P[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.*?)\s+[0-9]:(?Pon|off)\s+[0-9]:(?Pon|off)\s+[0-9]:(?Pon|off)\s+'
+ '[0-9]:(?Pon|off)\s+[0-9]:(?Pon|off)\s+[0-9]:(?Pon|off)\s+[0-9]:(?Pon|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.*?)\s+(?Pon|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()
\ No newline at end of file
diff --git a/collections/ansible_collections/demo/patching/plugins/modules/win_scan_packages.ps1 b/collections/ansible_collections/demo/patching/plugins/modules/win_scan_packages.ps1
new file mode 100644
index 0000000..0062f31
--- /dev/null
+++ b/collections/ansible_collections/demo/patching/plugins/modules/win_scan_packages.ps1
@@ -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 .
+
+# 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;
\ No newline at end of file
diff --git a/collections/ansible_collections/demo/patching/plugins/modules/win_scan_packages.py b/collections/ansible_collections/demo/patching/plugins/modules/win_scan_packages.py
new file mode 100644
index 0000000..3281818
--- /dev/null
+++ b/collections/ansible_collections/demo/patching/plugins/modules/win_scan_packages.py
@@ -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"
+ },
+# }
+'''
\ No newline at end of file
diff --git a/collections/ansible_collections/demo/patching/plugins/modules/win_scan_services.ps1 b/collections/ansible_collections/demo/patching/plugins/modules/win_scan_services.ps1
new file mode 100644
index 0000000..4a5f840
--- /dev/null
+++ b/collections/ansible_collections/demo/patching/plugins/modules/win_scan_services.ps1
@@ -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 .
+
+# 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;
\ No newline at end of file
diff --git a/collections/ansible_collections/demo/patching/plugins/modules/win_scan_services.py b/collections/ansible_collections/demo/patching/plugins/modules/win_scan_services.py
new file mode 100644
index 0000000..caae37d
--- /dev/null
+++ b/collections/ansible_collections/demo/patching/plugins/modules/win_scan_services.py
@@ -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"
+ },
+# }
+'''
\ No newline at end of file
diff --git a/collections/ansible_collections/demo/patching/roles/build_report_network/README.md b/collections/ansible_collections/demo/patching/roles/build_report_network/README.md
new file mode 100644
index 0000000..a8b311a
--- /dev/null
+++ b/collections/ansible_collections/demo/patching/roles/build_report_network/README.md
@@ -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
+
+```
\ No newline at end of file
diff --git a/collections/ansible_collections/demo/patching/roles/build_report_network/example_results/Ansible Network Automation Report.png b/collections/ansible_collections/demo/patching/roles/build_report_network/example_results/Ansible Network Automation Report.png
new file mode 100644
index 0000000..d766491
Binary files /dev/null and b/collections/ansible_collections/demo/patching/roles/build_report_network/example_results/Ansible Network Automation Report.png differ
diff --git a/collections/ansible_collections/demo/patching/roles/build_report_network/files/css/new.css b/collections/ansible_collections/demo/patching/roles/build_report_network/files/css/new.css
new file mode 100644
index 0000000..f58d18f
--- /dev/null
+++ b/collections/ansible_collections/demo/patching/roles/build_report_network/files/css/new.css
@@ -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;
+}
\ No newline at end of file
diff --git a/collections/ansible_collections/demo/patching/roles/build_report_network/files/redhat-ansible-logo.svg b/collections/ansible_collections/demo/patching/roles/build_report_network/files/redhat-ansible-logo.svg
new file mode 100644
index 0000000..2ecef98
--- /dev/null
+++ b/collections/ansible_collections/demo/patching/roles/build_report_network/files/redhat-ansible-logo.svg
@@ -0,0 +1,48 @@
+
+
+
diff --git a/collections/ansible_collections/demo/patching/roles/build_report_network/files/router.png b/collections/ansible_collections/demo/patching/roles/build_report_network/files/router.png
new file mode 100644
index 0000000..1fdc4f0
Binary files /dev/null and b/collections/ansible_collections/demo/patching/roles/build_report_network/files/router.png differ
diff --git a/collections/ansible_collections/demo/patching/roles/build_report_network/files/webpage_logo.png b/collections/ansible_collections/demo/patching/roles/build_report_network/files/webpage_logo.png
new file mode 100644
index 0000000..65b5836
Binary files /dev/null and b/collections/ansible_collections/demo/patching/roles/build_report_network/files/webpage_logo.png differ
diff --git a/collections/ansible_collections/demo/patching/roles/build_report_network/tasks/main.yml b/collections/ansible_collections/demo/patching/roles/build_report_network/tasks/main.yml
new file mode 100644
index 0000000..accaeba
--- /dev/null
+++ b/collections/ansible_collections/demo/patching/roles/build_report_network/tasks/main.yml
@@ -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"
\ No newline at end of file
diff --git a/collections/ansible_collections/demo/patching/roles/build_report_network/templates/bgp.j2 b/collections/ansible_collections/demo/patching/roles/build_report_network/templates/bgp.j2
new file mode 100644
index 0000000..d6b0f67
--- /dev/null
+++ b/collections/ansible_collections/demo/patching/roles/build_report_network/templates/bgp.j2
@@ -0,0 +1,31 @@
+
+
+
+
BGP Global Info
+
+{% if hostvars[network_switch]['ansible_network_resources']['bgp_global'] is defined and hostvars[network_switch]['ansible_network_resources']['bgp_global']|length > 0 %}
+
+
+
+
ASN
+
Router ID
+
+
+
+{% for bgpinfo in hostvars[network_switch]['ansible_network_resources']['bgp_global'] %}
+
+{% 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 %}
+
+
+
+
\ No newline at end of file
diff --git a/collections/ansible_collections/demo/patching/roles/build_report_network/templates/header.j2 b/collections/ansible_collections/demo/patching/roles/build_report_network/templates/header.j2
new file mode 100644
index 0000000..6d504d0
--- /dev/null
+++ b/collections/ansible_collections/demo/patching/roles/build_report_network/templates/header.j2
@@ -0,0 +1,15 @@
+
+
+
+
\ No newline at end of file
diff --git a/collections/ansible_collections/demo/patching/roles/build_report_network/templates/interfaces.j2 b/collections/ansible_collections/demo/patching/roles/build_report_network/templates/interfaces.j2
new file mode 100644
index 0000000..bf13414
--- /dev/null
+++ b/collections/ansible_collections/demo/patching/roles/build_report_network/templates/interfaces.j2
@@ -0,0 +1,41 @@
+
+
+
+
+ Interfaces - MTU/Duplex/Speed
+
+
+{% if hostvars[network_switch]['ansible_network_resources']['interfaces'] is defined and hostvars[network_switch]['ansible_network_resources']['interfaces']|length > 0 %}
+
+
+
+
Interface Name
+
Description
+
Duplex
+
Enabled
+
MTU
+
Speed
+
+
+
+{% for interface in hostvars[network_switch]['ansible_network_resources']['interfaces'] %}
+
+
{{interface['name']}}
+
{{interface['description']|default("none")}}
+
{{interface['duplex']|default("default")}}
+
{{interface['enabled']}}
+
{{interface['mtu']|default("default")}}
+
{{interface['speed']|default("default")}}
+
+{% endfor %}
+
+
+{% 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 %}
+
+
+
+
\ No newline at end of file
diff --git a/collections/ansible_collections/demo/patching/roles/build_report_network/templates/l2_interfaces.j2 b/collections/ansible_collections/demo/patching/roles/build_report_network/templates/l2_interfaces.j2
new file mode 100644
index 0000000..a170b5d
--- /dev/null
+++ b/collections/ansible_collections/demo/patching/roles/build_report_network/templates/l2_interfaces.j2
@@ -0,0 +1,37 @@
+
+
+
+
L2 Interfaces - Trunk/Access Ports
+
+{% if hostvars[network_switch]['ansible_network_resources']['l2_interfaces'] is defined and hostvars[network_switch]['ansible_network_resources']['l2_interfaces']|length > 0 %}
+
+
+
+
Name
+
Mode
+
Access VLAN
+
Trunk Native VLAN
+
Trunk Allowed VLANs
+
+
+
+{% for l2_interface in hostvars[network_switch]['ansible_network_resources']['l2_interfaces'] %}
+
+{% 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 %}
+
+
+
+
\ No newline at end of file
diff --git a/collections/ansible_collections/demo/patching/roles/build_report_network/templates/l3_interfaces.j2 b/collections/ansible_collections/demo/patching/roles/build_report_network/templates/l3_interfaces.j2
new file mode 100644
index 0000000..deb217c
--- /dev/null
+++ b/collections/ansible_collections/demo/patching/roles/build_report_network/templates/l3_interfaces.j2
@@ -0,0 +1,58 @@
+
+
+
+
L3 Interfaces - IP Addresses
+
+{% if hostvars[network_switch]['ansible_network_resources']['l3_interfaces'] is defined and hostvars[network_switch]['ansible_network_resources']['l3_interfaces']|length > 0 %}
+
+
+
+
Interface Name
+
IPv4
+
IPv6
+
+
+
+{% for interface in hostvars[network_switch]['ansible_network_resources']['l3_interfaces'] %}
+
+
{{interface['name']}}
+
+
+{% 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 %} {% endif %}
+{% endfor %}
+{% endif %}
+
+
+
+
+{% if interface.ipv6 is defined %}
+{% for v6address in interface.ipv6 %}
+{{v6address['address']}}
+{% if loop.length > 1 and not loop.last %} {% endif %}
+{% endfor %}
+{% endif %}
+
+
+
+{% endfor %}
+
+
+{% 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 %}
+
+
+
+
\ No newline at end of file
diff --git a/collections/ansible_collections/demo/patching/roles/build_report_network/templates/lacp.j2 b/collections/ansible_collections/demo/patching/roles/build_report_network/templates/lacp.j2
new file mode 100644
index 0000000..f53b2f7
--- /dev/null
+++ b/collections/ansible_collections/demo/patching/roles/build_report_network/templates/lacp.j2
@@ -0,0 +1,29 @@
+
+
+
+
LACP
+
+{% if hostvars[network_switch]['ansible_network_resources']['lacp'] is defined and hostvars[network_switch]['ansible_network_resources']['lacp'].keys()|length > 0 %}
+
+
+
+
System Priority
+
+
+
+
+ {% if hostvars[network_switch]['ansible_network_resources']['lacp']['system'] is defined %}
+
+{% 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 %}
+
+
+
+
\ No newline at end of file
diff --git a/collections/ansible_collections/demo/patching/roles/build_report_network/templates/lldp_interfaces.j2 b/collections/ansible_collections/demo/patching/roles/build_report_network/templates/lldp_interfaces.j2
new file mode 100644
index 0000000..3ad0852
--- /dev/null
+++ b/collections/ansible_collections/demo/patching/roles/build_report_network/templates/lldp_interfaces.j2
@@ -0,0 +1,33 @@
+
+
+
+
LLDP Interfaces
+
+{% if hostvars[network_switch]['ansible_network_resources']['lldp_interfaces'] is defined and hostvars[network_switch]['ansible_network_resources']['lldp_interfaces']|length > 0 %}
+
+
+
+
vlan_id
+
Name
+
state
+
+
+
+{% for interface in hostvars[network_switch]['ansible_network_resources']['lldp_interfaces'] %}
+
+{% 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 %}
+
+
+
+
\ No newline at end of file
diff --git a/collections/ansible_collections/demo/patching/roles/build_report_network/templates/ospf.j2 b/collections/ansible_collections/demo/patching/roles/build_report_network/templates/ospf.j2
new file mode 100644
index 0000000..d06ff5b
--- /dev/null
+++ b/collections/ansible_collections/demo/patching/roles/build_report_network/templates/ospf.j2
@@ -0,0 +1,29 @@
+
+
+
+
OSPF Global Info
+
+{% if hostvars[network_switch]['ansible_network_resources']['ospfv2'] is defined and hostvars[network_switch]['ansible_network_resources']['ospfv2']|length > 0 %}
+
+{% 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 %}
+
+
+
+
\ No newline at end of file
diff --git a/collections/ansible_collections/demo/patching/roles/build_report_network/templates/report.j2 b/collections/ansible_collections/demo/patching/roles/build_report_network/templates/report.j2
new file mode 100644
index 0000000..f00eac7
--- /dev/null
+++ b/collections/ansible_collections/demo/patching/roles/build_report_network/templates/report.j2
@@ -0,0 +1,111 @@
+
+
+
+ Ansible Network Automation Report
+
+
+
+
+
+
+
+
+
+
+
+ {% include 'header.j2' %}
+
+
+
Ansible Network Automation Report
+
+
+
+
+
+
Network Device
+
Layer 1
+
Layer 2
+
Layer 3
+
+
+
+{% for network_switch in groups['tag_Router']|sort %}
+
diff --git a/collections/ansible_collections/demo/patching/roles/build_report_network/templates/vlans.j2 b/collections/ansible_collections/demo/patching/roles/build_report_network/templates/vlans.j2
new file mode 100644
index 0000000..044a334
--- /dev/null
+++ b/collections/ansible_collections/demo/patching/roles/build_report_network/templates/vlans.j2
@@ -0,0 +1,33 @@
+
+
+
+
VLANs
+
+{% if hostvars[network_switch]['ansible_network_resources']['vlans'] is defined and hostvars[network_switch]['ansible_network_resources']['vlans']|length > 0 %}
+
+
+
+
vlan_id
+
Name
+
state
+
+
+
+{% for vlan in hostvars[network_switch]['ansible_network_resources']['vlans'] %}
+
+
{{vlan['vlan_id']}}
+
{{vlan['name']|default("none")}}
+
{{vlan['state']|default("default")}}
+
+{% endfor %}
+
+
+{% 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 %}
+
+
+
+
\ No newline at end of file
diff --git a/collections/ansible_collections/demo/patching/roles/build_report_network/vars/main.yml b/collections/ansible_collections/demo/patching/roles/build_report_network/vars/main.yml
new file mode 100644
index 0000000..f09b15e
--- /dev/null
+++ b/collections/ansible_collections/demo/patching/roles/build_report_network/vars/main.yml
@@ -0,0 +1 @@
+file_path: /var/www/html
\ No newline at end of file
diff --git a/collections/ansible_collections/demo/patching/roles/build_report_windows/README.md b/collections/ansible_collections/demo/patching/roles/build_report_windows/README.md
new file mode 100644
index 0000000..93d6df4
--- /dev/null
+++ b/collections/ansible_collections/demo/patching/roles/build_report_windows/README.md
@@ -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
+
+```
\ No newline at end of file
diff --git a/collections/ansible_collections/demo/patching/roles/build_report_windows/defaults/main.yml b/collections/ansible_collections/demo/patching/roles/build_report_windows/defaults/main.yml
new file mode 100644
index 0000000..1154771
--- /dev/null
+++ b/collections/ansible_collections/demo/patching/roles/build_report_windows/defaults/main.yml
@@ -0,0 +1,2 @@
+---
+detailedreport: True
\ No newline at end of file
diff --git a/collections/ansible_collections/demo/patching/roles/build_report_windows/example_results/Ansible Windows Automation Report.png b/collections/ansible_collections/demo/patching/roles/build_report_windows/example_results/Ansible Windows Automation Report.png
new file mode 100644
index 0000000..1ead477
Binary files /dev/null and b/collections/ansible_collections/demo/patching/roles/build_report_windows/example_results/Ansible Windows Automation Report.png differ
diff --git a/collections/ansible_collections/demo/patching/roles/build_report_windows/files/css/new.css b/collections/ansible_collections/demo/patching/roles/build_report_windows/files/css/new.css
new file mode 100644
index 0000000..f58d18f
--- /dev/null
+++ b/collections/ansible_collections/demo/patching/roles/build_report_windows/files/css/new.css
@@ -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;
+}
\ No newline at end of file
diff --git a/collections/ansible_collections/demo/patching/roles/build_report_windows/files/redhat-ansible-logo.svg b/collections/ansible_collections/demo/patching/roles/build_report_windows/files/redhat-ansible-logo.svg
new file mode 100644
index 0000000..2ecef98
--- /dev/null
+++ b/collections/ansible_collections/demo/patching/roles/build_report_windows/files/redhat-ansible-logo.svg
@@ -0,0 +1,48 @@
+
+
+
diff --git a/collections/ansible_collections/demo/patching/roles/build_report_windows/files/server.png b/collections/ansible_collections/demo/patching/roles/build_report_windows/files/server.png
new file mode 100644
index 0000000..9ad96fc
Binary files /dev/null and b/collections/ansible_collections/demo/patching/roles/build_report_windows/files/server.png differ
diff --git a/collections/ansible_collections/demo/patching/roles/build_report_windows/files/webpage_logo.png b/collections/ansible_collections/demo/patching/roles/build_report_windows/files/webpage_logo.png
new file mode 100644
index 0000000..65b5836
Binary files /dev/null and b/collections/ansible_collections/demo/patching/roles/build_report_windows/files/webpage_logo.png differ
diff --git a/collections/ansible_collections/demo/patching/roles/build_report_windows/tasks/main.yml b/collections/ansible_collections/demo/patching/roles/build_report_windows/tasks/main.yml
new file mode 100644
index 0000000..f349f23
--- /dev/null
+++ b/collections/ansible_collections/demo/patching/roles/build_report_windows/tasks/main.yml
@@ -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"
\ No newline at end of file
diff --git a/collections/ansible_collections/demo/patching/roles/build_report_windows/templates/header.j2 b/collections/ansible_collections/demo/patching/roles/build_report_windows/templates/header.j2
new file mode 100644
index 0000000..6d504d0
--- /dev/null
+++ b/collections/ansible_collections/demo/patching/roles/build_report_windows/templates/header.j2
@@ -0,0 +1,15 @@
+
+
+
+
\ No newline at end of file
diff --git a/collections/ansible_collections/demo/patching/roles/build_report_windows/templates/packages.j2 b/collections/ansible_collections/demo/patching/roles/build_report_windows/templates/packages.j2
new file mode 100644
index 0000000..f290897
--- /dev/null
+++ b/collections/ansible_collections/demo/patching/roles/build_report_windows/templates/packages.j2
@@ -0,0 +1,29 @@
+
+
+
+
Package Facts
+
+
+
+
+
Package Name
+
Version
+
Publisher
+
+
+
+ {% if hostvars[windows_host]['packages'] is defined %}
+ {% for package in hostvars[windows_host]['packages'] %}
+
+
{{package['name']}}
+
{{package['version']}}
+
{{package['publisher']}}
+
+ {% endfor %}
+ {% endif %}
+
+
+
+
+
+
\ No newline at end of file
diff --git a/collections/ansible_collections/demo/patching/roles/build_report_windows/templates/report.j2 b/collections/ansible_collections/demo/patching/roles/build_report_windows/templates/report.j2
new file mode 100644
index 0000000..96b5f3d
--- /dev/null
+++ b/collections/ansible_collections/demo/patching/roles/build_report_windows/templates/report.j2
@@ -0,0 +1,101 @@
+
+
+
+ Ansible Windows Automation Report
+
+
+
+
+
+
+
+
+
+
+
+
+ {% include 'header.j2' %}
+
+
+
Ansible Windows Automation Report
+
+
+
+
+
+
Windows Device
+
Operating System
+
Operating System Kernel Version
+
+
+
+{% for windows_host in groups['tag_Windows']|sort %}
+
+
+{% 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] %}
+
{{updatenum.title}}
+{% endfor %}
+{% else %}
+
Compliant
+{% endif %}
+
+
+
+
+{% endfor %}
+
+
+{% for host in ansible_play_hosts %}
+
Created with Ansible on {{hostvars[host].ansible_date_time.iso8601}}
+{% endfor %}
+
+
+
\ No newline at end of file
diff --git a/collections/ansible_collections/demo/patching/roles/build_report_windows_patch/vars/main.yml b/collections/ansible_collections/demo/patching/roles/build_report_windows_patch/vars/main.yml
new file mode 100644
index 0000000..f09b15e
--- /dev/null
+++ b/collections/ansible_collections/demo/patching/roles/build_report_windows_patch/vars/main.yml
@@ -0,0 +1 @@
+file_path: /var/www/html
\ No newline at end of file
diff --git a/collections/ansible_collections/demo/patching/roles/patch_linux/defaults/main.yml b/collections/ansible_collections/demo/patching/roles/patch_linux/defaults/main.yml
new file mode 100644
index 0000000..04bf772
--- /dev/null
+++ b/collections/ansible_collections/demo/patching/roles/patch_linux/defaults/main.yml
@@ -0,0 +1,3 @@
+---
+exclude_packages: []
+allow_reboot: true
\ No newline at end of file
diff --git a/collections/ansible_collections/demo/patching/roles/patch_linux/tasks/main.yml b/collections/ansible_collections/demo/patching/roles/patch_linux/tasks/main.yml
new file mode 100644
index 0000000..512a900
--- /dev/null
+++ b/collections/ansible_collections/demo/patching/roles/patch_linux/tasks/main.yml
@@ -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
\ No newline at end of file
diff --git a/collections/ansible_collections/demo/patching/roles/report_linux/README.md b/collections/ansible_collections/demo/patching/roles/report_linux/README.md
new file mode 100644
index 0000000..88fa95c
--- /dev/null
+++ b/collections/ansible_collections/demo/patching/roles/report_linux/README.md
@@ -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
+
+```
\ No newline at end of file
diff --git a/collections/ansible_collections/demo/patching/roles/report_linux/defaults/main.yml b/collections/ansible_collections/demo/patching/roles/report_linux/defaults/main.yml
new file mode 100644
index 0000000..1154771
--- /dev/null
+++ b/collections/ansible_collections/demo/patching/roles/report_linux/defaults/main.yml
@@ -0,0 +1,2 @@
+---
+detailedreport: True
\ No newline at end of file
diff --git a/collections/ansible_collections/demo/patching/roles/report_linux/example_results/Ansible Linux Automation Report.png b/collections/ansible_collections/demo/patching/roles/report_linux/example_results/Ansible Linux Automation Report.png
new file mode 100644
index 0000000..3e2287a
Binary files /dev/null and b/collections/ansible_collections/demo/patching/roles/report_linux/example_results/Ansible Linux Automation Report.png differ
diff --git a/collections/ansible_collections/demo/patching/roles/report_linux/files/css/new.css b/collections/ansible_collections/demo/patching/roles/report_linux/files/css/new.css
new file mode 100644
index 0000000..f58d18f
--- /dev/null
+++ b/collections/ansible_collections/demo/patching/roles/report_linux/files/css/new.css
@@ -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;
+}
\ No newline at end of file
diff --git a/collections/ansible_collections/demo/patching/roles/report_linux/files/redhat-ansible-logo.svg b/collections/ansible_collections/demo/patching/roles/report_linux/files/redhat-ansible-logo.svg
new file mode 100644
index 0000000..2ecef98
--- /dev/null
+++ b/collections/ansible_collections/demo/patching/roles/report_linux/files/redhat-ansible-logo.svg
@@ -0,0 +1,48 @@
+
+
+
diff --git a/collections/ansible_collections/demo/patching/roles/report_linux/files/server.png b/collections/ansible_collections/demo/patching/roles/report_linux/files/server.png
new file mode 100644
index 0000000..9ad96fc
Binary files /dev/null and b/collections/ansible_collections/demo/patching/roles/report_linux/files/server.png differ
diff --git a/collections/ansible_collections/demo/patching/roles/report_linux/files/webpage_logo.png b/collections/ansible_collections/demo/patching/roles/report_linux/files/webpage_logo.png
new file mode 100644
index 0000000..65b5836
Binary files /dev/null and b/collections/ansible_collections/demo/patching/roles/report_linux/files/webpage_logo.png differ
diff --git a/collections/ansible_collections/demo/patching/roles/report_linux/tasks/main.yml b/collections/ansible_collections/demo/patching/roles/report_linux/tasks/main.yml
new file mode 100644
index 0000000..71d2364
--- /dev/null
+++ b/collections/ansible_collections/demo/patching/roles/report_linux/tasks/main.yml
@@ -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"
\ No newline at end of file
diff --git a/collections/ansible_collections/demo/patching/roles/report_linux/templates/header.j2 b/collections/ansible_collections/demo/patching/roles/report_linux/templates/header.j2
new file mode 100644
index 0000000..6d504d0
--- /dev/null
+++ b/collections/ansible_collections/demo/patching/roles/report_linux/templates/header.j2
@@ -0,0 +1,15 @@
+
+
+
+
\ No newline at end of file
diff --git a/collections/ansible_collections/demo/patching/roles/report_linux/templates/packages.j2 b/collections/ansible_collections/demo/patching/roles/report_linux/templates/packages.j2
new file mode 100644
index 0000000..86a842d
--- /dev/null
+++ b/collections/ansible_collections/demo/patching/roles/report_linux/templates/packages.j2
@@ -0,0 +1,31 @@
+
+
+
+
Package Facts
+
+
+
+
+
Package Name
+
source
+
release
+
version
+
+
+
+ {% if hostvars[linux_host]['packages'] is defined %}
+ {% for package in hostvars[linux_host]['packages'] %}
+
+
{{package['name']}}
+
{{package['source']}}
+
{{package['release']}}
+
{{package['version']}}
+
+{% endfor %}
+{% endif %}
+
+
+
+
+
+
\ No newline at end of file
diff --git a/collections/ansible_collections/demo/patching/roles/report_linux/templates/report.j2 b/collections/ansible_collections/demo/patching/roles/report_linux/templates/report.j2
new file mode 100644
index 0000000..779740b
--- /dev/null
+++ b/collections/ansible_collections/demo/patching/roles/report_linux/templates/report.j2
@@ -0,0 +1,105 @@
+
+
+
+ Ansible Linux Automation Report
+
+
+
+
+
+
+
+
+
+
+
+
+ {% include 'header.j2' %}
+
+
+
Ansible Linux Automation Report
+
+
+
+
+
+
Linux Device
+
Package Manager
+
Operating System
+
Operating System Version
+
Operating System Kernel Version
+
+
+
+{% for linux_host in ansible_play_hosts |sort %}
+
+
+
+ {% if hostvars[linux_host]['services'] is defined %}
+ {% for servicesname in hostvars[linux_host]['services']|sort %}
+ {% set service = hostvars[linux_host]['services'][servicesname] %}
+
+
{{service['name']}}
+
{{service['state']}}
+
{{service['source']}}
+
+{% endfor %}
+{% endif %}
+
+
+
+
+
+
\ No newline at end of file
diff --git a/collections/ansible_collections/demo/patching/roles/report_linux/vars/main.yml b/collections/ansible_collections/demo/patching/roles/report_linux/vars/main.yml
new file mode 100644
index 0000000..b91cc7a
--- /dev/null
+++ b/collections/ansible_collections/demo/patching/roles/report_linux/vars/main.yml
@@ -0,0 +1 @@
+file_path: /var/www/html/reports
\ No newline at end of file
diff --git a/collections/ansible_collections/demo/patching/roles/report_linux_patching/README.md b/collections/ansible_collections/demo/patching/roles/report_linux_patching/README.md
new file mode 100644
index 0000000..b7e2fe2
--- /dev/null
+++ b/collections/ansible_collections/demo/patching/roles/report_linux_patching/README.md
@@ -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
+
+```
\ No newline at end of file
diff --git a/collections/ansible_collections/demo/patching/roles/report_linux_patching/defaults/main.yml b/collections/ansible_collections/demo/patching/roles/report_linux_patching/defaults/main.yml
new file mode 100644
index 0000000..aaf5c3d
--- /dev/null
+++ b/collections/ansible_collections/demo/patching/roles/report_linux_patching/defaults/main.yml
@@ -0,0 +1,3 @@
+EMAIL_FROM: tower@shadowman.dev
+to_emails: alex@shadowman.dev,tower@shadowman.dev
+EMAIL_TO: "{{ to_emails.split(',') }}"
\ No newline at end of file
diff --git a/collections/ansible_collections/demo/patching/roles/report_linux_patching/example_results/Full_Report.png b/collections/ansible_collections/demo/patching/roles/report_linux_patching/example_results/Full_Report.png
new file mode 100644
index 0000000..a7d66a2
Binary files /dev/null and b/collections/ansible_collections/demo/patching/roles/report_linux_patching/example_results/Full_Report.png differ
diff --git a/collections/ansible_collections/demo/patching/roles/report_linux_patching/example_results/Splunk Patching Aggregated RHEL 8.png b/collections/ansible_collections/demo/patching/roles/report_linux_patching/example_results/Splunk Patching Aggregated RHEL 8.png
new file mode 100644
index 0000000..4367264
Binary files /dev/null and b/collections/ansible_collections/demo/patching/roles/report_linux_patching/example_results/Splunk Patching Aggregated RHEL 8.png differ
diff --git a/collections/ansible_collections/demo/patching/roles/report_linux_patching/files/css/main.css b/collections/ansible_collections/demo/patching/roles/report_linux_patching/files/css/main.css
new file mode 100644
index 0000000..dfeb435
--- /dev/null
+++ b/collections/ansible_collections/demo/patching/roles/report_linux_patching/files/css/main.css
@@ -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;
+ }
\ No newline at end of file
diff --git a/collections/ansible_collections/demo/patching/roles/report_linux_patching/files/webpage_logo.png b/collections/ansible_collections/demo/patching/roles/report_linux_patching/files/webpage_logo.png
new file mode 100644
index 0000000..3d99673
Binary files /dev/null and b/collections/ansible_collections/demo/patching/roles/report_linux_patching/files/webpage_logo.png differ
diff --git a/collections/ansible_collections/demo/patching/roles/report_linux_patching/tasks/main.yml b/collections/ansible_collections/demo/patching/roles/report_linux_patching/tasks/main.yml
new file mode 100644
index 0000000..bd92a21
--- /dev/null
+++ b/collections/ansible_collections/demo/patching/roles/report_linux_patching/tasks/main.yml
@@ -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
\ No newline at end of file
diff --git a/collections/ansible_collections/demo/patching/roles/report_linux_patching/templates/report.j2 b/collections/ansible_collections/demo/patching/roles/report_linux_patching/templates/report.j2
new file mode 100644
index 0000000..01df73b
--- /dev/null
+++ b/collections/ansible_collections/demo/patching/roles/report_linux_patching/templates/report.j2
@@ -0,0 +1,120 @@
+
+
+
+ Linux Patch Report
+
+
+
+
Ansible Linux Patching Report
+
+
+
+
+
+
+
+
+
+
Hostname
+
Operating System
+
Operating System Version
+
Required Updates
+
+
+
+{% for linux_host in ansible_play_hosts |sort %}
+
+{% if hostvars[linux_host].patchingresult_yum.changed|default("false",true) == true %}
+{% for packagename in hostvars[linux_host].patchingresult_yum.changes.updated|sort %}
+
{{ packagename[0] }} - {{ packagename[1] }}
+{% endfor %}
+{% elif hostvars[linux_host].patchingresult_dnf.changed|default("false",true) == true %}
+{% for packagename in hostvars[linux_host].patchingresult_dnf.results|sort %}
+
{{ packagename }}
+{% endfor %}
+{% elif hostvars[linux_host].patchingresult_dnf.changed is undefined %}
+
Patching Failed
+{% elif hostvars[linux_host].patchingresult_yum.changed is undefined %}
+
Patching Failed
+{% else %}
+
Compliant
+{% endif %}
+
+
+
+{% endfor %}
+
+
+
Created with Ansible on {{hostvars[inventory_hostname].ansible_date_time.iso8601}}