Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • andersharrisson/csentry
  • ics-infrastructure/csentry
2 results
Show changes
Commits on Source (29)
--- ---
include: include:
- 'https://gitlab.esss.lu.se/ics-infrastructure/gitlab-ci-yml/raw/master/PreCommit.gitlab-ci.yml' - remote: "https://gitlab.esss.lu.se/ics-infrastructure/gitlab-ci-yml/raw/master/PreCommit.gitlab-ci.yml"
- remote: "https://gitlab.esss.lu.se/ics-infrastructure/gitlab-ci-yml/raw/master/SonarScanner.gitlab-ci.yml"
.runner_tags: &runner_tags
tags:
- docker
variables: variables:
CONTAINER_TEST_IMAGE: "$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME" CONTAINER_TEST_IMAGE: "$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME"
...@@ -22,11 +19,14 @@ stages: ...@@ -22,11 +19,14 @@ stages:
- release - release
- deploy - deploy
default:
tags:
- docker
before_script: before_script:
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
build: build:
<<: *runner_tags
stage: build stage: build
image: docker:latest image: docker:latest
script: script:
...@@ -35,7 +35,6 @@ build: ...@@ -35,7 +35,6 @@ build:
- docker push "$CONTAINER_TEST_IMAGE" - docker push "$CONTAINER_TEST_IMAGE"
test: test:
<<: *runner_tags
stage: test stage: test
image: "$CONTAINER_TEST_IMAGE" image: "$CONTAINER_TEST_IMAGE"
services: services:
...@@ -56,16 +55,7 @@ test: ...@@ -56,16 +55,7 @@ test:
junit: junit.xml junit: junit.xml
expire_in: 1 hour expire_in: 1 hour
analyse:
<<: *runner_tags
stage: analyse
image: registry.esss.lu.se/ics-docker/sonar-scanner:3
before_script: []
script:
- sonar-scanner -Dsonar.login=$SONARQUBE_TOKEN -Dsonar.projectVersion=$CI_COMMIT_REF_NAME
release-image: release-image:
<<: *runner_tags
stage: release stage: release
image: docker:latest image: docker:latest
dependencies: [] dependencies: []
...@@ -76,8 +66,26 @@ release-image: ...@@ -76,8 +66,26 @@ release-image:
only: only:
- tags - tags
deploy-dev:
stage: deploy
image: registry.esss.lu.se/ics-docker/tower-cli
before_script: []
dependencies: []
script:
- >
tower-cli job launch
-h torn.tn.esss.lu.se
-t ${TOWER_OAUTH_TOKEN}
-J deploy-csentry-dev
-e "csentry_tag=$CI_COMMIT_REF_NAME" --monitor
environment:
name: dev
url: https://csentry-lab-01.cslab.esss.lu.se
except:
- master
- tags
deploy-staging: deploy-staging:
<<: *runner_tags
stage: deploy stage: deploy
image: registry.esss.lu.se/ics-docker/tower-cli image: registry.esss.lu.se/ics-docker/tower-cli
before_script: [] before_script: []
...@@ -97,7 +105,6 @@ deploy-staging: ...@@ -97,7 +105,6 @@ deploy-staging:
- tags - tags
pages: pages:
<<: *runner_tags
stage: deploy stage: deploy
image: "$CONTAINER_RELEASE_IMAGE" image: "$CONTAINER_RELEASE_IMAGE"
dependencies: [] dependencies: []
...@@ -115,7 +122,6 @@ pages: ...@@ -115,7 +122,6 @@ pages:
- tags - tags
deploy-production: deploy-production:
<<: *runner_tags
stage: deploy stage: deploy
image: registry.esss.lu.se/ics-docker/tower-cli image: registry.esss.lu.se/ics-docker/tower-cli
before_script: [] before_script: []
......
repos: repos:
- repo: https://github.com/psf/black - repo: https://github.com/psf/black
rev: 20.8b1 rev: 22.6.0
hooks: hooks:
- id: black - id: black
- repo: https://gitlab.com/pycqa/flake8 - repo: https://github.com/pycqa/flake8
rev: 3.8.3 rev: 4.0.1
hooks: hooks:
- id: flake8 - id: flake8
Changelog Changelog
========= =========
Version 2021.04.06
------------------
- Validate group names (INFRA-3135)
- Add warning text for creating vm (INFRA-3292)
- Paginate history in ansible groups and hosts (INFRA-3136)
Version 2020.11.25
------------------
- Add documentation about database versioning
- Set display_name to username when empty (INFRA-2909)
- Improve logging (INFRA-2899)
Version 2020.11.13
------------------
- Increased hostname character limit from 20 -> 24 (INFRA-2808)
- Use SSL by default for LDAP connection (INFRA-2805)
- Use SonarScanner.gitlab-ci.yml template (INFRA-2832)
Version 2020.10.30
------------------
- Fix inventory export to excel (INFRA-2780)
- Add developer documentation
Version 2020.10.27
------------------
- Update Python and requirements (INFRA-2649)
- Update RQ and rq-dashboard (INFRA-2649 / INFRA-2754)
- Fix code smells (INFRA-2729)
- Add auditor group (INFRA-2728)
- Update Sphinx and switch to sphinx_rtd_theme (INFRA-2742)
Version 2020.10.06 Version 2020.10.06
------------------ ------------------
......
FROM python:3.8-slim as base FROM python:3.8-slim as base
# Add ESS specific debian repository mirror
RUN echo "deb https://artifactory.esss.lu.se/artifactory/debian-mirror stable main" | tee /etc/apt/sources.list.d/ess-debian-mirror.list
# Install Python dependencies in an intermediate image # Install Python dependencies in an intermediate image
# as some requires a compiler (psycopg2) # as some requires a compiler (psycopg2)
FROM base as builder FROM base as builder
...@@ -65,6 +68,8 @@ RUN apt-get update \ ...@@ -65,6 +68,8 @@ RUN apt-get update \
&& apt-get clean \ && apt-get clean \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
RUN rm /etc/apt/sources.list.d/ess-debian-mirror.list
COPY --chown=csi:csi . /app/ COPY --chown=csi:csi . /app/
WORKDIR /app WORKDIR /app
RUN echo "__version__ = \"$(git describe)\"" > app/_version.py RUN echo "__version__ = \"$(git describe)\"" > app/_version.py
......
.PHONY: help build tag push refresh release db init_db upgrade_db test db_image test_image .PHONY: help build tag push refresh release db init_db upgrade_db test db_image test_image docs
OWNER := registry.esss.lu.se/ics-infrastructure OWNER := registry.esss.lu.se/ics-infrastructure
GIT_TAG := $(shell git describe --always) GIT_TAG := $(shell git describe --always)
...@@ -60,3 +60,6 @@ test_image: ## run the tests (on the latest image) ...@@ -60,3 +60,6 @@ test_image: ## run the tests (on the latest image)
run_uwsgi: ## run the application with uwsgi (to test prod env) run_uwsgi: ## run the application with uwsgi (to test prod env)
docker-compose run -p 8000:8000 --rm web uwsgi --master --http 0.0.0.0:8000 --http-keepalive --manage-script-name --mount /csentry=wsgi.py --callable app --uid conda --processes 2 -b 16384 docker-compose run -p 8000:8000 --rm web uwsgi --master --http 0.0.0.0:8000 --http-keepalive --manage-script-name --mount /csentry=wsgi.py --callable app --uid conda --processes 2 -b 16384
docs: ## run the tests (on current directory)
docker run --rm -v $(shell pwd):/app registry.esss.lu.se/ics-infrastructure/csentry:master sphinx-build -M html docs docs/_build
...@@ -313,7 +313,7 @@ def patch_interface(interface_id): ...@@ -313,7 +313,7 @@ def patch_interface(interface_id):
@bp.route("/interfaces/<int:interface_id>", methods=["DELETE"]) @bp.route("/interfaces/<int:interface_id>", methods=["DELETE"])
@login_groups_accepted("admin") @login_groups_accepted("admin", "network")
def delete_interface(interface_id): def delete_interface(interface_id):
"""Delete an interface """Delete an interface
......
...@@ -12,6 +12,7 @@ This module implements useful functions for the API. ...@@ -12,6 +12,7 @@ This module implements useful functions for the API.
import urllib.parse import urllib.parse
import sqlalchemy as sa import sqlalchemy as sa
from flask import current_app, jsonify, request from flask import current_app, jsonify, request
from flask_login import current_user
from flask_sqlalchemy import Pagination from flask_sqlalchemy import Pagination
from ..extensions import db from ..extensions import db
from .. import utils from .. import utils
...@@ -124,7 +125,6 @@ def get_json_body(): ...@@ -124,7 +125,6 @@ def get_json_body():
data = request.get_json() data = request.get_json()
if data is None: if data is None:
raise utils.CSEntryError("Body should be a JSON object") raise utils.CSEntryError("Body should be a JSON object")
current_app.logger.debug(f"Received: {data}")
if not data: if not data:
raise utils.CSEntryError("At least one field is required", status_code=422) raise utils.CSEntryError("At least one field is required", status_code=422)
return data return data
...@@ -154,6 +154,9 @@ def create_generic_model(model, mandatory_fields=("name",), **kwargs): ...@@ -154,6 +154,9 @@ def create_generic_model(model, mandatory_fields=("name",), **kwargs):
raise utils.CSEntryError(str(e), status_code=422) raise utils.CSEntryError(str(e), status_code=422)
db.session.add(instance) db.session.add(instance)
commit() commit()
current_app.logger.info(
f"New {model.__tablename__} created by {current_user}: {instance.to_dict()}"
)
return jsonify(instance.to_dict()), 201 return jsonify(instance.to_dict()), 201
...@@ -166,6 +169,9 @@ def delete_generic_model(model, primary_key): ...@@ -166,6 +169,9 @@ def delete_generic_model(model, primary_key):
instance = model.query.get_or_404(primary_key) instance = model.query.get_or_404(primary_key)
db.session.delete(instance) db.session.delete(instance)
commit() commit()
current_app.logger.info(
f"{model.__tablename__} {instance} deleted by {current_user}"
)
return jsonify(), 204 return jsonify(), 204
...@@ -195,4 +201,7 @@ def update_generic_model(model, primary_key, allowed_fields): ...@@ -195,4 +201,7 @@ def update_generic_model(model, primary_key, allowed_fields):
except Exception as e: except Exception as e:
raise utils.CSEntryError(str(e), status_code=422) raise utils.CSEntryError(str(e), status_code=422)
commit() commit()
current_app.logger.info(
f"{model.__tablename__} {primary_key} updated by {current_user}: {instance.to_dict()}"
)
return jsonify(instance.to_dict()), 200 return jsonify(instance.to_dict()), 200
...@@ -54,7 +54,9 @@ def sync_user(connection, user): ...@@ -54,7 +54,9 @@ def sync_user(connection, user):
disable_user(user) disable_user(user)
else: else:
attributes = ldap_user["attributes"] attributes = ldap_user["attributes"]
user.display_name = utils.attribute_to_string(attributes["cn"]) user.display_name = (
utils.attribute_to_string(attributes["cn"]) or user.username
)
user.email = utils.attribute_to_string(attributes["mail"]) user.email = utils.attribute_to_string(attributes["mail"])
groups = ldap_manager.get_user_groups( groups = ldap_manager.get_user_groups(
dn=ldap3.utils.conv.escape_filter_chars(ldap_user["dn"]), dn=ldap3.utils.conv.escape_filter_chars(ldap_user["dn"]),
......
...@@ -21,6 +21,7 @@ def login_groups_accepted(*groups): ...@@ -21,6 +21,7 @@ def login_groups_accepted(*groups):
This can be used for users logged in using a cookie (web UI) or JWT (API). This can be used for users logged in using a cookie (web UI) or JWT (API).
Example:: Example::
@bp.route('/models', methods=['POST']) @bp.route('/models', methods=['POST'])
@login_groups_accepted('admin', 'inventory') @login_groups_accepted('admin', 'inventory')
def create_model(): def create_model():
......
...@@ -72,19 +72,7 @@ def create_app(config=None): ...@@ -72,19 +72,7 @@ def create_app(config=None):
integrations=[FlaskIntegration()], integrations=[FlaskIntegration()],
) )
if not app.debug: app.logger.setLevel(logging.DEBUG)
# Log to stderr
handler = logging.StreamHandler()
handler.setFormatter(
logging.Formatter(
"%(asctime)s %(levelname)s: %(message)s " "[in %(pathname)s:%(lineno)d]"
)
)
# Set app logger level to DEBUG
# otherwise only WARNING and above are propagated
app.logger.setLevel(logging.DEBUG)
handler.setLevel(logging.DEBUG)
app.logger.addHandler(handler)
app.logger.info("CSEntry created!") app.logger.info("CSEntry created!")
# Remove variables that contain a password # Remove variables that contain a password
settings_to_display = { settings_to_display = {
...@@ -107,6 +95,9 @@ def create_app(config=None): ...@@ -107,6 +95,9 @@ def create_app(config=None):
) )
app.logger.info(f"Settings:\n{settings_string}") app.logger.info(f"Settings:\n{settings_string}")
# Force RQ_DASHBOARD_REDIS_URL to RQ_REDIS_URL
app.config["RQ_DASHBOARD_REDIS_URL"] = app.config["RQ_REDIS_URL"]
db.init_app(app) db.init_app(app)
migrate.init_app(app) migrate.init_app(app)
login_manager.init_app(app) login_manager.init_app(app)
......
...@@ -46,7 +46,7 @@ def list_items(): ...@@ -46,7 +46,7 @@ def list_items():
@login_required @login_required
def _generate_excel_file(): def _generate_excel_file():
task = current_user.launch_task( task = current_user.launch_task(
"generate_items_excel_file", func="generate_items_excel_file", timeout=180 "generate_items_excel_file", func="generate_items_excel_file", job_timeout=180
) )
db.session.commit() db.session.commit()
return utils.redirect_to_job_status(task.id) return utils.redirect_to_job_status(task.id)
......
...@@ -99,6 +99,7 @@ def pop_rq_connection(exception=None): ...@@ -99,6 +99,7 @@ def pop_rq_connection(exception=None):
@bp.route("/") @bp.route("/")
@login_required @login_required
def index(): def index():
"""Return the application index"""
return render_template("index.html") return render_template("index.html")
......
...@@ -32,6 +32,7 @@ from .plugins import FlaskUserPlugin ...@@ -32,6 +32,7 @@ from .plugins import FlaskUserPlugin
from .validators import ( from .validators import (
ICS_ID_RE, ICS_ID_RE,
HOST_NAME_RE, HOST_NAME_RE,
GROUP_NAME_RE,
INTERFACE_NAME_RE, INTERFACE_NAME_RE,
VLAN_NAME_RE, VLAN_NAME_RE,
MAC_ADDRESS_RE, MAC_ADDRESS_RE,
...@@ -101,7 +102,7 @@ def save_user(dn, username, data, memberships): ...@@ -101,7 +102,7 @@ def save_user(dn, username, data, memberships):
if user is None: if user is None:
user = User( user = User(
username=username, username=username,
display_name=utils.attribute_to_string(data["cn"]), display_name=utils.attribute_to_string(data["cn"]) or username,
email=utils.attribute_to_string(data["mail"]), email=utils.attribute_to_string(data["mail"]),
) )
# Always update the user groups to keep them up-to-date # Always update the user groups to keep them up-to-date
...@@ -1169,6 +1170,22 @@ class AnsibleGroup(CreatedMixin, SearchableMixin, db.Model): ...@@ -1169,6 +1170,22 @@ class AnsibleGroup(CreatedMixin, SearchableMixin, db.Model):
check_parents(self) check_parents(self)
return child return child
@validates("name")
def validate_name(self, key, string):
"""Ensure the name matches the required format"""
if string is None:
return None
# Force the string to lowercase
lower_string = string.lower()
if GROUP_NAME_RE.fullmatch(lower_string) is None:
raise ValidationError(f"Group name shall match {GROUP_NAME_RE.pattern}")
existing_group_name = AnsibleGroup.query.filter(
AnsibleGroup.name == lower_string, AnsibleGroup.id != self.id
).first()
if existing_group_name:
raise ValidationError("Group name matches an existing group")
return lower_string
@property @property
def is_dynamic(self): def is_dynamic(self):
return self.type != AnsibleGroupType.STATIC return self.type != AnsibleGroupType.STATIC
...@@ -1464,7 +1481,7 @@ class Host(CreatedMixin, SearchableMixin, db.Model): ...@@ -1464,7 +1481,7 @@ class Host(CreatedMixin, SearchableMixin, db.Model):
# Force the string to lowercase # Force the string to lowercase
lower_string = string.lower() lower_string = string.lower()
if HOST_NAME_RE.fullmatch(lower_string) is None: if HOST_NAME_RE.fullmatch(lower_string) is None:
raise ValidationError(r"Host name shall match [a-z0-9\-]{2,20}") raise ValidationError(f"Host name shall match {HOST_NAME_RE.pattern}")
existing_cname = Cname.query.filter_by(name=lower_string).first() existing_cname = Cname.query.filter_by(name=lower_string).first()
if existing_cname: if existing_cname:
raise ValidationError("Host name matches an existing cname") raise ValidationError("Host name matches an existing cname")
...@@ -1594,7 +1611,9 @@ class Interface(CreatedMixin, db.Model): ...@@ -1594,7 +1611,9 @@ class Interface(CreatedMixin, db.Model):
# Force the string to lowercase # Force the string to lowercase
lower_string = string.lower() lower_string = string.lower()
if INTERFACE_NAME_RE.fullmatch(lower_string) is None: if INTERFACE_NAME_RE.fullmatch(lower_string) is None:
raise ValidationError(r"Interface name shall match [a-z0-9\-]{2,25}") raise ValidationError(
f"Interface name shall match {INTERFACE_NAME_RE.pattern}"
)
if self.host and not lower_string.startswith(self.host.name): if self.host and not lower_string.startswith(self.host.name):
raise ValidationError( raise ValidationError(
f"Interface name shall start with the host name '{self.host}'" f"Interface name shall start with the host name '{self.host}'"
...@@ -1729,7 +1748,7 @@ class Cname(CreatedMixin, db.Model): ...@@ -1729,7 +1748,7 @@ class Cname(CreatedMixin, db.Model):
# Force the string to lowercase # Force the string to lowercase
lower_string = string.lower() lower_string = string.lower()
if HOST_NAME_RE.fullmatch(lower_string) is None: if HOST_NAME_RE.fullmatch(lower_string) is None:
raise ValidationError(r"cname shall match [a-z0-9\-]{2,20}") raise ValidationError(f"cname shall match {HOST_NAME_RE.pattern}")
existing_interface = Interface.query.filter_by(name=lower_string).first() existing_interface = Interface.query.filter_by(name=lower_string).first()
if existing_interface: if existing_interface:
raise ValidationError("cname matches an existing interface") raise ValidationError("cname matches an existing interface")
......
...@@ -154,7 +154,7 @@ class EditNetworkForm(CSEntryForm): ...@@ -154,7 +154,7 @@ class EditNetworkForm(CSEntryForm):
class HostForm(CSEntryForm): class HostForm(CSEntryForm):
name = StringField( name = StringField(
"Hostname", "Hostname",
description="hostname must be 2-20 characters long and contain only letters, numbers and dash", description="hostname must be 2-24 characters long and contain only letters, numbers and dash",
validators=[ validators=[
validators.InputRequired(), validators.InputRequired(),
validators.Regexp(HOST_NAME_RE), validators.Regexp(HOST_NAME_RE),
...@@ -165,7 +165,11 @@ class HostForm(CSEntryForm): ...@@ -165,7 +165,11 @@ class HostForm(CSEntryForm):
) )
description = TextAreaField("Description") description = TextAreaField("Description")
device_type_id = SelectField("Device Type") device_type_id = SelectField("Device Type")
is_ioc = BooleanField("IOC", default=False) is_ioc = BooleanField(
"IOC",
default=False,
description="This host will be used to run IOCs",
)
ansible_vars = YAMLField( ansible_vars = YAMLField(
"Ansible vars", "Ansible vars",
description="Enter variables in YAML format. See https://docs.ansible.com/ansible/latest/reference_appendices/YAMLSyntax.html", description="Enter variables in YAML format. See https://docs.ansible.com/ansible/latest/reference_appendices/YAMLSyntax.html",
...@@ -199,7 +203,7 @@ class InterfaceForm(CSEntryForm): ...@@ -199,7 +203,7 @@ class InterfaceForm(CSEntryForm):
) )
interface_name = StringField( interface_name = StringField(
"Interface name", "Interface name",
description="name must be 2-25 characters long and contain only letters, numbers and dash", description="name must be 2-29 characters long and contain only letters, numbers and dash",
validators=[ validators=[
validators.InputRequired(), validators.InputRequired(),
validators.Regexp(INTERFACE_NAME_RE), validators.Regexp(INTERFACE_NAME_RE),
...@@ -221,7 +225,7 @@ class InterfaceForm(CSEntryForm): ...@@ -221,7 +225,7 @@ class InterfaceForm(CSEntryForm):
) )
cnames_string = StringField( cnames_string = StringField(
"Cnames", "Cnames",
description="space separated list of cnames (must be 2-20 characters long and contain only letters, numbers and dash)", description="space separated list of cnames (must be 2-24 characters long and contain only letters, numbers and dash)",
validators=[ validators=[
validators.Optional(), validators.Optional(),
RegexpList(HOST_NAME_RE), RegexpList(HOST_NAME_RE),
......
...@@ -110,7 +110,7 @@ def create_host(): ...@@ -110,7 +110,7 @@ def create_host():
current_app.logger.warning(f"{e}") current_app.logger.warning(f"{e}")
flash(f"{e}", "error") flash(f"{e}", "error")
return render_template("network/create_host.html", form=form) return render_template("network/create_host.html", form=form)
current_app.logger.debug(f"Trying to create: {host!r}") current_app.logger.debug(f"Trying to create: {host}")
db.session.add(host) db.session.add(host)
try: try:
db.session.commit() db.session.commit()
...@@ -119,6 +119,9 @@ def create_host(): ...@@ -119,6 +119,9 @@ def create_host():
current_app.logger.warning(f"{e}") current_app.logger.warning(f"{e}")
flash(f"{e}", "error") flash(f"{e}", "error")
else: else:
current_app.logger.info(
f"Host {host} created by {current_user}: {host.to_dict()}"
)
flash(f"Host {host} created!", "success") flash(f"Host {host} created!", "success")
# Save network_id and device_type_id to the session to retrieve them after the redirect # Save network_id and device_type_id to the session to retrieve them after the redirect
session["network_id"] = network_id session["network_id"] = network_id
...@@ -138,6 +141,7 @@ def delete_host(): ...@@ -138,6 +141,7 @@ def delete_host():
# defined on the model # defined on the model
db.session.delete(host) db.session.delete(host)
db.session.commit() db.session.commit()
current_app.logger.info(f"Host {host} deleted by {current_user}")
flash(f"Host {host.name} has been deleted", "success") flash(f"Host {host.name} has been deleted", "success")
if host.device_type.name == "VirtualMachine": if host.device_type.name == "VirtualMachine":
flash( flash(
...@@ -202,7 +206,7 @@ def view_host(name): ...@@ -202,7 +206,7 @@ def view_host(name):
utils.trigger_core_services_update() utils.trigger_core_services_update()
db.session.commit() db.session.commit()
current_app.logger.info( current_app.logger.info(
f"Set network boot profile to {boot_profile} for {name} requested: task {task.id}" f"Set network boot profile to {boot_profile} for {name} requested by {current_user}: task {task.id}"
) )
flash( flash(
f"Set network boot profile to {boot_profile} for {name} requested! " f"Set network boot profile to {boot_profile} for {name} requested! "
...@@ -227,7 +231,9 @@ def view_host(name): ...@@ -227,7 +231,9 @@ def view_host(name):
skip_post_install_job=form.skip_post_install_job.data, skip_post_install_job=form.skip_post_install_job.data,
) )
db.session.commit() db.session.commit()
current_app.logger.info(f"Creation of {name} requested: task {task.id}") current_app.logger.info(
f"Creation of {name} requested by {current_user}: task {task.id}"
)
flash( flash(
f"Creation of {name} requested! Refresh the page to update the status.", f"Creation of {name} requested! Refresh the page to update the status.",
"success", "success",
...@@ -267,7 +273,7 @@ def edit_host(name): ...@@ -267,7 +273,7 @@ def edit_host(name):
current_app.logger.warning(f"{e}") current_app.logger.warning(f"{e}")
flash(f"{e}", "error") flash(f"{e}", "error")
return render_template("network/edit_host.html", form=form) return render_template("network/edit_host.html", form=form)
current_app.logger.debug(f"Trying to update: {host!r}") current_app.logger.debug(f"Trying to update: {host}")
try: try:
db.session.commit() db.session.commit()
except sa.exc.IntegrityError as e: except sa.exc.IntegrityError as e:
...@@ -275,6 +281,9 @@ def edit_host(name): ...@@ -275,6 +281,9 @@ def edit_host(name):
current_app.logger.warning(f"{e}") current_app.logger.warning(f"{e}")
flash(f"{e}", "error") flash(f"{e}", "error")
else: else:
current_app.logger.info(
f"Host {name} updated by {current_user}: {host.to_dict()}"
)
flash(f"Host {host} updated!", "success") flash(f"Host {host} updated!", "success")
return redirect(url_for("network.view_host", name=host.name)) return redirect(url_for("network.view_host", name=host.name))
return render_template("network/edit_host.html", form=form) return render_template("network/edit_host.html", form=form)
...@@ -333,6 +342,9 @@ def create_interface(hostname): ...@@ -333,6 +342,9 @@ def create_interface(hostname):
current_app.logger.warning(f"{e}") current_app.logger.warning(f"{e}")
flash(f"{e}", "error") flash(f"{e}", "error")
else: else:
current_app.logger.info(
f"Interface {interface} created by {current_user}: {interface.to_dict()}"
)
flash(f"Interface {interface} created!", "success") flash(f"Interface {interface} created!", "success")
return redirect(url_for("network.create_interface", hostname=hostname)) return redirect(url_for("network.create_interface", hostname=hostname))
return render_template( return render_template(
...@@ -415,6 +427,9 @@ def edit_interface(name): ...@@ -415,6 +427,9 @@ def edit_interface(name):
current_app.logger.warning(f"{e}") current_app.logger.warning(f"{e}")
flash(f"{e}", "error") flash(f"{e}", "error")
else: else:
current_app.logger.info(
f"Interface {name} updated by {current_user}: {interface.to_dict()}"
)
flash(f"Interface {interface} updated!", "success") flash(f"Interface {interface} updated!", "success")
return redirect(url_for("network.view_host", name=interface.host.name)) return redirect(url_for("network.view_host", name=interface.host.name))
return render_template( return render_template(
...@@ -437,6 +452,7 @@ def delete_interface(): ...@@ -437,6 +452,7 @@ def delete_interface():
# defined on the model # defined on the model
db.session.delete(interface) db.session.delete(interface)
db.session.commit() db.session.commit()
current_app.logger.info(f"Interface {interface} deleted by {current_user}")
flash(f"Interface {interface.name} has been deleted", "success") flash(f"Interface {interface.name} has been deleted", "success")
return redirect(url_for("network.view_host", name=hostname)) return redirect(url_for("network.view_host", name=hostname))
...@@ -460,6 +476,7 @@ def delete_ansible_group(): ...@@ -460,6 +476,7 @@ def delete_ansible_group():
group = models.AnsibleGroup.query.get_or_404(request.form["group_id"]) group = models.AnsibleGroup.query.get_or_404(request.form["group_id"])
db.session.delete(group) db.session.delete(group)
db.session.commit() db.session.commit()
current_app.logger.info(f"Group {group} deleted by {current_user}")
flash(f"Group {group.name} has been deleted", "success") flash(f"Group {group.name} has been deleted", "success")
return redirect(url_for("network.list_ansible_groups")) return redirect(url_for("network.list_ansible_groups"))
...@@ -513,6 +530,9 @@ def edit_ansible_group(name): ...@@ -513,6 +530,9 @@ def edit_ansible_group(name):
current_app.logger.warning(f"{e}") current_app.logger.warning(f"{e}")
flash(f"{e}", "error") flash(f"{e}", "error")
else: else:
current_app.logger.info(
f"Group {name} updated by {current_user}: {group.to_dict()}"
)
flash(f"Group {group} updated!", "success") flash(f"Group {group} updated!", "success")
return redirect(url_for("network.view_ansible_group", name=group.name)) return redirect(url_for("network.view_ansible_group", name=group.name))
return render_template("network/edit_group.html", form=form) return render_template("network/edit_group.html", form=form)
...@@ -547,6 +567,9 @@ def create_ansible_group(): ...@@ -547,6 +567,9 @@ def create_ansible_group():
current_app.logger.warning(f"{e}") current_app.logger.warning(f"{e}")
flash(f"{e}", "error") flash(f"{e}", "error")
else: else:
current_app.logger.info(
f"Group {group} created by {current_user}: {group.to_dict()}"
)
flash(f"Group {group} created!", "success") flash(f"Group {group} created!", "success")
return redirect(url_for("network.view_ansible_group", name=group.name)) return redirect(url_for("network.view_ansible_group", name=group.name))
return render_template("network/create_group.html", form=form) return render_template("network/create_group.html", form=form)
...@@ -574,6 +597,9 @@ def create_domain(): ...@@ -574,6 +597,9 @@ def create_domain():
current_app.logger.warning(f"{e}") current_app.logger.warning(f"{e}")
flash(f"{e}", "error") flash(f"{e}", "error")
else: else:
current_app.logger.info(
f"Domain {domain} created by {current_user}: {domain.to_dict()}"
)
flash(f"Domain {domain} created!", "success") flash(f"Domain {domain} created!", "success")
return redirect(url_for("network.create_domain")) return redirect(url_for("network.create_domain"))
return render_template("network/create_domain.html", form=form) return render_template("network/create_domain.html", form=form)
...@@ -621,6 +647,9 @@ def create_scope(): ...@@ -621,6 +647,9 @@ def create_scope():
current_app.logger.warning(f"{e}") current_app.logger.warning(f"{e}")
flash(f"{e}", "error") flash(f"{e}", "error")
else: else:
current_app.logger.info(
f"Network scope {scope} created by {current_user}: {scope.to_dict()}"
)
flash(f"Network Scope {scope} created!", "success") flash(f"Network Scope {scope} created!", "success")
return redirect(url_for("network.create_scope")) return redirect(url_for("network.create_scope"))
return render_template("network/create_scope.html", form=form) return render_template("network/create_scope.html", form=form)
...@@ -655,6 +684,9 @@ def edit_scope(name): ...@@ -655,6 +684,9 @@ def edit_scope(name):
current_app.logger.warning(f"{e}") current_app.logger.warning(f"{e}")
flash(f"{e}", "error") flash(f"{e}", "error")
else: else:
current_app.logger.info(
f"Network scope {name} updated by {current_user}: {scope.to_dict()}"
)
flash(f"Network Scope {scope} updated!", "success") flash(f"Network Scope {scope} updated!", "success")
return redirect(url_for("network.view_scope", name=scope.name)) return redirect(url_for("network.view_scope", name=scope.name))
return render_template("network/edit_scope.html", form=form) return render_template("network/edit_scope.html", form=form)
...@@ -734,6 +766,9 @@ def create_network(): ...@@ -734,6 +766,9 @@ def create_network():
current_app.logger.warning(f"{e}") current_app.logger.warning(f"{e}")
flash(f"{e}", "error") flash(f"{e}", "error")
else: else:
current_app.logger.info(
f"Network {network} created by {current_user}: {network.to_dict()}"
)
flash(f"Network {network} created!", "success") flash(f"Network {network} created!", "success")
# Save scope_id to the session to retrieve it after the redirect # Save scope_id to the session to retrieve it after the redirect
session["scope_id"] = scope_id session["scope_id"] = scope_id
...@@ -776,6 +811,9 @@ def edit_network(vlan_name): ...@@ -776,6 +811,9 @@ def edit_network(vlan_name):
current_app.logger.warning(f"{e}") current_app.logger.warning(f"{e}")
flash(f"{e}", "error") flash(f"{e}", "error")
else: else:
current_app.logger.info(
f"Network {vlan_name} updated by {current_user}: {network.to_dict()}"
)
flash(f"Network {network} updated!", "success") flash(f"Network {network} updated!", "success")
return redirect(url_for("network.view_network", vlan_name=network.vlan_name)) return redirect(url_for("network.view_network", vlan_name=network.vlan_name))
return render_template("network/edit_network.html", form=form) return render_template("network/edit_network.html", form=form)
......
...@@ -33,7 +33,6 @@ SESSION_REDIS_URL = "redis://redis:6379/0" ...@@ -33,7 +33,6 @@ SESSION_REDIS_URL = "redis://redis:6379/0"
CACHE_TYPE = "redis" CACHE_TYPE = "redis"
CACHE_REDIS_URL = "redis://redis:6379/1" CACHE_REDIS_URL = "redis://redis:6379/1"
RQ_REDIS_URL = "redis://redis:6379/2" RQ_REDIS_URL = "redis://redis:6379/2"
RQ_DASHBOARD_REDIS_URL = RQ_REDIS_URL
ELASTICSEARCH_URL = "http://elasticsearch:9200" ELASTICSEARCH_URL = "http://elasticsearch:9200"
ELASTICSEARCH_INDEX_SUFFIX = "-dev" ELASTICSEARCH_INDEX_SUFFIX = "-dev"
...@@ -43,8 +42,8 @@ ELASTICSEARCH_INDEX_SUFFIX = "-dev" ...@@ -43,8 +42,8 @@ ELASTICSEARCH_INDEX_SUFFIX = "-dev"
ELASTICSEARCH_REFRESH = "false" ELASTICSEARCH_REFRESH = "false"
LDAP_HOST = os.environ.get("LDAP_HOST", "esss.lu.se") LDAP_HOST = os.environ.get("LDAP_HOST", "esss.lu.se")
LDAP_PORT = int(os.environ.get("LDAP_PORT", 389)) LDAP_PORT = int(os.environ.get("LDAP_PORT", 636))
LDAP_USE_SSL = os.environ.get("LDAP_USE_SSL", "false").lower() == "true" LDAP_USE_SSL = os.environ.get("LDAP_USE_SSL", "true").lower() == "true"
LDAP_BASE_DN = os.environ.get("LDAP_BASE_DN", "DC=esss,DC=lu,DC=se") LDAP_BASE_DN = os.environ.get("LDAP_BASE_DN", "DC=esss,DC=lu,DC=se")
LDAP_USER_DN = os.environ.get("LDAP_USER_DN", "") LDAP_USER_DN = os.environ.get("LDAP_USER_DN", "")
LDAP_GROUP_DN = os.environ.get("LDAP_GROUP_DN", "") LDAP_GROUP_DN = os.environ.get("LDAP_GROUP_DN", "")
......
$(document).ready(function () {
$("#host_version_table").DataTable({
"lengthMenu": [[1, 5, 10, 25, -1], [1, 5, 10, 25, "All"]],
"order": [[1, "desc"]]
});
});
$(document).ready(function () {
$("#group_version_table").DataTable({
"lengthMenu": [[1, 5, 10, 25, -1], [1, 5, 10, 25, "All"]],
"order": [[1, "desc"]]
});
});
...@@ -92,12 +92,12 @@ class TaskWorker(Worker): ...@@ -92,12 +92,12 @@ class TaskWorker(Worker):
def launch_awx_job(resource="job", **kwargs): def launch_awx_job(resource="job", **kwargs):
"""Launch an AWX job r"""Launch an AWX job
job_template or inventory_source shall be passed as keyword argument job_template or inventory_source shall be passed as keyword argument
:param resource: job|workflow_job|inventory_source :param resource: job|workflow_job|inventory_source
:param **kwargs: keyword arguments passed to launch the job :param \*\*kwargs: keyword arguments passed to launch the job
:returns: A dictionary with information from resource.monitor :returns: A dictionary with information from resource.monitor
""" """
rq_job = get_current_job() rq_job = get_current_job()
...@@ -106,14 +106,10 @@ def launch_awx_job(resource="job", **kwargs): ...@@ -106,14 +106,10 @@ def launch_awx_job(resource="job", **kwargs):
if job_template is None and inventory_source is None: if job_template is None and inventory_source is None:
current_app.logger.warning("No job_template nor inventory_source passed!") current_app.logger.warning("No job_template nor inventory_source passed!")
return "No job_template nor inventory_source passed!" return "No job_template nor inventory_source passed!"
if ( if job_template in (
job_template current_app.config["AWX_CREATE_VIOC"],
in ( current_app.config["AWX_CREATE_VM"],
current_app.config["AWX_CREATE_VIOC"], ) and not current_app.config.get("AWX_VM_CREATION_ENABLED", False):
current_app.config["AWX_CREATE_VM"],
)
and not current_app.config.get("AWX_VM_CREATION_ENABLED", False)
):
current_app.logger.info("AWX VM creation is disabled. Not sending any request.") current_app.logger.info("AWX VM creation is disabled. Not sending any request.")
return "AWX VM creation not triggered" return "AWX VM creation not triggered"
if not current_app.config.get("AWX_JOB_ENABLED", False): if not current_app.config.get("AWX_JOB_ENABLED", False):
......
...@@ -136,7 +136,12 @@ ...@@ -136,7 +136,12 @@
{{ field(class_="form-check-input", **kwargs) }} {{ field(class_="form-check-input", **kwargs) }}
{{ field.label(class_="form-check-label") }} {{ field.label(class_="form-check-label") }}
</div> </div>
</div> {% if field.description %}
<small class="form-text text-muted">
{{ field.description|safe }}
</small>
{% endif %}
</div>
{% else %} {% else %}
{{ field.label(class_="col-sm-" + label_size + " col-form-label") }} {{ field.label(class_="col-sm-" + label_size + " col-form-label") }}
<div class="col-sm-{{ input_size }}"> <div class="col-sm-{{ input_size }}">
...@@ -184,7 +189,7 @@ ...@@ -184,7 +189,7 @@
</div> </div>
{%- endmacro %} {%- endmacro %}
{% macro submit_button_with_confirmation(title, message) -%} {% macro submit_button_with_confirmation(title, message, message2) -%}
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#submitModal" }}> <button type="button" class="btn btn-primary" data-toggle="modal" data-target="#submitModal" }}>
{{ title }} {{ title }}
</button> </button>
...@@ -194,7 +199,7 @@ ...@@ -194,7 +199,7 @@
<div class="modal-dialog" role="document"> <div class="modal-dialog" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h6 class="modal-title">{{ message }}</h6> <h6 class="modal-title">{{ message }} {% if message2 is defined %}<br/><br/><br/> {{ message2 }} {% endif %}</h6>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> <button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
</button> </button>
...@@ -277,4 +282,4 @@ ...@@ -277,4 +282,4 @@
</div> </div>
<div class="card-body item-comment">{{ comment.body }}</div> <div class="card-body item-comment">{{ comment.body }}</div>
</div> </div>
{%- endmacro %} {%- endmacro %}
\ No newline at end of file
...@@ -78,6 +78,7 @@ ...@@ -78,6 +78,7 @@
<script type=text/javascript> <script type=text/javascript>
$SCRIPT_ROOT = {{ request.script_root|tojson|safe }}; $SCRIPT_ROOT = {{ request.script_root|tojson|safe }};
</script> </script>
<script src="{{ url_for('static', filename='js/history.js') }}"></script>
<script src="{{ url_for('static', filename='js/csentry.js') }}"></script> <script src="{{ url_for('static', filename='js/csentry.js') }}"></script>
{% block csentry_scripts %}{% endblock %} {% block csentry_scripts %}{% endblock %}
{% endblock %} {% endblock %}