New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement chatops-enabled configuration consistency checks #10
Changes from 10 commits
52e9751
e2436b8
5b36c50
0e2931e
ac8ed15
02f768f
1ee8442
1195b46
452b59b
c64d4b7
651bcc2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,6 @@ | ||
# OS files | ||
*.DS_Store | ||
|
||
# Byte-compiled / optimized / DLL files | ||
__pycache__/ | ||
*.py[cod] | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
--- | ||
name: "check_consistency" | ||
runner_type: "python-script" | ||
description: "Check that the device's configuration is consistent with the 'golden' config in a Git repository" | ||
enabled: true | ||
entry_point: "check_consistency.py" | ||
parameters: | ||
hostname: | ||
type: "string" | ||
description: "The hostname of the device to connect to. Driver must be specified if hostname is not in configuration. Hostname without FQDN can be given if device is in configuration." | ||
required: true | ||
position: 0 | ||
driver: | ||
type: "string" | ||
description: "Device driver name for connecting to device, see https://napalm.readthedocs.io/en/latest/support/index.html for list." | ||
required: false | ||
position: 1 | ||
port: | ||
type: "string" | ||
description: "port for accessing device" | ||
required: false | ||
position: 2 | ||
credentials: | ||
type: "string" | ||
description: "The credentials group which contains the username and password to log in" | ||
required: false | ||
position: 3 | ||
htmlout: | ||
type: "boolean" | ||
description: "In addition to the normal output also includes html output." | ||
required: false | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can probably supply default: false There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as last comment - this needs to get fixed on all actions There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
position: 4 | ||
repository: | ||
type: "string" | ||
description: "Git repository where the 'golden' device configurations are stored (overrides configuration)" | ||
required: false | ||
position: 5 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
import difflib | ||
import logging | ||
import os | ||
import re | ||
import shutil | ||
import tempfile | ||
|
||
from lib.action import NapalmBaseAction | ||
|
||
import git | ||
|
||
# Suppressing the 'No handlers could be found for logger "st2.st2common.util.loader"' message | ||
# that appears when including the TempRepo class, until I have a chance to troubleshoot why | ||
# this is happening TODO(mierdin) | ||
loaderlog = logging.getLogger('st2.st2common.util.loader') | ||
loaderlog.setLevel(logging.DEBUG) | ||
loaderlog.addHandler(logging.NullHandler()) | ||
|
||
|
||
class TempRepo(object): | ||
def __enter__(self): | ||
self.name = tempfile.mkdtemp() | ||
return self.name | ||
|
||
def __exit__(self, exc_type, exc_value, traceback): | ||
shutil.rmtree(self.name) | ||
|
||
|
||
class NapalmCheckConsistency(NapalmBaseAction): | ||
"""Check that the device's configuration is consistent with the 'golden' config in a Git repository | ||
""" | ||
|
||
def get_golden_config(self, repo, device): | ||
|
||
with TempRepo() as tmpdir: | ||
repo_path = os.path.join(tmpdir, 'repo') | ||
git.Repo.clone_from(repo, repo_path, branch='master') | ||
|
||
try: | ||
|
||
with open('%s/%s.txt' % (repo_path, device)) as config_file: | ||
return "".join(config_file.readlines()) | ||
|
||
except IOError: | ||
self.logger.error("Golden config not present in repo") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Provide the repo name for easy debugging There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ack that, done in 651bcc2 |
||
raise | ||
|
||
def run(self, repository=None, **std_kwargs): | ||
|
||
if not self.config['config_repo'] and not repository: | ||
raise Exception("Golden configs repository not provided in args or config") | ||
else: | ||
# Use config if arg not provided | ||
if not repository: | ||
repository = self.config['config_repo'] | ||
|
||
result = { | ||
"deviation": False, | ||
"diff_contents": "" | ||
} | ||
|
||
try: | ||
|
||
with self.get_driver(**std_kwargs) as device: | ||
|
||
# Get golden and actual configs | ||
golden_config = self.get_golden_config(repository, self.hostname) | ||
actual_config = device.get_config()['running'] | ||
|
||
# Regular expressions for matching lines to ignore | ||
# Lot of network devices have lines like "last modified" that we don't | ||
# want to include in the diff | ||
# | ||
# In the future, this may be a configurable option, but we're doing | ||
# this statically for now. | ||
ignore_regexs = [ | ||
"## .*\n" | ||
] | ||
for pattern in ignore_regexs: | ||
actual_config = re.sub(pattern, "", actual_config) | ||
golden_config = re.sub(pattern, "", golden_config) | ||
|
||
# Generate diff | ||
golden = golden_config.splitlines(1) | ||
actual = actual_config.splitlines(1) | ||
diff = difflib.unified_diff(golden, actual) | ||
diff = ''.join(diff) | ||
|
||
if diff: | ||
result['deviation'] = True | ||
result['diff_contents'] = ''.join(diff) | ||
|
||
except Exception, e: | ||
self.logger.error(str(e)) | ||
return (False, str(e)) | ||
|
||
return (True, result) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you paste an example of the diff in description please? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just realized you meant the PR description. Duh. Posted |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
--- | ||
name: "check_consistency" | ||
pack: "napalm" | ||
action_ref: "napalm.check_consistency" | ||
description: "Check consistency of the device's configuration" | ||
formats: | ||
- "napalm check consistency on {{hostname}}" | ||
# - "check consistency on all" #TODO(mierdin): Won't work until actions can iterate over device list | ||
|
||
result: | ||
format: | | ||
{% if execution.result.result.deviation %} | ||
The configuration on {{ execution.parameters.hostname }} has deviated from the golden configuration. | ||
|
||
Diff to follow: | ||
{{ execution.result.result.diff_contents }} | ||
|
||
{% else %} | ||
The configuration on {{ execution.parameters.hostname }} exactly matches the golden configuration. | ||
{% endif %} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
--- | ||
html_table_class: napalm | ||
config_repo: https://github.com/StackStorm/vsrx-configs.git | ||
|
||
credentials: | ||
core: | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,3 +10,4 @@ napalm-pluribus | |
napalm-panos | ||
napalm | ||
json2table | ||
GitPython |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Python actions don't need positions. If it's a python script then, yes. The runner type being python-script can be confusing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ack that. Unfortunately all the actions in the pack are like this. So I'll address this in a follow-up PR that tackles all of them
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#11