Skip to content
Snippets Groups Projects
Commit a00090e0 authored by Benjamin Bertrand's avatar Benjamin Bertrand
Browse files

Add Ansible groups views

JIRA INFRA-412
parent 5e76c0b1
No related branches found
No related tags found
No related merge requests found
......@@ -144,10 +144,16 @@ class HostForm(CSEntryForm):
"Ansible vars",
description="Enter variables in YAML format. See https://docs.ansible.com/ansible/latest/reference_appendices/YAMLSyntax.html",
)
ansible_groups = SelectMultipleField(
"Ansible groups", coerce=utils.coerce_to_str_or_none
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.device_type_id.choices = utils.get_model_choices(models.DeviceType)
self.ansible_groups.choices = utils.get_model_choices(
models.AnsibleGroup, attr="name"
)
class InterfaceForm(CSEntryForm):
......@@ -219,3 +225,20 @@ class CreateVMForm(CSEntryForm):
super().__init__(*args, **kwargs)
self.cores.choices = utils.get_choices(current_app.config["VM_CORES_CHOICES"])
self.memory.choices = utils.get_choices(current_app.config["VM_MEMORY_CHOICES"])
class AnsibleGroupForm(CSEntryForm):
name = StringField(
"name",
validators=[validators.InputRequired(), Unique(models.AnsibleGroup)],
filters=[utils.lowercase_field],
)
vars = YAMLField(
"Ansible vars",
description="Enter variables in YAML format. See https://docs.ansible.com/ansible/latest/reference_appendices/YAMLSyntax.html",
)
hosts = SelectMultipleField("Hosts", coerce=utils.coerce_to_str_or_none)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.hosts.choices = utils.get_model_choices(models.Host, attr="name")
......@@ -31,6 +31,7 @@ from .forms import (
NetworkScopeForm,
DomainForm,
CreateVMForm,
AnsibleGroupForm,
)
from ..extensions import db
from ..decorators import login_groups_accepted
......@@ -63,11 +64,15 @@ def create_host():
del form.host_id
if form.validate_on_submit():
network_id = form.network_id.data
ansible_groups = [
models.AnsibleQuery.query.get(id_) for id_ in form.ansible_groups.data
]
host = models.Host(
name=form.name.data,
device_type_id=form.device_type_id.data,
description=form.description.data or None,
ansible_vars=form.ansible_vars.data or None,
ansible_groups=ansible_groups,
)
# The total number of tags will always be quite small
# It's more efficient to retrieve all of them in one query
......@@ -135,11 +140,20 @@ def view_host(name):
def edit_host(name):
host = models.Host.query.filter_by(name=name).first_or_404()
form = HostForm(request.form, obj=host)
# Passing ansible_groups as kwarg to the HostForm doesn't work because
# obj takes precedence (but host.ansible_groups contain AnsibleGroup instances and not id)
# We need to update the default values. Calling process is required.
# See https://stackoverflow.com/questions/5519729/wtforms-how-to-select-options-in-selectmultiplefield
form.ansible_groups.default = [group.id for group in host.ansible_groups]
form.ansible_groups.process(request.form)
if form.validate_on_submit():
host.name = form.name.data
host.device_type_id = form.device_type_id.data
host.description = form.description.data or None
host.ansible_vars = form.ansible_vars.data or None
host.ansible_groups = [
models.AnsibleGroup.query.get(id_) for id_ in form.ansible_groups.data
]
current_app.logger.debug(f"Trying to update: {host!r}")
try:
db.session.commit()
......@@ -281,6 +295,70 @@ def delete_interface():
return redirect(url_for("network.view_host", name=hostname))
@bp.route("/groups")
@login_required
def list_ansible_groups():
return render_template("network/groups.html")
@bp.route("/groups/view/<name>", methods=("GET", "POST"))
@login_required
def view_ansible_group(name):
group = models.AnsibleGroup.query.filter_by(name=name).first_or_404()
return render_template("network/view_group.html", group=group)
@bp.route("/groups/edit/<name>", methods=("GET", "POST"))
@login_groups_accepted("admin")
def edit_ansible_group(name):
group = models.AnsibleGroup.query.filter_by(name=name).first_or_404()
form = AnsibleGroupForm(request.form, obj=group)
# Passing hosts as kwarg to the AnsibleGroupForm doesn't work because
# obj takes precedence (but group.hosts contain Host instances and not id)
# We need to update the default values. Calling process is required.
# See https://stackoverflow.com/questions/5519729/wtforms-how-to-select-options-in-selectmultiplefield
form.hosts.default = [host.id for host in group.hosts]
form.hosts.process(request.form)
if form.validate_on_submit():
group.name = form.name.data
group.vars = form.vars.data or None
group.hosts = [models.Host.query.get(id_) for id_ in form.hosts.data]
current_app.logger.debug(f"Trying to update: {group!r}")
try:
db.session.commit()
except sa.exc.IntegrityError as e:
db.session.rollback()
current_app.logger.warning(f"{e}")
flash(f"{e}", "error")
else:
flash(f"Group {group} updated!", "success")
return redirect(url_for("network.view_ansible_group", name=group.name))
return render_template("network/edit_group.html", form=form)
@bp.route("/groups/create", methods=("GET", "POST"))
@login_groups_accepted("admin")
def create_ansible_group():
form = AnsibleGroupForm()
if form.validate_on_submit():
hosts = [models.Host.query.get(id_) for id_ in form.hosts.data]
group = models.AnsibleGroup(
name=form.name.data, vars=form.vars.data or None, hosts=hosts
)
current_app.logger.debug(f"Trying to create: {group!r}")
db.session.add(group)
try:
db.session.commit()
except sa.exc.IntegrityError as e:
db.session.rollback()
current_app.logger.warning(f"{e}")
flash(f"{e}", "error")
else:
flash(f"Group {group} created!", "success")
return redirect(url_for("network.view_ansible_group", name=group.name))
return render_template("network/create_group.html", form=form)
@bp.route("/domains")
@login_required
def list_domains():
......@@ -528,6 +606,13 @@ def retrieve_domains():
return jsonify(data=data)
@bp.route("/_retrieve_groups")
@login_required
def retrieve_groups():
data = [group.to_dict() for group in models.AnsibleGroup.query.all()]
return jsonify(data=data)
@bp.route("/_generate_random_mac")
@login_required
def generate_random_mac():
......
$(document).ready(function() {
if( $("#groupForm").length || $("#editGroupForm").length ) {
var groupVarsEditor = CodeMirror.fromTextArea(vars, {
lineNumbers: true,
mode: "yaml"
});
groupVarsEditor.setSize(null, 120);
}
var groups_table = $("#groups_table").DataTable({
"ajax": function(data, callback, settings) {
$.getJSON(
$SCRIPT_ROOT + "/network/_retrieve_groups",
function(json) {
callback(json);
});
},
"paging": false,
"columns": [
{ data: 'name',
render: function(data, type, row) {
// render funtion to create link to group view page
if ( data === null ) {
return data;
}
var url = $SCRIPT_ROOT + "/network/groups/view/" + data;
return '<a href="' + url + '">' + data + '</a>';
}
},
{ data: 'vars',
render: function(data, type, row) {
if ( data === "null" ) {
return "";
}
return '<pre>' + data + '</pre>';
}
},
{ data: 'hosts' }
]
});
});
......@@ -6,6 +6,12 @@
<a href="{{ url_for('network.view_host', name=name) }}">{{ name }}</a>
{%- endmacro %}
{% macro link_to_hosts(hosts) -%}
{% for host in hosts %}
{{ link_to_host(host.name) }}
{% endfor %}
{%- endmacro %}
{% macro link_to_item(ics_id) -%}
{% if ics_id %}
<a href="{{ url_for('inventory.view_item', ics_id=ics_id) }}">{{ ics_id }}</a>
......@@ -34,6 +40,16 @@
{% endfor %}
{%- endmacro %}
{% macro link_to_ansible_group(group) -%}
<a href="{{ url_for('network.view_ansible_group', name=group.name) }}">{{ group.name }}</a>
{%- endmacro %}
{% macro link_to_ansible_groups(groups) -%}
{% for group in groups %}
{{ link_to_ansible_group(group) }}
{% endfor %}
{%- endmacro %}
{% macro render_field(field) -%}
{% set field_class = kwargs.pop('class_', '') + ' form-control' %}
{% set label_size = kwargs.pop('label_size', '2') %}
......
......@@ -25,6 +25,8 @@
href="{{ url_for('network.list_scopes') }}">Network Scopes</a>
<a class="list-group-item list-group-item-action {{ is_active(path.startswith("/network/domains")) }}"
href="{{ url_for('network.list_domains') }}">Domains</a>
<a class="list-group-item list-group-item-action {{ is_active(path.startswith("/network/groups")) }}"
href="{{ url_for('network.list_ansible_groups') }}">Ansible groups</a>
{% elif path.startswith("/task") %}
<a class="list-group-item list-group-item-action {{ is_active(path.startswith("/task/tasks")) }}"
href="{{ url_for('task.list_tasks') }}">Tasks</a>
......
{% extends "network/groups.html" %}
{% from "_helpers.html" import render_field %}
{% block title %}Register Ansible group - CSEntry{% endblock %}
{% block groups_main %}
<form id="groupForm" method="POST">
{{ form.hidden_tag() }}
{{ render_field(form.name, class_="text-lowercase") }}
{{ render_field(form.vars) }}
{{ render_field(form.hosts, class_="selectpicker") }}
<div class="form-group row">
<div class="col-sm-10">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</div>
</form>
{%- endblock %}
......@@ -17,6 +17,7 @@
{{ render_field(form.cnames_string) }}
{{ render_field(form.tags, class_="selectpicker") }}
{{ render_field(form.ansible_vars) }}
{{ render_field(form.ansible_groups, class_="selectpicker") }}
<div class="form-group row">
<div class="col-sm-10">
<button type="submit" class="btn btn-primary">Submit</button>
......
{% extends "network/groups.html" %}
{% from "_helpers.html" import render_field %}
{% block title %}Edit Ansible group - CSEntry{% endblock %}
{% block groups_nav %}
<li class="nav-item">
<a class="nav-link" href="{{ url_for('network.view_ansible_group', name=form.name.data) }}">View group</a>
</li>
<li class="nav-item">
<a class="nav-link active" href="{{ url_for('network.edit_ansible_group', name=form.name.data) }}">Edit group</a>
</li>
{% endblock %}
{% block groups_main %}
<form id="editGroupForm" method="POST">
{{ form.hidden_tag() }}
{{ render_field(form.name, class_="text-lowercase") }}
{{ render_field(form.vars) }}
{{ render_field(form.hosts, class_="selectpicker") }}
<div class="form-group row">
<div class="col-sm-10">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</div>
</form>
{%- endblock %}
......@@ -22,6 +22,7 @@
{{ render_field(form.device_type_id) }}
{{ render_field(form.description) }}
{{ render_field(form.ansible_vars) }}
{{ render_field(form.ansible_groups, class_="selectpicker") }}
<div class="form-group row">
<div class="col-sm-10">
<button type="submit" class="btn btn-primary">Submit</button>
......
{% extends "base-fluid.html" %}
{% from "_helpers.html" import is_active %}
{% block title %}Ansible Groups - CSEntry{% endblock %}
{% block main %}
{% set path = request.path %}
<ul class="nav nav-tabs">
<li class="nav-item">
<a class="nav-link {{ is_active(path.endswith("/network/groups")) }}" href="{{ url_for('network.list_ansible_groups') }}">List groups</a>
</li>
<li class="nav-item">
<a class="nav-link {{ is_active(path.startswith("/network/groups/create")) }}" href="{{ url_for('network.create_ansible_group') }}">Register new group</a>
</li>
{% block groups_nav %}{% endblock %}
</ul>
<br>
{% block groups_main %}
<table id="groups_table" class="table table-bordered table-hover table-sm" cellspacing="0" width="100%">
<thead>
<tr>
<th>Name</th>
<th>Vars</th>
<th>Hosts</th>
</tr>
</thead>
</table>
{%- endblock %}
{%- endblock %}
{% block csentry_scripts %}
<script src="{{ url_for('static', filename='js/groups.js') }}"></script>
{% endblock %}
{% extends "network/groups.html" %}
{% from "_helpers.html" import link_to_hosts %}
{% block title %}View Ansible group - CSEntry{% endblock %}
{% block groups_nav %}
<li class="nav-item">
<a class="nav-link active" href="{{ url_for('network.view_ansible_group', name=group.name) }}">View group</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('network.edit_ansible_group', name=group.name) }}">Edit group</a>
</li>
{% endblock %}
{% block groups_main %}
<div class="row">
<div class="col-sm-9">
<dl class="row">
<dt class="col-sm-3">Name</dt>
<dd class="col-sm-9">{{ group.name }}</dd>
<dt class="col-sm-3">Variables</dt>
<dd class="col-sm-9"><pre>{{ group.vars | toyaml }}</pre></dd>
<dt class="col-sm-3">hosts</dt>
<dd class="col-sm-9">{{ link_to_hosts(group.hosts) }}</dd>
<dt class="col-sm-3">Created by</dt>
<dd class="col-sm-9">{{ group.user }}</dd>
<dt class="col-sm-3">Created at</dt>
<dd class="col-sm-9">{{ group.created_at | datetimeformat }}</dd>
<dt class="col-sm-3">Updated at</dt>
<dd class="col-sm-9">{{ group.updated_at | datetimeformat }}</dd>
</dl>
</div>
</div>
{%- endblock %}
{% extends "network/hosts.html" %}
{% from "_helpers.html" import link_to_items, link_to_stack_members,
link_to_ansible_groups,
delete_button_with_confirmation, render_field, submit_button_with_confirmation %}
{% block title %}View Host - CSEntry{% endblock %}
......@@ -42,6 +43,10 @@
<dt class="col-sm-3">Ansible vars</dt>
<dd class="col-sm-9"><pre>{{ host.ansible_vars | toyaml }}</pre></dd>
{% endif %}
{% if host.ansible_groups %}
<dt class="col-sm-3">Ansible groups</dt>
<dd class="col-sm-9">{{ link_to_ansible_groups(host.ansible_groups) }}</dd>
{% endif %}
</dl>
</div>
{% if host.device_type.name.startswith('Virtual') and current_user.is_admin %}
......
......@@ -231,7 +231,10 @@ def pretty_yaml(value):
Function used as a jinja2 filter
"""
return yaml.safe_dump(value, default_flow_style=False)
if value:
return yaml.safe_dump(value, default_flow_style=False)
else:
return ""
def trigger_core_services_update():
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment