Skip to content

Commit

Permalink
Merge pull request #595 from ZeitOnline/ZO-4519
Browse files Browse the repository at this point in the history
ZO-4519: Deploy nightwatch to k8s
  • Loading branch information
wosc committed Jan 25, 2024
2 parents e1f7c38 + 817c60b commit 3d32828
Show file tree
Hide file tree
Showing 13 changed files with 871 additions and 0 deletions.
21 changes: 21 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
commit-message:
prefix: "MAINT:"

- package-ecosystem: "docker"
directory: "/smoketest"
schedule:
interval: "weekly"
commit-message:
prefix: "MAINT:"
- package-ecosystem: "pip"
directory: "/smoketest"
schedule:
interval: "weekly"
commit-message:
prefix: "MAINT:"
37 changes: 37 additions & 0 deletions .github/workflows/nightwatch.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: Build and deploy nightwatch tests

on:
push:
branches:
- main
paths:
- '.github/workflows/nightwatch.yaml'
- 'smoketest/**'
pull_request:
paths:
- '.github/workflows/nightwatch.yaml'
- 'smoketest/**'

jobs:
build:
uses: zeitonline/gh-action-workflows/.github/workflows/[email protected]
secrets: inherit
with:
versions: smoketest/k8s/base/versions
# copy&paste from k8s/base and k8s/staging manifest;
# the json/shell quoting is atrocious.
args: |
--override-type=strategic --overrides="{\"spec\": {
\"serviceAccount\": \"baseproject\",
\"containers\": [{
\"name\": \"nightwatch-test-$TAG\",
\"env\": [
{\"name\": \"HTTPS_PROXY\", \"value\": \"http://static-ip-proxy.ops.zeit.de:3128\"},
{\"name\": \"VIVI_XMLRPC_PASSWORD\", \"valueFrom\": {\"secretKeyRef\": {
\"name\": \"principals\",
\"key\": \"vivi_zeit.cms.principals_system.nightwatch\"
}}}
]
}] }}"
# deploy happens via flux (on `main` branch)
44 changes: 44 additions & 0 deletions bin/test
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!/bin/bash

DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"

function vault_read() {
local path=$1
local field=$2

if [[ -z "$VAULT_TOKEN" ]]; then
VAULT_TOKEN=$(<"$HOME/.vault-token")
fi
curl --silent -H "X-Vault-Token: $VAULT_TOKEN" \
"${VAULT_ADDR%/}/v1/zon/v1/${path}" | \
sed -e "s+^.*\"${field}\":\"\([^\"]*\).*$+\1+"
}


COMMAND=$1
case $COMMAND in
smoke)
set -e
shift
if [[ "$1" != -* ]]; then
environment=$1
shift
else
environment="staging"
fi

cd "$DIR/../smoketest"

image=$(awk -F': ' '/^ newName:/ { print $2 }' \
< k8s/base/kustomization.yaml)
docker buildx build --output type=docker --quiet --tag $image .
docker run --rm -it \
-e VIVI_XMLRPC_PASSWORD=$(vault_read vivi/$environment/nightwatch password) \
$image \
--nightwatch-environment=$environment "$@"
;;
*)
echo "Unrecognized command: $COMMAND"
exit 1
;;
esac
1 change: 1 addition & 0 deletions smoketest/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
k8s/**/*
9 changes: 9 additions & 0 deletions smoketest/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# See https://github.com/ZeitOnline/gh-action-workflows/blob/main/.github/workflows/nightwatch-build.yaml
FROM python:3.12.1-slim as nightwatch
WORKDIR /app
RUN pip --no-cache-dir install pipenv
COPY Pipfile Pipfile.lock ./
RUN pipenv sync
COPY *.py ./
# See https://github.com/ZeitOnline/kustomize/blob/main/components/nightwatch/deployment.yaml
ENTRYPOINT ["pipenv", "run", "pytest", "--tb=native"]
11 changes: 11 additions & 0 deletions smoketest/Pipfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"

[packages]
"webdavclient3" = "*"
"zeit.nightwatch" = ">=1.3.2"

[requires]
python_version = "3"
549 changes: 549 additions & 0 deletions smoketest/Pipfile.lock

Large diffs are not rendered by default.

77 changes: 77 additions & 0 deletions smoketest/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
from io import BytesIO
from urllib.parse import urlparse
import os
import xmlrpc.client

import pytest
import webdav3.client


XMLRPC_AUTH = 'nightwatch:' + os.environ['VIVI_XMLRPC_PASSWORD']
CONFIG_STAGING = {
'browser': {'baseurl': 'https://www.staging.zeit.de'},
'vivi': {
'dav_url': 'http://cms-backend.staging.zeit.de:9000',
'xmlrpc_url': f'https://{XMLRPC_AUTH}@vivi-frontend.staging.zeit.de:9090/',
},
'elasticsearch': 'https://tms-es.staging.zon.zeit.de/zeit_content/_search',
}


CONFIG_PRODUCTION = {
'browser': {'baseurl': 'https://www.zeit.de'},
'vivi': {
'dav_url': 'http://cms-backend.zeit.de:9000',
'xmlrpc_url': f'https://{XMLRPC_AUTH}@vivi-frontend.zeit.de:9090/',
},
'elasticsearch': 'https://tms-es.zon.zeit.de/zeit_content/_search',
}


@pytest.fixture(scope='session')
def nightwatch_config(nightwatch_environment):
config = globals()['CONFIG_%s' % nightwatch_environment.upper()]
return dict(config, environment=nightwatch_environment)


@pytest.fixture(scope='session')
def config(nightwatch_config): # shorter spelling for our tests
return nightwatch_config


def pytest_configure(config):
config.option.prometheus_job_name = 'vivi-deployment-%s' % config.option.nightwatch_environment
if config.option.prometheus_extra_labels is None:
config.option.prometheus_extra_labels = []
config.option.prometheus_extra_labels.append('project=vivi-deployment')


class ViviClient:
def __init__(self, dav_url, xmlrpc_url):
self.dav = webdav3.client.Client({'webdav_hostname': dav_url})
self.xmlrpc = xmlrpc.client.ServerProxy(xmlrpc_url)

def set_property(self, unique_id, ns, name, value):
path = '/cms/work' + urlparse(unique_id).path
if not ns.startswith('http'):
ns = 'http://namespaces.zeit.de/CMS/%s' % ns
self.dav.set_property(path, {'namespace': ns, 'name': name, 'value': value})

def put(self, unique_id, body):
path = '/cms/work' + urlparse(unique_id).path
self.dav.upload_to(BytesIO(body.encode('utf-8')), path)

def refresh_dav_cache(self, unique_id):
if unique_id.startswith('/'):
unique_id = 'http://xml.zeit.de' + unique_id
self.xmlrpc.invalidate(unique_id)

def publish(self, unique_id):
if unique_id.startswith('/'):
unique_id = 'http://xml.zeit.de' + unique_id
self.xmlrpc.publish(unique_id)


@pytest.fixture(scope='session')
def vivi(config):
return ViviClient(**config['vivi'])
25 changes: 25 additions & 0 deletions smoketest/k8s/base/kustomization.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

components:
- github.com/ZeitOnline/kustomize/components/nightwatch?ref=1.3
- versions

patches:
- target:
kind: Deployment
name: nightwatch
patch: |
- op: add
path: /spec/template/spec/containers/0/env
value:
- name: VIVI_XMLRPC_PASSWORD
valueFrom:
secretKeyRef:
name: principals
key: vivi_zeit.cms.principals_system.nightwatch
# See https://github.com/ZeitOnline/gh-action-workflows/blob/main/.github/workflows/nightwatch-build.yaml
images:
- name: nightwatch
newName: europe-west3-docker.pkg.dev/zeitonline-engineering/docker-zon/vivi-nightwatch
6 changes: 6 additions & 0 deletions smoketest/k8s/base/versions/kustomization.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
apiVersion: kustomize.config.k8s.io/v1alpha1
kind: Component

images:
- name: nightwatch
newTag: "nothing"
15 changes: 15 additions & 0 deletions smoketest/k8s/production/kustomization.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
- ../base

patches:
- target:
kind: Deployment
name: nightwatch
patch: |-
- op: replace
path: /spec/template/spec/containers/0/args
value:
- "--nightwatch-environment=production"
16 changes: 16 additions & 0 deletions smoketest/k8s/staging/kustomization.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
- ../base

patches:
- target:
kind: Deployment
name: nightwatch
patch: |-
- op: add
path: /spec/template/spec/containers/0/env/-
value:
name: HTTPS_PROXY
value: http://static-ip-proxy.ops.zeit.de:3128
60 changes: 60 additions & 0 deletions smoketest/test_publisher.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
from datetime import datetime, timezone
from time import sleep

import pytest


def test_publisher_invalidates_fastly(vivi, http):
article = '/data/nightwatch-publish.txt'

expected = datetime.now().isoformat()

vivi.put(article, expected)
vivi.publish(article)

# vivi runs the publisher asynchronously from the API call.
timeout = 60
for i in range(timeout):
sleep(1)
r = http(article)
current = r.text.strip()
if current == expected:
break
else:
pytest.fail('Expected %s, got %s after %s seconds' % (expected, current, timeout))


@pytest.mark.parametrize(
'content',
[
'/wirtschaft/2010-01/automarkt-usa-deutschland-smart',
'/2010/01/index',
],
)
def test_publisher_updates_metadata(vivi, http, config, content):
before = datetime.now(timezone.utc)
sleep(1)

vivi.publish(content)

# vivi runs the publisher asynchronously from the API call.
timeout = 60
for i in range(timeout):
sleep(1)
r = http(
config['elasticsearch'],
json={
'query': {'bool': {'filter': [{'term': {'url': content}}]}},
'_source': ['payload.workflow.date_last_published'],
},
)
try:
hit = r.json()['hits']['hits'][0]['_source']
current = hit['payload']['workflow']['date_last_published']
current = datetime.fromisoformat(current)
except Exception:
current = datetime.min
if current > before:
break
else:
pytest.fail('%s did not increase after %s seconds' % (current, timeout))

0 comments on commit 3d32828

Please sign in to comment.