Showing with 77 additions and 85 deletions.
  1. +9 −0 CHANGELOG.md
  2. +3 −5 README.md
  3. +14 −12 lib/facter/pe_status_check.rb
  4. +50 −67 lib/shared/pe_status_check.rb
  5. +1 −1 metadata.json
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@

All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org).

## [v1.1.0](https://github.com/puppetlabs/puppetlabs-pe_status_check/tree/v1.1.0) (2022-02-24)

[Full Changelog](https://github.com/puppetlabs/puppetlabs-pe_status_check/compare/v1.0.0...v1.1.0)

### Added

- Change to Use 'module\_function' instead of `self` [\#83](https://github.com/puppetlabs/puppetlabs-pe_status_check/pull/83) ([m0dular](https://github.com/m0dular))
- Standardize net http [\#82](https://github.com/puppetlabs/puppetlabs-pe_status_check/pull/82) ([m0dular](https://github.com/m0dular))

## [v1.0.0](https://github.com/puppetlabs/puppetlabs-pe_status_check/tree/v1.0.0) (2022-02-08)

[Full Changelog](https://github.com/puppetlabs/puppetlabs-pe_status_check/compare/v0.2.0...v1.0.0)
Expand Down
8 changes: 3 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
- [Setup requirements](#setup-requirements)
- [Beginning with pe_status_check](#beginning-with-pe_status_check)
- [Usage](#usage)
- [Class declaration (Optional)](#class-declaration-optional)
- [Class declaration (optional)](#class-declaration-optional)
- [Reference](#reference)
- [Fact: pe_status_check](#fact-pe_status_check)
- [Fact: agent_status_check](#fact-agent_status_check)
- [How to Report an issue or contribute to the module](#how-to-report-an-issue-or-contribute-to-the-module)
- [How to report an issue or contribute to the module](#how-to-report-an-issue-or-contribute-to-the-module)

## Description

Expand Down Expand Up @@ -102,9 +102,7 @@ Refer to this section for next steps when any indicator reports a `false`.

| Indicator ID | Description | Self-service steps | What to include in a Support ticket |
|--------------|------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| AS001 | Determines if the agent host certificate is expiring in the next 90 days. | Puppet Enterprise has a plan built into extend agent certificates.

Uses a puppet query to find expiring host certificates and pass the node ID to this plan: `puppet plan run enterprise_tasks::agent_cert_regen agent=$(puppet query 'inventory[certname] { facts.agent_self_service.AS001 = false }' \| jq -r '.[].certname' \| paste -sd, -) master=$(puppet config print certname)` | If the plan fails to run, open a support ticket referencing AS001 and provide the error message recieved when running the plan.
| AS001 | Determines if the agent host certificate is expiring in the next 90 days. | Puppet Enterprise has a plan built into extend agent certificates.|Uses a puppet query to find expiring host certificates and pass the node ID to this plan: `puppet plan run enterprise_tasks::agent_cert_regen agent=$(puppet query 'inventory[certname] { facts.agent_status_check.AS001 = false }' \| jq -r '.[].certname' \| paste -sd, -) master=$(puppet config print certname)` | If the plan fails to run, open a support ticket referencing AS001 and provide the error message recieved when running the plan.

## How to report an issue or contribute to the module

Expand Down
26 changes: 14 additions & 12 deletions lib/facter/pe_status_check.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,16 @@

next unless PEStatusCheck.primary? || PEStatusCheck.replica? || PEStatusCheck.compiler? || PEStatusCheck.legacy_compiler?

puppetendpoint = 'https://127.0.0.1:8140/status/v1/services'

puppetendpointcall = PEStatusCheck.nethttp_puppet_api(puppetendpoint)

if puppetendpointcall.include?('error') || puppetendpointcall.include?('starting') || puppetendpointcall.include?('stopping') || puppetendpointcall.include?('unknown')
{ S0004: false }
response = PEStatusCheck.http_get('/status/v1/services', 8140)
if response
# In the reponse, keys are the names of the services and values are a hash of its properties
# We can check that all are in 'running' state to see if all are ok
all_running = response.values.all? do |service|
service['state'] == 'running'
end
{ S0004: all_running }
else
{ S0004: true }
{ S0004: false }
end
end

Expand Down Expand Up @@ -166,12 +168,12 @@

chunk(:S0019) do
next unless PEStatusCheck.primary? || PEStatusCheck.replica? || PEStatusCheck.compiler? || PEStatusCheck.legacy_compiler?
pupserv_api = PEStatusCheck.status_check('8140', 'pe-jruby-metrics?level=debug')
if pupserv_api.nil?
{ S0019: false }
response = PEStatusCheck.http_get('/status/v1/services/pe-jruby-metrics?level=debug', 8140)
if response
free_jrubies = response.dig('status', 'experimental', 'metrics', 'average-free-jrubies')
{ S0019: free_jrubies >= 0.9 }
else
jruby_check = pupserv_api.dig('status', 'experimental', 'metrics', 'average-free-jrubies')
{ S0019: jruby_check >= 0.9 }
{ S0019: false }
end
end

Expand Down
117 changes: 50 additions & 67 deletions lib/shared/pe_status_check.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
require 'puppet'
require 'json'
require 'net/http'
require 'openssl'

# PEStatusCheck - Shared code for pe_status_check facts
module PEStatusCheck
module_function

# Gets the resource object by name
# @param resource [String] The resource type to get
# @param name [String] The name of the resource
# @return [Puppet::Resource] The instance of the resource or nil
def self.get_resource(resource, name)
def get_resource(resource, name)
name += '.service' if (resource == 'service') && !name.include?('.')
Puppet::Indirector::Indirection.instance(:resource).find("#{resource}/#{name}")
end
Expand All @@ -15,7 +20,7 @@ def self.get_resource(resource, name)
# @param name [String] The name of the service
# @param service [Puppet::Resource] An optional service resource to use
# @return [Boolean] True if the service is running
def self.service_running(name, service = nil)
def service_running(name, service = nil)
service ||= get_resource('service', name)
return false if service.nil?

Expand All @@ -26,7 +31,7 @@ def self.service_running(name, service = nil)
# @param name [String] The name of the service
# @param service [Puppet::Resource] An optional service resource to use
# @return [Boolean] True if the service is enabled
def self.service_enabled(name, service = nil)
def service_enabled(name, service = nil)
service ||= get_resource('service', name)
return false if service.nil?

Expand All @@ -37,7 +42,7 @@ def self.service_enabled(name, service = nil)
# @param name [String] The name of the service
# @param service [Puppet::Resource] An optional service resource to use
# @return [Boolean] True if the service is running and enabled
def self.service_running_enabled(name, service = nil)
def service_running_enabled(name, service = nil)
service ||= get_resource('service', name)
return false if service.nil?

Expand All @@ -46,7 +51,7 @@ def self.service_running_enabled(name, service = nil)

# Return the name of the pe-postgresql service for the current OS
# @return [String] The name of the pe-postgresql service
def self.pe_postgres_service_name
def pe_postgres_service_name
if Facter.value(:os)['family'].eql?('Debian')
"pe-postgresql#{Facter.value(:pe_postgresql_info)['installed_server_version']}"
else
Expand All @@ -57,7 +62,7 @@ def self.pe_postgres_service_name
# Checks if passed service file exists in correct directory for the OS
# @return [Boolean] true if file exists
# @param configfile [String] The name of the pe service to be tested
def self.service_file_exist?(configfile)
def service_file_exist?(configfile)
configdir = if Facter.value(:os)['family'].eql?('RedHat') || Facter.value(:os)['family'].eql?('Suse')
'/etc/sysconfig'
else
Expand All @@ -66,33 +71,45 @@ def self.service_file_exist?(configfile)
File.exist?("#{configdir}/#{configfile}")
end

# Queries the passed port and endpoint for the status API
# Module method to make a GET request to an api specified by path and port params
# @return [Hash] Response body of the API call
# param port [Integer] The status API port to query
# @param endpoint [String] The status API endpoint to query
def self.status_check(port, endpoint)
require 'json'

host = Puppet[:certname]
client = Puppet.runtime[:http]
response = client.get(URI(Puppet::Util.uri_encode("https://#{host}:#{port}/status/v1/services/#{endpoint}")))
status = JSON.parse(response.body)
status
rescue Puppet::HTTP::ResponseError => e
Facter.debug("fact 'self_service' - HTTP: #{e.response.code} #{e.response.reason}")
rescue Puppet::HTTP::ConnectionError => e
Facter.debug("fact 'self_service' - Connection error: #{e.message}")
rescue Puppet::SSL::SSLError => e
Facter.debug("fact 'self_service' - SSL error: #{e.message}")
rescue Puppet::HTTP::HTTPError => e
Facter.debug("fact 'self_service' - General HTTP error: #{e.message}")
rescue JSON::ParserError => e
Facter.debug("fact 'self_service' - Could not parse body for JSON: #{e}")
# @param path [String] The API path to query. Should include a '/' prefix and query parameters
# @param port [Integer] The port to use
# @param host [String] The FQDN to use in making the connection. Defaults to the Puppet certname
def http_get(path, port, host = Puppet[:certname])
# Use an instance variable to only create an SSLContext once
@ssl_context ||= Puppet::SSL::SSLContext.new(
cacerts: Puppet[:localcacert],
private_key: OpenSSL::PKey::RSA.new(File.read(Puppet[:hostprivkey])),
client_cert: OpenSSL::X509::Certificate.new(File.open(Puppet[:hostcert])),
)

client = Net::HTTP.new(host, port)
# The main reason to use this approach is to set open and read timeouts to a small value
# Puppet's HTTP client does not allow access to these
client.open_timeout = 2
client.read_timeout = 2
client.use_ssl = true
client.verify_mode = OpenSSL::SSL::VERIFY_PEER
client.cert = @ssl_context.client_cert
client.key = @ssl_context.private_key
client.ca_file = @ssl_context.cacerts

response = client.request_get(Puppet::Util.uri_encode(path))
if response.is_a? Net::HTTPSuccess
JSON.parse(response.body)
else
false
end
rescue StandardError => e
Facter.warn("Error in fact 'pe_status_check' when querying #{path}: #{e.message}")
Facter.debug(e.backtrace)
false
end

# Check if Primary node
# @return [Boolean] true is primary node
def self.primary?
def primary?
service_file_exist?('pe-puppetserver') &&
service_file_exist?('pe-orchestration-services') &&
service_file_exist?('pe-console-services') &&
Expand All @@ -101,7 +118,7 @@ def self.primary?

# Check if replica node
# @return [Boolean]
def self.replica?
def replica?
service_file_exist?('pe-puppetserver') &&
!service_file_exist?('pe-orchestration-services') &&
service_file_exist?('pe-console-services') &&
Expand All @@ -110,7 +127,7 @@ def self.replica?

# Check if Compiler node
# @return [Boolean]
def self.compiler?
def compiler?
service_file_exist?('pe-puppetserver') &&
!service_file_exist?('pe-orchestration-services') &&
!service_file_exist?('pe-console-services') &&
Expand All @@ -119,7 +136,7 @@ def self.compiler?

# Check if lagacy compiler node
# @return [Boolean] true
def self.legacy_compiler?
def legacy_compiler?
service_file_exist?('pe-puppetserver') &&
!service_file_exist?('pe-orchestration-services') &&
!service_file_exist?('pe-console-services') &&
Expand All @@ -128,7 +145,7 @@ def self.legacy_compiler?

# Check if Pe postgres node
# @return [Boolean]
def self.postgres?
def postgres?
!service_file_exist?('pe-puppetserver') &&
!service_file_exist?('pe-orchestration-services') &&
!service_file_exist?('pe-console-services') &&
Expand All @@ -139,44 +156,10 @@ def self.postgres?
# Get the free disk percentage from a path
# @param name [String] The path on the file system
# @return [Integer] The percentage of free disk space on the mount
def self.filesystem_free(path)
def filesystem_free(path)
require 'sys/filesystem'

stat = Sys::Filesystem.stat(path)
(stat.blocks_available.to_f / stat.blocks.to_f * 100).to_i
end

# This is a generic NET::HTTP function that can be reusable across different API requests
def self.nethttp_puppet_api(puppetendpoint)
uri = URI.parse(puppetendpoint.to_s)
request = Net::HTTP::Get.new(uri)
request.content_type = 'application/json'
request.body = JSON.dump({
'level' => 'info',
'timeout' => '2'
})

req_options = {
use_ssl: uri.scheme == 'https',
verify_mode: OpenSSL::SSL::VERIFY_NONE,
# waiting for a max of 2 secs to open connection
open_timeout: 2,
# waiting for a max of 2 secs to read response from socket
read_timeout: 2,
}

begin
response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
http.request(request)
end

# returns the body of the response

response.body
rescue StandardError
# returns a connection error

'error'
end
end
end
2 changes: 1 addition & 1 deletion metadata.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "puppetlabs-pe_status_check",
"version": "1.0.0",
"version": "1.1.0",
"author": "Marty Ewings",
"summary": "A Puppet Enterprise Module to Promote Preventative Maintenance and Self Service",
"license": "Apache-2.0",
Expand Down