AI overhaul
This commit is contained in:
+34
-161
@@ -1,171 +1,44 @@
|
||||
#SPDX-License-Identifier: MIT-0
|
||||
# SPDX-License-Identifier: MIT-0
|
||||
---
|
||||
# tasks file for ansible-role-www
|
||||
- name: namespace
|
||||
k8s:
|
||||
- name: Validate public website configuration
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- www_hostname is match('^[a-z0-9]([a-z0-9.-]*[a-z0-9])?$')
|
||||
- www_namespace is match('^[a-z0-9]([a-z0-9-]*[a-z0-9])?$')
|
||||
fail_msg: Use a DNS hostname and a Kubernetes namespace without URL paths or credentials.
|
||||
|
||||
- name: Ensure website namespace
|
||||
kubernetes.core.k8s:
|
||||
state: present
|
||||
definition:
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: www
|
||||
name: "{{ www_namespace }}"
|
||||
|
||||
- name: pvc
|
||||
k8s:
|
||||
- name: Deploy membership prerequisites and workloads
|
||||
ansible.builtin.include_tasks: membership.yml
|
||||
when: www_membership_enabled | bool
|
||||
|
||||
- name: Render Apache membership proxy configuration
|
||||
ansible.builtin.set_fact:
|
||||
_www_proxy_config: "{{ lookup('ansible.builtin.template', 'membership-httpd.conf.j2') }}"
|
||||
|
||||
- name: Deploy Apache, public services, storage and ingress
|
||||
kubernetes.core.k8s:
|
||||
state: present
|
||||
definition:
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: data
|
||||
namespace: www
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteMany
|
||||
resources:
|
||||
requests:
|
||||
storage: 8Ti
|
||||
definition: "{{ lookup('ansible.builtin.template', 'website.yml.j2') | from_yaml_all | list }}"
|
||||
|
||||
- name: configmap for httpd.conf
|
||||
k8s:
|
||||
state: present
|
||||
definition:
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: httpd
|
||||
namespace: www
|
||||
data:
|
||||
httpd.conf: "{{ lookup('file', 'httpd.conf') }}"
|
||||
|
||||
- name: deployment for www
|
||||
k8s:
|
||||
definition:
|
||||
apiVersion: v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: httpd
|
||||
namespace: www
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: httpd
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: httpd
|
||||
spec:
|
||||
initContainers:
|
||||
- name: init
|
||||
image: debian:trixie
|
||||
command:
|
||||
- /bin/bash
|
||||
- -c
|
||||
- "apt update -y && apt install -y git && git clone {{ www_repo_url }} /tmp/www && rm -rf /data/* && cp -r /tmp/www/htdocs/* /data/"
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /data
|
||||
containers:
|
||||
- name: httpd
|
||||
image: ericomeehan/www:latest
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: 80
|
||||
- containerPort: 9117
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /usr/local/apache2/htdocs
|
||||
- name: httpd
|
||||
mountPath: /usr/local/apache2/conf/httpd.conf
|
||||
subPath: httpd.conf
|
||||
volumes:
|
||||
- name: data
|
||||
persistentVolumeClaim:
|
||||
claimName: data
|
||||
- name: httpd
|
||||
configMap:
|
||||
name: httpd
|
||||
|
||||
- name: service for prometheus
|
||||
k8s:
|
||||
definition:
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: prometheus
|
||||
namespace: www
|
||||
spec:
|
||||
selector:
|
||||
app: httpd
|
||||
ports:
|
||||
- port: 9117
|
||||
name: http
|
||||
type: ClusterIP
|
||||
|
||||
- name: service for httpd
|
||||
k8s:
|
||||
definition:
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: httpd
|
||||
namespace: www
|
||||
spec:
|
||||
selector:
|
||||
app: httpd
|
||||
ports:
|
||||
- port: 80
|
||||
name: http
|
||||
type: ClusterIP
|
||||
|
||||
- name: onionservice
|
||||
k8s:
|
||||
definition:
|
||||
apiVersion: tor.k8s.torproject.org/v1alpha2
|
||||
kind: OnionService
|
||||
metadata:
|
||||
name: httpd
|
||||
namespace: www
|
||||
spec:
|
||||
version: 3
|
||||
rules:
|
||||
- port:
|
||||
number: 80
|
||||
backend:
|
||||
service:
|
||||
name: httpd
|
||||
port:
|
||||
number: 80
|
||||
|
||||
- name: ingress
|
||||
k8s:
|
||||
state: present
|
||||
definition:
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: ca-issuer
|
||||
nginx.ingress.kubernetes.io/enable-cors: "true"
|
||||
nginx.ingress.kubernetes.io/cors-allow-headers: "X-Forwarded-For"
|
||||
name: www
|
||||
namespace: www
|
||||
spec:
|
||||
ingressClassName: nginx
|
||||
rules:
|
||||
- host: www.eom.dev
|
||||
http:
|
||||
paths:
|
||||
- pathType: Prefix
|
||||
path: /
|
||||
backend:
|
||||
service:
|
||||
name: httpd
|
||||
port:
|
||||
number: 80
|
||||
tls:
|
||||
- hosts:
|
||||
- www.eom.dev
|
||||
secretName: www
|
||||
- name: Wait for Apache rollout
|
||||
kubernetes.core.k8s_info:
|
||||
api_version: apps/v1
|
||||
kind: Deployment
|
||||
name: httpd
|
||||
namespace: "{{ www_namespace }}"
|
||||
wait: true
|
||||
wait_timeout: "{{ www_wait_timeout }}"
|
||||
wait_condition:
|
||||
type: Available
|
||||
status: "True"
|
||||
when: not ansible_check_mode
|
||||
|
||||
|
||||
Executable
+195
@@ -0,0 +1,195 @@
|
||||
---
|
||||
- name: Merge public membership environment overrides
|
||||
tags: [membership_validate]
|
||||
no_log: true
|
||||
ansible.builtin.set_fact:
|
||||
www_membership_environment: "{{ www_membership_environment | combine(www_membership_environment_overrides) }}"
|
||||
|
||||
- name: Validate membership deployment settings
|
||||
tags: [membership_validate]
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- www_membership_image | length > 0
|
||||
- not www_membership_image.endswith(':latest')
|
||||
- "www_membership_image is search('(:[^/]+$|@sha256:[a-f0-9]{64}$)')"
|
||||
- www_membership_environment is mapping
|
||||
- www_membership_tiers is mapping
|
||||
- www_membership_environment.MEMBERSHIP_PUBLIC_URL == 'https://' ~ www_hostname
|
||||
- www_membership_environment.MEMBERSHIP_ALLOW_HTTP | string == '0'
|
||||
- www_membership_environment.MEMBERSHIP_TRUST_PROXY | string == '1'
|
||||
- www_membership_environment.MEMBERSHIP_TIERS_FILE == '/etc/membership/tiers/tiers.json'
|
||||
- "'MEMBERSHIP_SQLITE_PATH' not in www_membership_environment"
|
||||
- "'MEMBERSHIP_TIERS_JSON' not in www_membership_environment"
|
||||
- www_membership_replicas | int >= 1
|
||||
- www_membership_worker_interval | int >= 10
|
||||
- www_membership_environment.LDAP_PASSWORD_SCHEME == '{ARGON2}'
|
||||
- www_membership_environment.LDAP_BIND_DN | length > 0
|
||||
- www_membership_environment.EMAIL_HOST | length > 0
|
||||
- www_membership_environment.EMAIL_USE_TLS | string == '1'
|
||||
- www_membership_environment.DEX_ISSUER is match('^https://[^/@]+')
|
||||
- www_membership_environment.LDAP_URL is match('^ldaps?://[^/@]+')
|
||||
- not www_membership_shkeeper_enabled or (www_membership_environment.SHKEEPER_URL is match('^https://[^/@]+') and www_membership_environment.SHKEEPER_CURRENCIES | length > 0)
|
||||
- www_membership_shkeeper_enabled or (www_membership_environment.SHKEEPER_URL | length == 0 and www_membership_environment.SHKEEPER_CURRENCIES | length == 0)
|
||||
- not www_membership_stripe_enabled or www_membership_environment.STRIPE_PORTAL_CONFIGURATION | length > 0
|
||||
- not www_membership_postgres_managed or www_membership_environment.POSTGRES_HOST == 'membership-db'
|
||||
- www_membership_postgres_managed or www_membership_environment.POSTGRES_SSLMODE == 'verify-full'
|
||||
- not www_membership_postgres_managed or www_membership_environment.POSTGRES_PORT | string == '5432'
|
||||
fail_msg: Supply a versioned membership image and configure verified LDAP, SMTP, Dex, database and payment settings (see role README).
|
||||
|
||||
- name: Validate optional CA file mount paths
|
||||
tags: [membership_validate]
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- item.value | length == 0 or (www_membership_ca_secret_name | length > 0 and item.value is match('^/etc/membership/ca/[A-Za-z0-9._-]+$'))
|
||||
fail_msg: CA paths must refer to filenames in the configured Secret mounted at /etc/membership/ca/.
|
||||
loop: "{{ www_membership_environment | dict2items | selectattr('key', 'in', ['LDAP_CA_CERT_FILE', 'SHKEEPER_CA_CERT_FILE', 'PGSSLROOTCERT', 'REQUESTS_CA_BUNDLE']) | list }}"
|
||||
|
||||
- name: Define secret-only settings
|
||||
tags: [membership_validate]
|
||||
ansible.builtin.set_fact:
|
||||
_www_secret_keys: [MEMBERSHIP_SECRET_KEY, POSTGRES_PASSWORD, DEX_CLIENT_SECRET, LDAP_BIND_PASSWORD, EMAIL_HOST_PASSWORD, STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, SHKEEPER_API_KEY]
|
||||
_www_required_secret_keys: "{{ ['MEMBERSHIP_SECRET_KEY', 'POSTGRES_PASSWORD', 'DEX_CLIENT_SECRET', 'LDAP_BIND_PASSWORD'] + (['STRIPE_SECRET_KEY', 'STRIPE_WEBHOOK_SECRET'] if www_membership_stripe_enabled else []) + (['SHKEEPER_API_KEY'] if www_membership_shkeeper_enabled else []) }}"
|
||||
|
||||
- name: Prevent secrets in public ConfigMaps
|
||||
tags: [membership_validate]
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- www_membership_environment.keys() | intersect(_www_secret_keys) | length == 0
|
||||
fail_msg: Put credentials in the Kubernetes Secret, never www_membership_environment.
|
||||
no_log: true
|
||||
|
||||
- name: Validate optional Vault-provided secret data
|
||||
tags: [membership_validate]
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- www_membership_secret_data is mapping
|
||||
- www_membership_secret_data.keys() | difference(_www_secret_keys) | length == 0
|
||||
- _www_required_secret_keys | difference(www_membership_secret_data.keys()) | length == 0
|
||||
- www_membership_secret_data.MEMBERSHIP_SECRET_KEY | length >= 32
|
||||
fail_msg: Provide the documented secret keys using Vault or a pre-existing Kubernetes Secret.
|
||||
when: www_membership_secret_data | length > 0
|
||||
no_log: true
|
||||
|
||||
- name: Create membership Secret only when explicitly supplied
|
||||
kubernetes.core.k8s:
|
||||
state: present
|
||||
definition:
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: "{{ www_membership_secret_name }}"
|
||||
namespace: "{{ www_namespace }}"
|
||||
type: Opaque
|
||||
data: "{{ dict(www_membership_secret_data.keys() | zip(www_membership_secret_data.values() | map('b64encode'))) }}"
|
||||
when: www_membership_secret_data | length > 0
|
||||
no_log: true
|
||||
diff: false
|
||||
|
||||
- name: Read deployed Secret for validation and rollout tracking
|
||||
kubernetes.core.k8s_info:
|
||||
api_version: v1
|
||||
kind: Secret
|
||||
name: "{{ www_membership_secret_name }}"
|
||||
namespace: "{{ www_namespace }}"
|
||||
register: _www_membership_secret
|
||||
no_log: true
|
||||
when: not ansible_check_mode
|
||||
|
||||
- name: Validate deployed secret keys without exposing values
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- _www_membership_secret.resources | length == 1
|
||||
- _www_required_secret_keys | difference(_www_membership_secret.resources[0].data.keys()) | length == 0
|
||||
- _www_membership_secret.resources[0].data.keys() | difference(_www_secret_keys) | length == 0
|
||||
- (_www_membership_secret.resources[0].data.MEMBERSHIP_SECRET_KEY | b64decode | length) >= 32
|
||||
fail_msg: The membership Secret is missing or contains invalid keys. See the role README.
|
||||
no_log: true
|
||||
when: not ansible_check_mode
|
||||
|
||||
- name: Validate mandatory secret values are nonempty
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- _www_membership_secret.resources[0].data[item] | b64decode | length > 0
|
||||
loop: "{{ _www_required_secret_keys }}"
|
||||
no_log: true
|
||||
when: not ansible_check_mode
|
||||
|
||||
- name: Read optional CA Secret revision
|
||||
kubernetes.core.k8s_info:
|
||||
api_version: v1
|
||||
kind: Secret
|
||||
name: "{{ www_membership_ca_secret_name }}"
|
||||
namespace: "{{ www_namespace }}"
|
||||
register: _www_membership_ca
|
||||
no_log: true
|
||||
when:
|
||||
- not ansible_check_mode
|
||||
- www_membership_ca_secret_name | length > 0
|
||||
|
||||
- name: Require the configured CA Secret to exist
|
||||
ansible.builtin.assert:
|
||||
that: _www_membership_ca.resources | length == 1
|
||||
when:
|
||||
- not ansible_check_mode
|
||||
- www_membership_ca_secret_name | length > 0
|
||||
no_log: true
|
||||
|
||||
- name: Compute configuration revision without secret contents
|
||||
# resourceVersion changes trigger a rollout without exposing secret values.
|
||||
ansible.builtin.set_fact:
|
||||
_www_membership_revision: "{{ {'image': www_membership_image, 'environment': www_membership_environment, 'tiers': www_membership_tiers, 'secret_name': www_membership_secret_name, 'secret_version': (_www_membership_secret.resources[0].metadata.resourceVersion if not ansible_check_mode else 'check'), 'ca_name': www_membership_ca_secret_name, 'ca_version': (_www_membership_ca.resources[0].metadata.resourceVersion if www_membership_ca_secret_name | length > 0 and not ansible_check_mode else ''), 'rollout': www_rollout_revision} | to_json(sort_keys=True) | hash('sha256') }}"
|
||||
no_log: true
|
||||
|
||||
- name: Deploy public membership configuration and network isolation
|
||||
kubernetes.core.k8s:
|
||||
state: present
|
||||
definition: "{{ lookup('ansible.builtin.template', 'membership-config.yml.j2') | from_yaml_all | list }}"
|
||||
|
||||
- name: Deploy persistent membership PostgreSQL
|
||||
kubernetes.core.k8s:
|
||||
state: present
|
||||
definition: "{{ lookup('ansible.builtin.template', 'membership-postgres.yml.j2') | from_yaml_all | list }}"
|
||||
when: www_membership_postgres_managed | bool
|
||||
|
||||
- name: Wait for membership PostgreSQL
|
||||
kubernetes.core.k8s_info:
|
||||
api_version: apps/v1
|
||||
kind: StatefulSet
|
||||
name: membership-db
|
||||
namespace: "{{ www_namespace }}"
|
||||
register: _www_postgres
|
||||
until: _www_postgres.resources | length == 1 and (_www_postgres.resources[0].status.readyReplicas | default(0) | int) == 1
|
||||
retries: "{{ (www_wait_timeout | int / 5) | int }}"
|
||||
delay: 5
|
||||
when:
|
||||
- www_membership_postgres_managed | bool
|
||||
- not ansible_check_mode
|
||||
|
||||
- name: Apply versioned database migration Job and wait for completion
|
||||
kubernetes.core.k8s:
|
||||
state: present
|
||||
definition: "{{ lookup('ansible.builtin.template', 'membership-migrate.yml.j2') | from_yaml }}"
|
||||
wait: "{{ not ansible_check_mode }}"
|
||||
wait_timeout: "{{ www_wait_timeout }}"
|
||||
wait_condition:
|
||||
type: Complete
|
||||
status: "True"
|
||||
|
||||
- name: Deploy membership web service and reconciliation worker
|
||||
kubernetes.core.k8s:
|
||||
state: present
|
||||
definition: "{{ lookup('ansible.builtin.template', 'membership-workloads.yml.j2') | from_yaml_all | list }}"
|
||||
|
||||
- name: Wait for membership web and worker rollouts
|
||||
kubernetes.core.k8s_info:
|
||||
api_version: apps/v1
|
||||
kind: Deployment
|
||||
name: "{{ item }}"
|
||||
namespace: "{{ www_namespace }}"
|
||||
wait: true
|
||||
wait_timeout: "{{ www_wait_timeout }}"
|
||||
wait_condition:
|
||||
type: Available
|
||||
status: "True"
|
||||
loop: [membership, membership-worker]
|
||||
when: not ansible_check_mode
|
||||
Reference in New Issue
Block a user