Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
b8316db
wip: added charm config
samhotep Jan 28, 2025
ac9761c
wip: added charm config
samhotep Jan 28, 2025
ed02220
fix: start even if directory api is unreachable
samhotep Jan 28, 2025
4296123
chore: update linter to exclude charm/*
samhotep Jan 28, 2025
0426120
fix: bug when checking for existing webpages
samhotep Jan 28, 2025
64d38ba
feat: revert to filecache if redis not available
samhotep Feb 18, 2025
077ccf0
chore: removed default debug in local
samhotep Feb 18, 2025
322c5bc
fix: run periodic tasks on celery configure
samhotep Feb 18, 2025
47392b3
feat: added juju deployment CI
samhotep Feb 19, 2025
d2145a4
fix: use redis for ci
samhotep Feb 19, 2025
160a021
fix: create rock directories
samhotep Feb 19, 2025
775ac88
feat: removed git dependency
samhotep Feb 21, 2025
4aaf9e5
feat: Added copydoctemplate id
samhotep Feb 24, 2025
ffbca64
chore: lint python
samhotep Feb 24, 2025
7151b46
fix: name controller in deployment
samhotep Feb 24, 2025
c90d6f0
feat: auto rollback on error
samhotep Feb 27, 2025
fbc6600
feat: directly import google credentials
samhotep Feb 27, 2025
eafb518
feat: use scoped sessions
samhotep Feb 27, 2025
1778b25
fix: updated juju deploy controller
samhotep Feb 27, 2025
1ae4e53
fix: update vault read controller
samhotep Feb 27, 2025
4092aeb
feat: default to flask_ prefix variables
samhotep Feb 27, 2025
6e1fcd8
feat: ping jira server on startup and report
samhotep Feb 27, 2025
f22fc28
chore: remove unused var
samhotep Feb 27, 2025
8baef15
feat: add google auth check on startup
samhotep Feb 27, 2025
ae265f3
feat: added prod deployment
samhotep Mar 4, 2025
3eca5e9
feat: updated repo directory perms
samhotep Mar 4, 2025
1d01a3d
feat: make redis optional in charm
samhotep Mar 4, 2025
b75ef3f
feat: reload tree if webpages are incomplete
samhotep Mar 4, 2025
8f829b0
feat: return new tree if pages are incomplete
samhotep Mar 4, 2025
95efcac
feat: reload incomplete tree root
samhotep Mar 4, 2025
8f14b63
chore: lint
samhotep Mar 5, 2025
12fead6
feat: run deployment job on main branch
samhotep Mar 5, 2025
f2c22da
chore: increased delay before image ping
samhotep Mar 5, 2025
2a9b822
chore: run ci in debug mode
samhotep Mar 5, 2025
8bb8dbc
feat: added native async tasks
samhotep Mar 7, 2025
d1c8e5e
chore: fix linting
samhotep Mar 7, 2025
d39afd7
fix: return celery fn if celery is active
samhotep Mar 11, 2025
2f935bd
chore: log commit
samhotep Mar 19, 2025
4e8a860
fix: github cleanup
samhotep Mar 19, 2025
a49610a
chore: final fix
samhotep Mar 19, 2025
72caa1b
fix: fix fix
samhotep Mar 19, 2025
f9fb4c0
fix:fix fix fix ! promise!!
samhotep Mar 19, 2025
c6efb7f
fix: create templates folder if doesnt exist on clone
samhotep Mar 19, 2025
4e96091
fix: dont create files as directorues
samhotep Mar 19, 2025
169b160
fix: update paths
samhotep Mar 19, 2025
87d8754
fix: plural
samhotep Mar 19, 2025
b930971
Fix: file path conversions between str and Path obj
muhammad-ali-pk Mar 20, 2025
2d969f6
temporarily add multiple public teams for SSO
muhammad-ali-pk Mar 21, 2025
f67361f
fix: deleting a newly published webpage returns error
muhammad-ali-pk Mar 25, 2025
877ec09
fix: creating a webpage fail copydoc creation
muhammad-ali-pk Mar 25, 2025
177011f
added jira reporter id variable in env and config
muhammad-ali-pk Apr 3, 2025
22bcf03
add variable to settings.py
muhammad-ali-pk Apr 3, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions .env
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PORT=8104
SECRET_KEY=secret_key
VALKEY_HOST=localhost
VALKEY_PORT=6379
REDIS_HOST=localhost
REDIS_PORT=6379
GH_TOKEN=token
REPO_ORG=https://github.com/canonical
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/content
Expand All @@ -17,4 +17,6 @@ GOOGLE_CREDENTIALS=googlecreds
GOOGLE_DRIVE_FOLDER_ID=googlecreds
COPYDOC_TEMPLATE_ID=googlecreds
GOOGLE_PRIVATE_KEY=base64encodedprivatekey
GOOGLE_PRIVATE_KEY_ID=privatekeyid
GOOGLE_PRIVATE_KEY_ID=privatekeyid
RABBITMQ_URI=amqp://guest:guest@localhost:5672/
JIRA_REPORTER_ID=jira-reporter-id
29 changes: 16 additions & 13 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -94,17 +94,19 @@ jobs:
name: Run Python
runs-on: ubuntu-latest
services:
valkey:
image: valkey/valkey
redis:
image: redis
options: >-
--health-cmd "valkey-cli ping"
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
postgres:
image: postgres:latest
env:
POSTGRES_PASSWORD: postgres
ports:
- 5432:5432
steps:
- uses: actions/checkout@v3
- name: Set up Python 3.12
Expand All @@ -121,9 +123,9 @@ jobs:
GOOGLE_PRIVATE_KEY_ID: ${{ secrets.PRIVATE_KEY_ID }}
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/postgres
SECRET_KEY: secret_key
VALKEY_HOST: localhost
VALKEY_PORT: 6379
GH_TOKEN: token
REDIS_HOST: localhost
REDIS_PORT: 6379
GH_TOKEN: ${{ github.token }}
REPO_ORG: https://github.com/canonical
JIRA_EMAIL: example@canonical.com
JIRA_TOKEN: jiratoken
Expand All @@ -141,10 +143,10 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 15
services:
valkey:
image: valkey/valkey
redis:
image: redis
options: >-
--health-cmd "valkey-cli ping"
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
Expand All @@ -170,9 +172,9 @@ jobs:
docker run \
-p 8104:8104 \
-e SECRET_KEY=secret_key \
-e VALKEY_HOST=localhost \
-e VALKEY_PORT=6379 \
-e GH_TOKEN=token \
-e REDIS_HOST=localhost \
-e REDIS_PORT=6379 \
-e GH_TOKEN=${{ github.token }} \
-e REPO_ORG=https://github.com/canonical \
-e DATABASE_URL=postgresql://postgres:postgres@localhost:5432/postgres \
-e JIRA_EMAIL=example@canonical.com \
Expand All @@ -184,6 +186,7 @@ jobs:
-e COPYDOC_TEMPLATE_ID=templateid \
-e GOOGLE_PRIVATE_KEY="$GOOGLE_PRIVATE_KEY" \
-e GOOGLE_PRIVATE_KEY_ID="$GOOGLE_PRIVATE_KEY_ID" \
-e FLASK_DEBUG=1 \
--network host \
websites-content-system & sleep 1
websites-content-system & sleep 3
curl --head --fail --retry-delay 1 --retry 30 --retry-connrefused http://localhost
161 changes: 161 additions & 0 deletions .github/workflows/deploy.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
name: Deploy

on:
push:
branches:
- add-charm
- main

env:
CHARMCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS: true
ROCKCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS: true

jobs:
pack-charm:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v3

- name: Setup LXD
uses: canonical/setup-lxd@main

- name: Setup Charmcraft
run: sudo snap install charmcraft --classic --channel=latest/edge

- name: Pack charm
run: charmcraft pack -v --project-dir ./charm

- name: Upload charm
uses: actions/upload-artifact@v4
with:
name: cs-canonical-com-charm
path: ./*.charm

pack-rock:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v3

- name: Use Node.js
uses: actions/setup-node@v3

- name: Build Assets
run: |
yarn install
yarn run build

- name: Setup LXD
uses: canonical/setup-lxd@main

- name: Create repositories directory
run: |
mkdir -m 777 repositories
mkdir -m 777 tree-cache

- name: Setup Rockcraft
run: sudo snap install rockcraft --classic --channel=latest/edge

- name: Pack Rock
run: rockcraft pack

- name: Upload Rock
uses: actions/upload-artifact@v4
with:
name: cs-canonical-com-rock
path: ./*.rock

publish-image:
runs-on: ubuntu-latest
needs: pack-rock
outputs:
image_url: ${{ steps.set_image_url.outputs.image_url }}
steps:
- name: Get Rock
uses: actions/download-artifact@v4
with:
name: cs-canonical-com-rock

- name: Set image URL
id: set_image_url
run: echo "image_url=ghcr.io/canonical/cs.canonical.com:$(date +%s)-${GITHUB_SHA:0:7}" >> $GITHUB_OUTPUT

- name: Push to GHCR
run: skopeo --insecure-policy copy oci-archive:$(ls *.rock) docker://${{ steps.set_image_url.outputs.image_url }} --dest-creds "canonical:${{ secrets.GITHUB_TOKEN }}"

deploy-staging:
runs-on: [self-hosted, self-hosted-linux-amd64-jammy-private-endpoint-medium]
needs: [pack-charm, publish-image]
steps:
- name: Checkout Code
uses: actions/checkout@v3

- name: Install Dependencies
run: |
sudo snap install juju --channel=3.4/stable --classic
sudo snap install vault --classic

- name: Download Charm Artifact
uses: actions/download-artifact@v4
with:
name: cs-canonical-com-charm

- name: Configure Vault and Juju
run: |
export VAULT_ADDR=https://vault.admin.canonical.com:8200
export TF_VAR_login_approle_role_id=${{ secrets.VAULT_APPROLE_ROLE_ID }}
export TF_VAR_login_approle_secret_id=${{ secrets.VAULT_APPROLE_SECRET_ID }}
export VAULT_SECRET_PATH_ROLE=secret/prodstack6/roles/stg-cs-canonical-com
export VAULT_SECRET_PATH_COMMON=secret/prodstack6/juju/common
VAULT_TOKEN=$(vault write -f -field=token auth/approle/login role_id=${TF_VAR_login_approle_role_id} secret_id=${TF_VAR_login_approle_secret_id})
export VAULT_TOKEN
mkdir -p ~/.local/share/juju
vault read -field=controller_config "${VAULT_SECRET_PATH_COMMON}/controllers/juju-controller-36-staging-ps6" | base64 -d > ~/.local/share/juju/controllers.yaml
USERNAME=$(vault read -field=username "${VAULT_SECRET_PATH_ROLE}/juju")
PASSWORD=$(vault read -field=password "${VAULT_SECRET_PATH_ROLE}/juju")
printf "controllers:\n juju-controller-36-staging-ps6:\n user: %s\n password: %s\n" "$USERNAME" "$PASSWORD" > ~/.local/share/juju/accounts.yaml

- name: Deploy Application to staging
run: |
export JUJU_MODEL=admin/stg-cs-canonical-com
juju refresh cs-canonical-com --path ./cs-canonical-com_ubuntu-22.04-amd64.charm --resource flask-app-image=${{ needs.publish-image.outputs.image_url }}
juju wait-for application cs-canonical-com --query='name=="cs-canonical-com" && (status=="active" || status=="idle")'

deploy-production:
runs-on: [self-hosted, self-hosted-linux-amd64-jammy-private-endpoint-medium]
needs: [pack-charm, publish-image]
steps:
- name: Checkout Code
uses: actions/checkout@v3

- name: Install Dependencies
run: |
sudo snap install juju --channel=3.6/stable --classic
sudo snap install vault --classic

- name: Download Charm Artifact
uses: actions/download-artifact@v4
with:
name: cs-canonical-com-charm

- name: Configure Vault and Juju
run: |
export VAULT_ADDR=https://vault.admin.canonical.com:8200
export TF_VAR_login_approle_role_id=${{ secrets.PROD_VAULT_APPROLE_ROLE_ID }}
export TF_VAR_login_approle_secret_id=${{ secrets.PROD_VAULT_APPROLE_SECRET_ID }}
export VAULT_SECRET_PATH_ROLE=secret/prodstack6/roles/prod-cs-canonical-com
export VAULT_SECRET_PATH_COMMON=secret/prodstack6/juju/common
VAULT_TOKEN=$(vault write -f -field=token auth/approle/login role_id=${TF_VAR_login_approle_role_id} secret_id=${TF_VAR_login_approle_secret_id})
export VAULT_TOKEN
mkdir -p ~/.local/share/juju
vault read -field=controller_config "${VAULT_SECRET_PATH_COMMON}/controllers/juju-controller-36-production-ps6" | base64 -d > ~/.local/share/juju/controllers.yaml
USERNAME=$(vault read -field=username "${VAULT_SECRET_PATH_ROLE}/juju")
PASSWORD=$(vault read -field=password "${VAULT_SECRET_PATH_ROLE}/juju")
printf "controllers:\n juju-controller-36-production-ps6:\n user: %s\n password: %s\n" "$USERNAME" "$PASSWORD" > ~/.local/share/juju/accounts.yaml

- name: Deploy Application to production
run: |
export JUJU_MODEL=admin/prod-cs-canonical-com
juju refresh cs-canonical-com --path ./cs-canonical-com_ubuntu-22.04-amd64.charm --resource flask-app-image=${{ needs.publish-image.outputs.image_url }}
juju wait-for application cs-canonical-com --query='name=="cs-canonical-com" && (status=="active" || status=="idle")'
8 changes: 4 additions & 4 deletions .github/workflows/playwright.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ jobs:
runs-on: ubuntu-latest
services:
valkey:
image: valkey/valkey
image: redis
options: >-
--health-cmd "valkey-cli ping"
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
Expand All @@ -27,8 +27,8 @@ jobs:
env:
DISABLE_SSO: True
SECRET_KEY: secret_key
VALKEY_HOST: localhost
VALKEY_PORT: 6379
REDIS_HOST: localhost
REDIS_PORT: 6379
GH_TOKEN: token
REPO_ORG: https://github.com/canonical
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/postgres
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ COPY . .

# Install python and import python dependencies
RUN apt-get update \
&& apt-get install --no-install-recommends --yes ca-certificates git python3-venv python3-pip python3-psycopg2
&& apt-get install --no-install-recommends --yes ca-certificates python3-venv python3-pip python3-psycopg2
RUN python3 -m venv .venv \
&& . .venv/bin/activate \
&& pip install --no-cache-dir -r requirements.txt
Expand Down
54 changes: 47 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
# Template parser backend
# Websites Content System

Backend service for the CMS template parser
This is a portal tailored to managing content on our websites. It's useful for:

- Making change requests on specific pages, and automating the related JIRA overhead
- Assigning owners to individual pages
- Collecting all relevant links for a page in one place:
- copydoc links
- link to github code
- product category

## Getting it running

Expand All @@ -17,8 +24,8 @@ PORT=8104
FLASK_DEBUG=true
SECRET_KEY=secret_key
DEVEL=True
VALKEY_HOST=valkey
VALKEY_PORT=6379
REDIS_HOST=valkey
REDIS_PORT=6379
GH_TOKEN=ghp_somepersonaltoken
REPO_ORG=https://github.com/canonical
DATABASE_URL=postgresql://postgres:postgres@postgres:5432/postgres
Expand Down Expand Up @@ -63,13 +70,15 @@ Else if the value is confidential, you need to first create a secret on the kube
```bash
$ kubectl create secret generic <secret-name> -n production with key1=supersecret and key2=supsecret
```

Make sure to replace `<secret-name>` with the actual name of the secret. For example, `cs-canonical-com`.

2. Verify the newly created secret

```bash
$ kubectl describe secret <secret-name> -n production
```

Make sure to replace `<secret-name>` with the actual name of the secret. For example, `cs-canonical-com`.

3. Add the secret ref to `konf/site.yaml` file.
Expand Down Expand Up @@ -208,8 +217,8 @@ Then modify the .env file, and change the following to match your valkey and pos

```
# .env
VALKEY_HOST=localhost
VALKEY_PORT=6379
REDIS_HOST=localhost
REDIS_PORT=6379
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/postgres
```

Expand All @@ -219,9 +228,15 @@ and load the variables into the shell environment.
$ source .env
```

Start the server.
Start the server. If using rabbitmq or redis, celery will be activated, and you should start the server with the below to ensure background tasks run.

```bash
$ celery -A webapp.app.celery_app worker -B --loglevel=DEBUG
```

Without celery or rabbitmq, you can start with flask to use native task processing.

```bash
$ flask --app webapp/app run --debug
```

Expand Down Expand Up @@ -257,6 +272,31 @@ To ensure hot module reloading, make sure to do the following changes.
- Comment out <code>"process.env.NODE_ENV": '"production"'</code> in vite.config.ts file.
- Run the vite dev server locally, using <code>yarn run dev</code>.

### Background tasks

### Creating tasks

Since we're using a hybrid of celery + native task management, tasks need to be registered before they can be called asynchronously.

1. To create a task, simply add the following to the bottom of tasks.py
```python
some_new_task = register_task(some_new_task)
```

This will attach the correct task runner behind the scenes.


2. Call the task from your Flask route as a normal python function:

```python
from webapp.tasks import some_new_task

@app.route('/call-task')
def some_route():
some_new_task.delay() # async
return 'Task started'
```

### API Requests

#### Getting the website page structure as a JSON tree
Expand Down
1 change: 1 addition & 0 deletions app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from webapp.app import app # noqa F401
Loading
Loading