Merge branch 'master' of github-DDS:dod-ccpo/atst into tests-ignore-object-representation

This commit is contained in:
Jay Newlin (PW) 2018-12-05 17:01:24 -05:00
commit 83aaafedb2
31 changed files with 740 additions and 86 deletions

View File

@ -195,6 +195,28 @@ jobs:
name: "Update Kubernetes Deployment" name: "Update Kubernetes Deployment"
command: ./deploy/kubernetes/atst-update-deploy.sh command: ./deploy/kubernetes/atst-update-deploy.sh
deploy_test:
docker:
- image: *sourceImage
auth: *sourceAuth
environment: *dockerCmdEnvironment
working_directory: *workingDirectory
steps:
- attach_workspace:
at: .
- run:
name: "Export GIT_SHA"
command: echo "export GIT_SHA=$(git rev-parse --short HEAD)" >> $BASH_ENV
- run:
name: "Generate the Target Image Name"
command: echo "export IMAGE_NAME=\"${ATAT_DOCKER_REGISTRY_URL}/${PROD_IMAGE_NAME}:${GIT_SHA}\"" >> $BASH_ENV
- run:
name: "Update Kubernetes Deployment"
command: ./deploy/kubernetes/atst-update-deploy.sh atat-test
- run:
name: "Reset the Sample Data"
command: ./deploy/kubernetes/atst-reset-sample-data.sh atat-test
workflows: workflows:
version: 2 version: 2
run-tests: run-tests:
@ -215,3 +237,30 @@ workflows:
filters: filters:
branches: branches:
only: master only: master
nightly:
triggers:
- schedule:
cron: "0 0 * * *"
filters:
branches:
only:
- master
jobs:
- app_setup
- test:
requires:
- app_setup
- build_and_push_image:
requires:
- test
filters:
branches:
only:
- master
- deploy_test:
requires:
- build_and_push_image
filters:
branches:
only:
- master

6
.gitignore vendored
View File

@ -45,8 +45,12 @@ ssl/client-certs/*.srl
# uploads # uploads
/uploads /uploads
# coverage output # python coverage output
.coverage .coverage
# je coverage output
coverage
# selenium testing # selenium testing
browserstacklocal browserstacklocal

View File

@ -1,6 +1,7 @@
import re import re
import datetime import datetime
from flask import current_app as app from flask import current_app as app, render_template
from jinja2.exceptions import TemplateNotFound
def iconSvg(name): def iconSvg(name):
@ -86,6 +87,14 @@ def pageWindow(pagination, size=2):
return (max(1, (page - size) - over), min(num_pages, (page + size) - under)) return (max(1, (page - size) - over), min(num_pages, (page + size) - under))
def renderAuditEvent(event):
template_name = "audit_log/events/{}.html".format(event.resource_type)
try:
return render_template(template_name, event=event)
except TemplateNotFound:
return render_template("audit_log/events/default.html", event=event)
def register_filters(app): def register_filters(app):
app.jinja_env.filters["iconSvg"] = iconSvg app.jinja_env.filters["iconSvg"] = iconSvg
app.jinja_env.filters["dollars"] = dollars app.jinja_env.filters["dollars"] = dollars
@ -98,3 +107,4 @@ def register_filters(app):
app.jinja_env.filters["formattedDate"] = formattedDate app.jinja_env.filters["formattedDate"] = formattedDate
app.jinja_env.filters["dateFromString"] = dateFromString app.jinja_env.filters["dateFromString"] = dateFromString
app.jinja_env.filters["pageWindow"] = pageWindow app.jinja_env.filters["pageWindow"] = pageWindow
app.jinja_env.filters["renderAuditEvent"] = renderAuditEvent

View File

@ -137,7 +137,7 @@ def get_pagination_opts(request, default_page=1, default_per_page=100):
def activity_history(): def activity_history():
pagination_opts = get_pagination_opts(request) pagination_opts = get_pagination_opts(request)
audit_events = AuditLog.get_all_events(g.current_user, pagination_opts) audit_events = AuditLog.get_all_events(g.current_user, pagination_opts)
return render_template("audit_log.html", audit_events=audit_events) return render_template("audit_log/audit_log.html", audit_events=audit_events)
@bp.route("/about") @bp.route("/about")

View File

@ -0,0 +1,81 @@
# Adding a New Environment
## New Config
Add a new subfolder to this directory. You can copy `uat` or `tests`. You'll need to change any references to the previous environment name (i.e., `uat`) to your new environment name. This includes things like the k8s namespace and the subdomain for the new site.
## New Load Balancers
You need two new load balancers. Currently, these are managed in Rackspace. You will need one for the regular site and one for the auth domain. They should have all of the Kubernetes worker nodes attached. When attached to the LB for the main site domain, the nodes should point to port 32761, which is the port where all our NGINX ingress is managed. The auth LB nodes should point to a new port in the 32700 - 32799 range. The `nodePort` specified in your new environment's config should match this port.
## Initially Apply the New Config
Apply your new environment config to create the namespace. Pod creation will fail at this point.
```
kubectl -n my-new-env apply -f deploy/kubernetes/my-new-env/
```
## Create New Secrets
You should then copy and duplicate the secrets from another environment.
### Adjust the INI Config for the New Site
You can duplicate the override.ini file from one of the existing sites. For instance, to get the `atst-config-ini` secret for the UAT env:
```
kubectl -n atat-uat get secret atst-config-ini -o yaml > uat-secrets.yml
```
You can copy the base64 secret content from the `uat-secrets.yml` file. Decode it into a new `override.ini` file:
```
echo '[Paste in the long base64 string here]' | base64 --decode > override.ini
```
Edit and adjust the application config as needed for your new site. Then add it as a secret:
```
kubectl -n my-new-env create secret generic atst-config-ini --from-file=override.ini
```
### Add a New htpasswd
Create a new htpasswd to protect the dev login of the new site:
```
htpasswd -c htpasswd atat
```
You'll be prompted for the new password. Then add it as a secret:
```
kubectl -n my-new-env create secret generic atst-nginx-htpasswd --from-file=htpasswd
```
### Duplicate the Rest
You should also the `dhparam-4096` and `nginx-client-ca-bundle` secrets. These can be copied from an existing environment to yours without any changes. The TLS secrets and token will be handled by another service.
## Disable SSL
In order for [kube-lego](https://github.com/jetstack/kube-lego) to generate new certs for your site, you have to temporarily disable SSL for your new load balancers.
For both your LBs, set them to use HTTP/80. Delete the attached nodes for both and re-add them, setting them to use port 32760. This will allow kube-lego to do its job.
To monitor the process, find the pod ID for the kube-lego worker. To find it, look in the output for:
```
kubectl -n kube-system get all
```
Then once you know the pod name:
```
kubectl -n kube-system --kubeconfig ~/.kube/atat log kube-lego-b96b7bc5c-9fmcv -f --tail=10
```
You will see output about kube-lego attempts to create certs for your new site.
Once kube-lego is successful, you should restore your load balancers to the config they had initially. Additionally, you should enable HTTPS redirects.

View File

@ -32,6 +32,9 @@ subjects:
- kind: ServiceAccount - kind: ServiceAccount
name: atat-deployer name: atat-deployer
namespace: atat namespace: atat
- kind: ServiceAccount
name: atat-deployer
namespace: atat-test
roleRef: roleRef:
kind: Role kind: Role
name: atat-deploy-role name: atat-deploy-role

View File

@ -0,0 +1,54 @@
#!/usr/bin/env bash
#
# deploy/kubernetes/atst-update-deploy.sh: Resets the sample data on the target
# environment.
set -o pipefail
set -o errexit
set -o nounset
# set -o xtrace
# Config
MAX_DEPLOY_WAIT='300'
if [[ $# -eq 0 ]]; then
NAMESPACE=atat
else
NAMESPACE=$1
fi
# Remove the K8S CA file when the script exits
function cleanup {
printf "Cleaning up...\n"
rm -vf "${HOME}/k8s_ca.crt"
printf "Cleaning done."
}
trap cleanup EXIT
# Decode and save the K8S CA cert
echo "${K8S_CA_CRT}" | base64 -d - > "${HOME}/k8s_ca.crt"
# Setup the local kubectl client
kubectl config set-context atst-deployer \
--cluster=atat-cluster \
--user=atat-deployer \
--namespace=${NAMESPACE}
kubectl config set-cluster atat-cluster \
--embed-certs=true \
--server="${K8S_ENDPOINT}" \
--certificate-authority="${HOME}/k8s_ca.crt"
kubectl config set-credentials atat-deployer --token="$(echo ${K8S_USER_TOKEN} | base64 -d -)"
kubectl config use-context atst-deployer
kubectl config current-context
# we only need to run these commands against one existing pod
ATST_POD=$(kubectl -n ${NAMESPACE} get pods -l app=atst -o custom-columns=NAME:.metadata.name --no-headers | sed -n 1p)
# echo "kubectl -n ${NAMESPACE} exec ${ATST_POD} -- pipenv run python script/remove_sample_data.py"
echo "removing sample data on pod ${ATST_POD}"
kubectl -n ${NAMESPACE} exec ${ATST_POD} -- pipenv run python script/remove_sample_data.py
echo "seeding sample data on pod ${ATST_POD}"
kubectl -n ${NAMESPACE} exec ${ATST_POD} -- pipenv run python script/seed_sample.py

View File

@ -11,6 +11,12 @@ set -o nounset
# Config # Config
MAX_DEPLOY_WAIT='300' MAX_DEPLOY_WAIT='300'
if [[ $# -eq 0 ]]; then
NAMESPACE=atat
else
NAMESPACE=$1
fi
if [ "${IMAGE_NAME}x" = "x" ] if [ "${IMAGE_NAME}x" = "x" ]
then then
IMAGE_NAME="${ATAT_DOCKER_REGISTRY_URL}/${PROD_IMAGE_NAME}:${GIT_SHA}" IMAGE_NAME="${ATAT_DOCKER_REGISTRY_URL}/${PROD_IMAGE_NAME}:${GIT_SHA}"
@ -31,7 +37,7 @@ echo "${K8S_CA_CRT}" | base64 -d - > "${HOME}/k8s_ca.crt"
kubectl config set-context atst-deployer \ kubectl config set-context atst-deployer \
--cluster=atat-cluster \ --cluster=atat-cluster \
--user=atat-deployer \ --user=atat-deployer \
--namespace=atat --namespace=${NAMESPACE}
kubectl config set-cluster atat-cluster \ kubectl config set-cluster atat-cluster \
--embed-certs=true \ --embed-certs=true \
@ -44,15 +50,15 @@ kubectl config use-context atst-deployer
kubectl config current-context kubectl config current-context
# Update the ATST deployment # Update the ATST deployment
kubectl -n atat set image deployment.apps/atst atst="${IMAGE_NAME}" kubectl -n ${NAMESPACE} set image deployment.apps/atst atst="${IMAGE_NAME}"
kubectl -n atat set image deployment.apps/atst-worker atst-worker="${IMAGE_NAME}" kubectl -n ${NAMESPACE} set image deployment.apps/atst-worker atst-worker="${IMAGE_NAME}"
# Wait for deployment to finish # Wait for deployment to finish
if ! timeout -t "${MAX_DEPLOY_WAIT}" -s INT kubectl -n atat rollout status deployment/atst if ! timeout -t "${MAX_DEPLOY_WAIT}" -s INT kubectl -n ${NAMESPACE} rollout status deployment/atst
then then
# Deploy did not finish before max wait time; abort and rollback the deploy # Deploy did not finish before max wait time; abort and rollback the deploy
kubectl -n atat rollout undo deployment/atst kubectl -n ${NAMESPACE} rollout undo deployment/atst
kubectl -n atat rollout undo deployment/atst-worker kubectl -n ${NAMESPACE} rollout undo deployment/atst-worker
# Exit with a non-zero return code # Exit with a non-zero return code
exit 2 exit 2
fi fi

View File

@ -0,0 +1,26 @@
kind: Role
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
namespace: atat-test
name: atat-sample-update
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list"]
- apiGroups: [""]
resources: ["pods/exec"]
verbs: ["create"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: atst-sample-role-binding
namespace: atat-test
subjects:
- kind: ServiceAccount
name: atat-deployer
namespace: atat
roleRef:
kind: Role
name: atat-sample-update
apiGroup: rbac.authorization.k8s.io

View File

@ -0,0 +1,15 @@
---
apiVersion: v1
kind: ConfigMap
metadata:
name: atst-config
namespace: atat-test
data:
uwsgi-config: |-
[uwsgi]
callable = app
module = app
socket = /var/run/uwsgi/uwsgi.socket
plugins = python3
virtualenv = /opt/atat/atst/.venv
chmod-socket = 666

View File

@ -0,0 +1,11 @@
---
apiVersion: v1
kind: ConfigMap
metadata:
name: atst-envvars
namespace: atat-test
data:
FLASK_ENV: dev
OVERRIDE_CONFIG_FULLPATH: /opt/atat/atst/atst-overrides.ini
UWSGI_CONFIG_FULLPATH: /opt/atat/atst/uwsgi-config.ini
RQ_QUEUES: atat-test

View File

@ -0,0 +1,79 @@
---
apiVersion: v1
kind: ConfigMap
metadata:
name: atst-nginx
namespace: atat-test
data:
nginx-config: |-
server {
server_name test.atat.code.mil;
listen 8442;
listen [::]:8442 ipv6only=on;
if ($http_x_forwarded_proto != 'https') {
return 301 https://$host$request_uri;
}
location /login-redirect {
return 301 https://auth-test.atat.code.mil$request_uri;
}
location /login-dev {
try_files $uri @appbasicauth;
}
location / {
try_files $uri @app;
}
location @app {
include uwsgi_params;
uwsgi_pass unix:///var/run/uwsgi/uwsgi.socket;
}
location @appbasicauth {
include uwsgi_params;
uwsgi_pass unix:///var/run/uwsgi/uwsgi.socket;
auth_basic "Developer Access";
auth_basic_user_file /etc/nginx/.htpasswd;
}
}
server {
server_name auth-test.atat.code.mil;
listen 8443 ssl;
listen [::]:8443 ssl ipv6only=on;
# SSL server certificate and private key
ssl_certificate /etc/ssl/private/auth.atat.crt;
ssl_certificate_key /etc/ssl/private/auth.atat.key;
# Set SSL protocols, ciphers, and related options
ssl_protocols TLSv1.3 TLSv1.2;
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256';
ssl_prefer_server_ciphers on;
ssl_ecdh_curve secp384r1;
ssl_dhparam /etc/ssl/dhparam.pem;
# SSL session options
ssl_session_timeout 4h;
ssl_session_cache shared:SSL:10m; # 1mb = ~4000 sessions
ssl_session_tickets off;
# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4;
# Request and validate client certificate
ssl_verify_client on;
ssl_verify_depth 10;
ssl_client_certificate /etc/ssl/client-ca-bundle.pem;
# Guard against HTTPS -> HTTP downgrade
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; always";
location / {
return 301 https://test.atat.code.mil$request_uri;
}
location /login-redirect {
try_files $uri @app;
}
location @app {
include uwsgi_params;
uwsgi_pass unix:///var/run/uwsgi/uwsgi.socket;
uwsgi_param HTTP_X_SSL_CLIENT_VERIFY $ssl_client_verify;
uwsgi_param HTTP_X_SSL_CLIENT_CERT $ssl_client_raw_cert;
uwsgi_param HTTP_X_SSL_CLIENT_S_DN $ssl_client_s_dn;
uwsgi_param HTTP_X_SSL_CLIENT_S_DN_LEGACY $ssl_client_s_dn_legacy;
uwsgi_param HTTP_X_SSL_CLIENT_I_DN $ssl_client_i_dn;
uwsgi_param HTTP_X_SSL_CLIENT_I_DN_LEGACY $ssl_client_i_dn_legacy;
}
}

View File

@ -0,0 +1,8 @@
---
apiVersion: v1
kind: ConfigMap
metadata:
name: atst-worker-envvars
namespace: atat-test
data:
REQUIRE_CRLS: "False"

View File

@ -0,0 +1,223 @@
---
apiVersion: v1
kind: Namespace
metadata:
name: atat-test
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
labels:
app: atst
name: atst
namespace: atat-test
spec:
replicas: 1
strategy:
type: RollingUpdate
template:
metadata:
labels:
app: atst
spec:
securityContext:
fsGroup: 101
containers:
- name: atst
image: registry.atat.codes:443/atst-prod:24b2543c
resources:
requests:
memory: "2500Mi"
envFrom:
- configMapRef:
name: atst-envvars
volumeMounts:
- name: atst-config
mountPath: "/opt/atat/atst/atst-overrides.ini"
subPath: atst-overrides.ini
- name: uwsgi-config
mountPath: "/opt/atat/atst/uwsgi-config.ini"
subPath: uwsgi-config.ini
- name: uwsgi-socket-dir
mountPath: "/var/run/uwsgi"
- name: atst-nginx
image: nginx:alpine
ports:
- containerPort: 8442
name: http
- containerPort: 8443
name: https
volumeMounts:
- name: nginx-auth-tls
mountPath: "/etc/ssl/private"
- name: nginx-client-ca-bundle
mountPath: "/etc/ssl/client-ca-bundle.pem"
subPath: client-ca-bundle.pem
- name: nginx-config
mountPath: "/etc/nginx/conf.d/atst.conf"
subPath: atst.conf
- name: nginx-dhparam
mountPath: "/etc/ssl/dhparam.pem"
subPath: dhparam.pem
- name: nginx-htpasswd
mountPath: "/etc/nginx/.htpasswd"
subPath: .htpasswd
- name: uwsgi-socket-dir
mountPath: "/var/run/uwsgi"
imagePullSecrets:
- name: regcred
volumes:
- name: atst-config
secret:
secretName: atst-config-ini
items:
- key: override.ini
path: atst-overrides.ini
mode: 0644
- name: nginx-auth-tls
secret:
secretName: atst-auth-test-ingress-tls
items:
- key: tls.crt
path: auth.atat.crt
mode: 0644
- key: tls.key
path: auth.atat.key
mode: 0640
- name: nginx-client-ca-bundle
secret:
secretName: nginx-client-ca-bundle
items:
- key: client-ca-bundle.pem
path: client-ca-bundle.pem
mode: 0666
- name: nginx-config
configMap:
name: atst-nginx
items:
- key: nginx-config
path: atst.conf
- name: nginx-dhparam
secret:
secretName: dhparam-4096
items:
- key: dhparam.pem
path: dhparam.pem
mode: 0640
- name: nginx-htpasswd
secret:
secretName: atst-nginx-htpasswd
items:
- key: htpasswd
path: .htpasswd
mode: 0640
- name: uwsgi-config
configMap:
name: atst-config
items:
- key: uwsgi-config
path: uwsgi-config.ini
mode: 0644
- name: uwsgi-socket-dir
emptyDir:
medium: Memory
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
labels:
app: atst
name: atst-worker
namespace: atat-test
spec:
replicas: 1
strategy:
type: RollingUpdate
template:
metadata:
labels:
app: atst
spec:
securityContext:
fsGroup: 101
containers:
- name: atst-worker
image: registry.atat.codes:443/atst-prod:24b2543c
args: ["/bin/bash", "-c", "/opt/atat/atst/script/rq_worker"]
resources:
requests:
memory: "500Mi"
envFrom:
- configMapRef:
name: atst-envvars
- configMapRef:
name: atst-worker-envvars
volumeMounts:
- name: atst-config
mountPath: "/opt/atat/atst/atst-overrides.ini"
subPath: atst-overrides.ini
imagePullSecrets:
- name: regcred
volumes:
- name: atst-config
secret:
secretName: atst-config-ini
items:
- key: override.ini
path: atst-overrides.ini
mode: 0644
---
apiVersion: v1
kind: Service
metadata:
labels:
app: atst
name: atst
namespace: atat-test
spec:
ports:
- name: http
port: 80
targetPort: 8442
selector:
app: atst
---
apiVersion: v1
kind: Service
metadata:
labels:
app: atst
name: atst-auth
namespace: atat-test
spec:
type: NodePort
ports:
- name: https
protocol: TCP
nodePort: 32711
port: 8443
selector:
app: atst
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: atst
namespace: atat-test
annotations:
kubernetes.io/tls-acme: "true"
kubernetes.io/ingress.class: "nginx"
nginx.ingress.kubernetes.io/proxy-body-size: 10m
spec:
tls:
- secretName: atst-test-ingress-tls
hosts:
- test.atat.code.mil
rules:
- host: test.atat.code.mil
http:
paths:
- path: /
backend:
serviceName: atst
servicePort: 80

View File

@ -84,7 +84,12 @@ export default {
onShow: function () { onShow: function () {
setTimeout(() => { // timeout is a hack to make focus work in Chrome setTimeout(() => { // timeout is a hack to make focus work in Chrome
this.$refs.choices.find(choice => choice.selected).$refs.input[0].focus() const selected = this.$refs.choices.find(choice => choice.selected)
if (selected) {
selected.$refs.input[0].focus()
} else {
this.$refs.choices[0].$refs.input[0].focus()
}
}, 100) }, 100)
}, },

View File

@ -1,75 +0,0 @@
{% extends "base.html" %}
{% from "components/pagination.html" import Pagination %}
{% block content %}
<section class="block-list">
<header class="block-list__header">
<h1 class="block-list__title">Activity History</h1>
</header>
<ul>
{% for event in audit_events %}
<li class="block-list__item">
<article class='audit-log__item'>
<div class='audit-log__item__timestamp'>
<local-datetime timestamp='{{ event.time_created }}'></local-datetime>
</div>
<div>
<h2 class='h4 audit-log__item__name'>
{{ event.user.full_name if event.user else "ATAT System" }}
</h2>
{{ event.action }} {{ event.resource_type }} <code>{{ event.resource_id }}</code>
{% if event.display_name %}
({{ event.display_name }})
{% endif %}
<br>
{% if event.event_details and event.resource_type == "user" %}
for User <code>{{ event.event_details.updated_user_id }}</code> ({{ event.event_details.updated_user_name }})
{% if event.event_details["environment"] %}
<br>
in Environment <code>{{ event.event_details["environment_id"] }}</code> ({{ event.event_details["environment"] }})
<br>
in Project <code>{{ event.event_details["project_id"] }}</code> ({{ event.event_details["project"] }})
<br>
in Workspace <code>{{ event.event_details["workspace_id"] }}</code> ({{ event.event_details["workspace"] }})
{% endif %}
<br>
{% elif event.event_details and event.resource_type == "invitation" %}
{% set accepted = event.changed_state.status and event.changed_state.status.1 == "ACCEPTED" %}
{% if accepted %}
accepted by {{ event.event_details.email }} (DOD <code>{{ event.event_details.dod_id }}</code>)
{% endif %}
{% endif %}
{% if event.changed_state and event.resource_type == 'environment_role' %}
from {{ event.changed_state.role[0] }} to {{ event.changed_state.role[1] }}
<br>
{% endif %}
{% if event.workspace %}
in Workspace <code>{{ event.workspace_id }}</code> ({{ event.workspace.name }})
{% elif event.request %}
on Request <code>{{ event.request_id }}</code> ({{ event.request.displayname }})
{% endif %}
{% if event.changed_state.role %}
from {{ event.changed_state.role[0] }} to {{ event.changed_state.role[1] }}
<br>
{% endif %}
</div>
</article>
</li>
{% endfor %}
</ul>
</section>
{{ Pagination(audit_events, 'atst.activity_history') }}
{% endblock %}

View File

@ -0,0 +1,25 @@
{% extends "base.html" %}
{% from "components/pagination.html" import Pagination %}
{% block content %}
<section class="block-list">
<header class="block-list__header">
<h1 class="block-list__title">Activity History</h1>
</header>
<ul>
{% for event in audit_events %}
<li class="block-list__item">
{% autoescape false %}
{{ event | renderAuditEvent }}
{% endautoescape %}
</li>
{% endfor %}
</ul>
</section>
{{ Pagination(audit_events, 'atst.activity_history') }}
{% endblock %}

View File

@ -0,0 +1,22 @@
<article class='audit-log__item'>
<div class='audit-log__item__timestamp'>
<local-datetime timestamp='{{ event.time_created }}'></local-datetime>
</div>
<div>
{% block header %}
<h2 class='h4 audit-log__item__name'>
{{ event.user.full_name if event.user else "ATAT System" }}
</h2>
{{ event.action }} {{ event.resource_type }} <code>{{ event.resource_id }}</code>
{% if event.display_name %}
({{ event.display_name }})
{% endif %}
{% endblock %}
<br>
{% block content %}{% endblock %}
</div>
</article>

View File

@ -0,0 +1,26 @@
{% extends "audit_log/events/_base.html" %}
{% block content %}
{% if event.event_details %}
<br>
<b>Details:</b>
<dl>
{% for key, value in event.event_details.items() %}
{% if value is not none %}
<dt>{{ key }}</dt>
<dd>{{ value }}</dd>
{% endif %}
{% endfor %}
</dl>
{% endif %}
{% if event.changed_state %}
<br>
<b>Changes:</b>
<dl>
{% for key, value in event.changed_state.items() %}
<dt>{{ key }}</dt>
<dd>{{ value[0] }} to {{ value[1] }}</dd>
{% endfor %}
</dl>
{% endif %}
{% endblock %}

View File

@ -0,0 +1,5 @@
{% extends "audit_log/events/_base.html" %}
{% block content %}
in Workspace <code>{{ event.workspace_id }}</code> ({{ event.workspace.name }})
{% endblock %}

View File

@ -0,0 +1,13 @@
{% extends 'audit_log/events/_base.html' %}
{% block content %}
for User <code>{{ event.event_details.updated_user_id }}</code> ({{ event.event_details.updated_user_name }})
{% if event.event_details["environment"] %}
<br>
in Environment <code>{{ event.event_details["environment_id"] }}</code> ({{ event.event_details["environment"] }})
<br>
in Project <code>{{ event.event_details["project_id"] }}</code> ({{ event.event_details["project"] }})
<br>
in Workspace <code>{{ event.event_details["workspace_id"] }}</code> ({{ event.event_details["workspace"] }})
{% endif %}
{% endblock %}

View File

@ -0,0 +1,8 @@
{% extends "audit_log/events/_base.html" %}
{% block content %}
{% set accepted = event.changed_state.status and event.changed_state.status.1 == "ACCEPTED" %}
{% if accepted %}
accepted by {{ event.event_details.email }} (DOD <code>{{ event.event_details.dod_id }}</code>)
{% endif %}
{% endblock %}

View File

@ -0,0 +1,4 @@
{% extends "audit_log/events/_base.html" %}
{% block content %}
{% endblock %}

View File

@ -0,0 +1,4 @@
{% extends "audit_log/events/_base.html" %}
{% block content %}
{% endblock %}

View File

@ -0,0 +1,4 @@
{% extends "audit_log/events/_base.html" %}
{% block content %}
{% endblock %}

View File

@ -0,0 +1,5 @@
{% extends "audit_log/events/_base.html" %}
{% block content %}
on Request <code>{{ event.request_id }}</code> ({{ event.request.displayname }})
{% endblock %}

View File

@ -0,0 +1,4 @@
{% extends "audit_log/events/_base.html" %}
{% block content %}
{% endblock %}

View File

@ -0,0 +1,4 @@
{% extends "audit_log/events/_base.html" %}
{% block content %}
{% endblock %}

View File

@ -0,0 +1,4 @@
{% extends "audit_log/events/_base.html" %}
{% block content %}
{% endblock %}

View File

@ -0,0 +1,14 @@
{% extends "audit_log/events/_base.html" %}
{% block content %}
for User <code>{{ event.event_details.updated_user_id }}</code> ({{ event.event_details.updated_user_name }})
in Workspace <code>{{ event.workspace_id }}</code> ({{ event.workspace.name }})
{% if event.changed_state.status %}
from status "{{ event.changed_state.status[0] }}" to "{{ event.changed_state.status[1] }}"
{% endif %}
{% if event.changed_state.role %}
from role {{ event.changed_state.role[0] }} to {{ event.changed_state.role[1] }}
{% endif %}
{% endblock %}

View File

@ -1,6 +1,7 @@
import pytest import pytest
from atst.filters import dollars from atst.filters import dollars, renderAuditEvent
from atst.models import AuditEvent
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -15,3 +16,15 @@ from atst.filters import dollars
) )
def test_dollar_fomatter(input, expected): def test_dollar_fomatter(input, expected):
assert dollars(input) == expected assert dollars(input) == expected
def test_render_audit_event_with_known_resource_type():
event = AuditEvent(resource_type="user")
result = renderAuditEvent(event)
assert "<article" in result
def test_render_audit_event_with_unknown_resource_type():
event = AuditEvent(resource_type="boat")
result = renderAuditEvent(event)
assert "<article" in result