Skip to content
This repository was archived by the owner on Oct 3, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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
23 changes: 23 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates

version: 2
updates:
# Enable version updates for npm
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
- package-ecosystem: "npm"
directory: "/frontend/"
schedule:
interval: "weekly"

# Enable version updates for pip
- package-ecosystem: "pip"
directory: "/backend/"
# Check for updates once a week
schedule:
interval: "weekly"
59 changes: 39 additions & 20 deletions .github/workflows/project.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,6 @@ env:
STAGING_BRANCH: '${{ github.event.pull_request.head.ref }}$STAGING_SUFFIX'
DEFAULT_BRANCH_CHAR_LIMIT: 28

SLEEP: 5

BACKEND_PATH: "api/v1/environment"

BLACKFIRE_CLIENT_ID: '${{ secrets.DEVREL_USER_BLACKFIRE_CLIENT_ID }}'
Expand All @@ -50,6 +48,15 @@ jobs:
steps:
################################################################################################
# A. Setup Upsun CLI, variables, & other tools.
- name: "[Debug] Verify environment variable"
run: |
echo "Token length is: ${{ env.UPSUN_CLI_TOKEN && 'non-empty' || 'empty' }}"
if [ -n "${{ env.UPSUN_CLI_TOKEN }}" ]; then
echo "UPSUN_CLI_TOKEN is set."
else
echo "::error::UPSUN_CLI_TOKEN is NOT set."
exit 1
fi
- name: "[tools] 1. Retrieve local files."
uses: actions/checkout@v4
with:
Expand Down Expand Up @@ -135,6 +142,18 @@ jobs:

################################################################################################
# D. Create project. Connect to local repo. Provide access to bot user to the project.
- name: "[clean up old project] Ensure old test runs are deleted"
run: |
if [[ "$PROJECT_TITLE" =~ ^Demo\ Test\ Run\ \(pr-[0-9]+/merge\)$ ]]; then
projectIds="$(upsun project:list --title "$PROJECT_TITLE" --format plain --no-header --columns id 2>/dev/null | paste -sd, -)"
if [ -n "$projectIds" ]; then
upsun multi project:delete -p "$projectIds" -- --yes
else
echo "No existing test runs named: $PROJECT_TITLE. Ready to go!"
fi
else
echo "Skipping cleanup: PROJECT_TITLE does not match 'Demo Test Run (pr-###/merge)'"
fi
- name: "[create_project] 1. Create a project in the test organization."
working-directory: ${{ env.PROJECT_LOCALDIR }}
run: |
Expand Down Expand Up @@ -196,19 +215,19 @@ jobs:
# working-directory: ${{ env.PROJECT_LOCALDIR }}
# run: |
# ./utils/tests/activity_outcome.sh ${{ steps.branch_names.outputs.default_branch }} environment_type.access.create result success "add_bot_user"
- name: "[create_project] 10. Test: verify user has been granted access to project."
working-directory: ${{ env.PROJECT_LOCALDIR }}
run: |
ACTIVITY_TYPE="environment_type.access.create"
ACTIVITY_ID=$(upsun activity:list --type $ACTIVITY_TYPE --limit 1 --no-header --columns=id --format plain)
EXPECTED=$(upsun activity:get $ACTIVITY_ID -P parameters.target)
RESULT=$(upsun auth:info id)
./$TEST_PATH/compare_strings.sh "$RESULT" "$EXPECTED" "bot_user_add access"
ACTIVITY_TYPE="environment_type.access.create"
ACTIVITY_ID=$(upsun activity:list --type $ACTIVITY_TYPE --limit 1 --no-header --columns=id --format plain)
EXPECTED=$(upsun activity:get $ACTIVITY_ID -P parameters.role)
RESULT="admin"
./$TEST_PATH/compare_strings.sh "$RESULT" "$EXPECTED" "bot_user_add role"
# - name: "[create_project] 10. Test: verify user has been granted access to project."
# working-directory: ${{ env.PROJECT_LOCALDIR }}
# run: |
# ACTIVITY_TYPE="environment_type.access.create"
# ACTIVITY_ID=$(upsun activity:list --type $ACTIVITY_TYPE --limit 1 --no-header --columns=id --format plain)
# EXPECTED=$(upsun activity:get $ACTIVITY_ID -P parameters.target)
# RESULT=$(upsun auth:info id)
# ./$TEST_PATH/compare_strings.sh "$RESULT" "$EXPECTED" "bot_user_add access"
# ACTIVITY_TYPE="environment_type.access.create"
# ACTIVITY_ID=$(upsun activity:list --type $ACTIVITY_TYPE --limit 1 --no-header --columns=id --format plain)
# EXPECTED=$(upsun activity:get $ACTIVITY_ID -P parameters.role)
# RESULT="admin"
# ./$TEST_PATH/compare_strings.sh "$RESULT" "$EXPECTED" "bot_user_add role"

################################################################################################
# E. First code deploy.
Expand Down Expand Up @@ -236,7 +255,7 @@ jobs:
cmd=$(cat frontend/src/commands.json | jq -r ".first_deploy.user.get_url")
eval "$cmd"
URL=$(eval "$cmd --pipe")
sleep $SLEEP
sleep 2
blackfire-player run blackfire.yaml --endpoint="$URL" --variable step=branch -vvv

################################################################################################
Expand All @@ -258,7 +277,7 @@ jobs:
working-directory: ${{ env.PROJECT_LOCALDIR }}
run: |
URL=$(upsun url --primary --pipe)
sleep $SLEEP
sleep 2
blackfire-player run blackfire.yaml --endpoint="$URL" --variable step=redis -vvv
- name: "[environment_branch] 5. Update staging environment name."
working-directory: ${{ env.PROJECT_LOCALDIR }}
Expand Down Expand Up @@ -289,7 +308,7 @@ jobs:
working-directory: ${{ env.PROJECT_LOCALDIR }}
run: |
URL=$(upsun url --primary --pipe)
sleep $SLEEP
sleep 2
blackfire-player run blackfire.yaml --endpoint="$URL" --variable step=merge-production -vvv

################################################################################################
Expand Down Expand Up @@ -332,7 +351,7 @@ jobs:
git checkout ${{ steps.branch_names.outputs.default_branch }}
git branch
URL=$(upsun url -e ${{ steps.branch_names.outputs.default_branch }} --primary --pipe)
sleep $SLEEP
sleep 2
blackfire-player run blackfire.yaml --endpoint="$URL" --variable step=complete -vvv

################################################################################################
Expand All @@ -357,7 +376,7 @@ jobs:
git checkout ${{ steps.branch_names.outputs.default_branch }}
git branch
URL=$(upsun url -e ${{ steps.branch_names.outputs.default_branch }} --primary --pipe)
sleep $SLEEP
sleep 2
blackfire-player run blackfire.yaml --endpoint="$URL" --variable step=scale -vvv

################################################################################################
Expand Down
33 changes: 25 additions & 8 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ jobs:
matrix:
# These versions match Upsun support
# Node.js: https://docs.upsun.com/languages/nodejs.html#supported-versions
node-version: [18.x, 20.x, 22.x]

node-version: [18.x, 20.x, 21.x]
# Python: https://docs.upsun.com/languages/python.html#supported-versions
python-version: ['3.9', '3.10', '3.11', '3.12']

steps:
################################################################################################
# A. Setup workflow.
Expand All @@ -31,26 +33,41 @@ jobs:
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- name: "3. Python."
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'

################################################################################################
# B. Prettify, lint, and test repo.
- name: "3. Preparing"
- name: "4. Preparing"
run: |
echo "::notice::Running react-scripts tests."
export CI=true
npm install cross-env npm-run-all -g
npm install
- name: "4. Verifying frontend code is pretty"
- name: "5. Verifying backend code is pretty"
run: |
npm run prettier:backend
- name: "6. Verifying frontend code is pretty"
run: |
npm run prettier:frontend
- name: "5. Linting frontend"
- name: "7. Linting frontend"
run: npm run lint:frontend
- name: "6. Run Frontend tests"
- name: "8. Run Frontend tests"
run: npm run test:frontend
- name: "9. Run Backend linting"
run: |
npm run lint:backend

################################################################################################
# C. Ensure no vulnerabilities.
- name: "7. Test: there should be no HIGH Node.js vulnerabilities."
- name: "10. Test: there should be no Python vulnerabilities."
run: |
echo "::notice::Checking for vulnerabilities in backend Python app dependencies."
npm run test:backend
- name: "11. Test: there should be no HIGH Node.js vulnerabilities."
run: |
echo "::notice::Checking for high vulnerabilities in frontend Node.js app dependencies."
cd frontend
Expand All @@ -64,7 +81,7 @@ jobs:
else
echo "::notice::No HIGH vulnerabilities found on frontend app."
fi
- name: "8. Test: there should be no CRITICAL Node.js vulnerabilities."
- name: "12. Test: there should be no CRITICAL Node.js vulnerabilities."
run: |
echo "::notice::Checking for critical vulnerabilities in frontend Node.js app dependencies."
cd frontend
Expand Down
46 changes: 36 additions & 10 deletions .upsun/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ applications:

type: "nodejs:18"

build:
flavor: none

variables:
env:
REACT_APP_BACKEND_URL: "/"
Expand All @@ -23,8 +26,9 @@ applications:
npm install
export REACT_APP_PROJECT_ID=$PLATFORM_PROJECT
npm run build
deploy: |
bash scripts/api.sh

relationships:
api: "backend:http"

web:
locations:
Expand All @@ -39,26 +43,43 @@ applications:
static\/*:
expires: 365d

mounts:
/build/api/v1/environment:
source: storage
source_path: api
# App 2: Backend (Python)
backend:

source:
root: "backend"

type: "python:3.12"

build:
flavor: none
hooks:
build: |
set -eux
pip install -r requirements.txt --disable-pip-version-check

web:
commands:
start: "gunicorn main:app"
upstream:
socket_family: tcp
locations:
/:
passthru: true

# #add_service_start
# ######################################################################################################################
# # Step 3: Add a service. Uncomment this section.
# ######################################################################################################################
# relationships:
# redis_session:
# service: "redis_service"
# endpoint: "redis"
# redis_session: "redis_service:redis"

# services:
# redis_service:
# type: "redis:7.0"
# ######################################################################################################################
# #add_service_end

# Routes configuration.
routes:
"https://{default}/":
Expand All @@ -71,3 +92,8 @@ routes:
id: frontend-redirect
type: redirect
to: "https://{default}/"

"https://{default}/api/":
id: backend
type: upstream
upstream: "backend:http"
22 changes: 22 additions & 0 deletions backend/.env.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Local
PLATFORM_ENVIRONMENT_TYPE=production

########################################
#### PLATFORM_RELATIONSHIPS Test Scenarios

# Has database and rediscache services
# PLATFORM_RELATIONSHIPS='eyAicmVkaXMtc2Vzc2lvbiI6IFsgeyAidXNlcm5hbWUiOiBudWxsLCAic2NoZW1lIjogInJlZGlzIiwgInNlcnZpY2UiOiAiY2FjaGUiLCAiZnJhZ21lbnQiOiBudWxsLCAiaXAiOiAiMTY5LjI1NC4xNi4xMTMiLCAiaG9zdG5hbWUiOiAiMnR6eXB3M24yZ3preGJhNmlsY2VkYmNmZ3UuY2FjaGUuc2VydmljZS5fLmV1LTMucGxhdGZvcm1zaC5zaXRlIiwgInBvcnQiOiA2Mzc5LCAiY2x1c3RlciI6ICJwcno2Yms0amltZmFpLW1haW4tYnZ4ZWE2aSIsICJob3N0IjogInJlZGlzLXNlc3Npb24uaW50ZXJuYWwiLCAicmVsIjogInJlZGlzIiwgInBhdGgiOiBudWxsLCAicXVlcnkiOiB7fSwgInBhc3N3b3JkIjogbnVsbCwgInR5cGUiOiAicmVkaXMtcGVyc2lzdGVudDo3LjAiLCAicHVibGljIjogZmFsc2UsICJob3N0X21hcHBlZCI6IGZhbHNlIH0gXSB9'

# Has no relationships
PLATFORM_RELATIONSHIPS=

########################################
#### PLATFORM_APPLICATION Test Scenarios

## instance_count: null
PLATFORM_APPLICATION='eyAgICAgInJlc291cmNlcyI6IG51bGwsICAgICAic2l6ZSI6ICJBVVRPIiwgICAgICJkaXNrIjogMjA0OCwgICAgICJhY2Nlc3MiOiB7ICAgICAgICJzc2giOiAiY29udHJpYnV0b3IiICAgICB9LCAgICAgInJlbGF0aW9uc2hpcHMiOiB7fSwgICAgICJhZGRpdGlvbmFsX2hvc3RzIjoge30sICAgICAibW91bnRzIjoge30sICAgICAidGltZXpvbmUiOiBudWxsLCAgICAgInZhcmlhYmxlcyI6IHsgICAgICAgImVudiI6IHsgICAgICAgICAiTl9QUkVGSVgiOiAiL2FwcC8uZ2xvYmFsIiAgICAgICB9ICAgICB9LCAgICAgImZpcmV3YWxsIjogbnVsbCwgICAgICJpbml0aWFsX3NpemUiOiBudWxsLCAgICAgImNvbnRhaW5lcl9wcm9maWxlIjogbnVsbCwgICAgICJvcGVyYXRpb25zIjoge30sICAgICAibmFtZSI6ICJhcHAiLCAgICAgInR5cGUiOiAicHl0aG9uOjMuMTEiLCAgICAgInJ1bnRpbWUiOiB7fSwgICAgICJwcmVmbGlnaHQiOiB7ICAgICAgICJlbmFibGVkIjogdHJ1ZSwgICAgICAgImlnbm9yZWRfcnVsZXMiOiBbXSAgICAgfSwgICAgICJ0cmVlX2lkIjogImNkZGQ3ZWIzZjZkYWNjNGNmOWVmZmU4Mjg5MjczZjRmMTRjOTdkN2YiLCAgICAgInNsdWdfaWQiOiAieGJsZjZ1amNwYjRsay1hcHAtY2RkZDdlYjNmNmRhY2M0Y2Y5ZWZmZTgyODkyNzNmNGYxNGM5N2Q3Zi1jMDYzZmZkNzdjMGI0MGJmYjM5YTQzMDUwZmFiMzI1Y2RlODkyYTFjIiwgICAgICJhcHBfZGlyIjogIi9hcHAiLCAgICAgImVuZHBvaW50cyI6IHsgICAgICAgImh0dHAiOiB7ICAgICAgICAgInNjaGVtZSI6ICJodHRwIiwgICAgICAgICAicG9ydCI6IDgwICAgICAgIH0sICAgICAgICJzc2giOiB7ICAgICAgICAgInNjaGVtZSI6ICJzc2giLCAgICAgICAgICJwb3J0IjogMjIgICAgICAgfSAgICAgfSwgICAgICJ3ZWIiOiB7ICAgICAgICJsb2NhdGlvbnMiOiB7fSwgICAgICAgIm1vdmVfdG9fcm9vdCI6IGZhbHNlICAgICB9LCAgICAgImhvb2tzIjogeyAgICAgICAiYnVpbGQiOiAiIiwgICAgICAgImRlcGxveSI6ICIiLCAgICAgICAicG9zdF9kZXBsb3kiOiBudWxsICAgICB9LCAgICAgImNyb25zIjoge30sICAgICAiaW5zdGFuY2VfY291bnQiOiBudWxsICAgfQ=='

## instance_count: 5
# PLATFORM_APPLICATION='eyAgICAgInJlc291cmNlcyI6IG51bGwsICAgICAic2l6ZSI6ICJBVVRPIiwgICAgICJkaXNrIjogMjA0OCwgICAgICJhY2Nlc3MiOiB7ICAgICAgICJzc2giOiAiY29udHJpYnV0b3IiICAgICB9LCAgICAgInJlbGF0aW9uc2hpcHMiOiB7fSwgICAgICJhZGRpdGlvbmFsX2hvc3RzIjoge30sICAgICAibW91bnRzIjoge30sICAgICAidGltZXpvbmUiOiBudWxsLCAgICAgInZhcmlhYmxlcyI6IHsgICAgICAgImVudiI6IHsgICAgICAgICAiTl9QUkVGSVgiOiAiL2FwcC8uZ2xvYmFsIiAgICAgICB9ICAgICB9LCAgICAgImZpcmV3YWxsIjogbnVsbCwgICAgICJpbml0aWFsX3NpemUiOiBudWxsLCAgICAgImNvbnRhaW5lcl9wcm9maWxlIjogbnVsbCwgICAgICJvcGVyYXRpb25zIjoge30sICAgICAibmFtZSI6ICJhcHAiLCAgICAgInR5cGUiOiAicHl0aG9uOjMuMTEiLCAgICAgInJ1bnRpbWUiOiB7fSwgICAgICJwcmVmbGlnaHQiOiB7ICAgICAgICJlbmFibGVkIjogdHJ1ZSwgICAgICAgImlnbm9yZWRfcnVsZXMiOiBbXSAgICAgfSwgICAgICJ0cmVlX2lkIjogImNkZGQ3ZWIzZjZkYWNjNGNmOWVmZmU4Mjg5MjczZjRmMTRjOTdkN2YiLCAgICAgInNsdWdfaWQiOiAieGJsZjZ1amNwYjRsay1hcHAtY2RkZDdlYjNmNmRhY2M0Y2Y5ZWZmZTgyODkyNzNmNGYxNGM5N2Q3Zi1jMDYzZmZkNzdjMGI0MGJmYjM5YTQzMDUwZmFiMzI1Y2RlODkyYTFjIiwgICAgICJhcHBfZGlyIjogIi9hcHAiLCAgICAgImVuZHBvaW50cyI6IHsgICAgICAgImh0dHAiOiB7ICAgICAgICAgInNjaGVtZSI6ICJodHRwIiwgICAgICAgICAicG9ydCI6IDgwICAgICAgIH0sICAgICAgICJzc2giOiB7ICAgICAgICAgInNjaGVtZSI6ICJzc2giLCAgICAgICAgICJwb3J0IjogMjIgICAgICAgfSAgICAgfSwgICAgICJ3ZWIiOiB7ICAgICAgICJsb2NhdGlvbnMiOiB7fSwgICAgICAgIm1vdmVfdG9fcm9vdCI6IGZhbHNlICAgICB9LCAgICAgImhvb2tzIjogeyAgICAgICAiYnVpbGQiOiAiIiwgICAgICAgImRlcGxveSI6ICIiLCAgICAgICAicG9zdF9kZXBsb3kiOiBudWxsICAgICB9LCAgICAgImNyb25zIjoge30sICAgICAiaW5zdGFuY2VfY291bnQiOiA1ICAgfQo='

PORT=8000
3 changes: 3 additions & 0 deletions backend/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
env
.env
__pycache__/
66 changes: 66 additions & 0 deletions backend/app/routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
"""
This module sets defines the routes for your Flask application
"""

import os
import base64
import json
from flask import Blueprint, jsonify

bp = Blueprint("routes", __name__)

SERVICE_RELATIONSHIP = "redis_session"

API_PREFIX = "/api/v1"


@bp.route(f"{API_PREFIX}/environment")
def environment():
"""
Returns the environment type and sessions storage type as json
"""
return jsonify(
type=get_platform_environment(), session_storage=get_session_storage_type()
)


@bp.route("/api/")
def home():
"""
Returns a welcome message for the home route.
"""
return "Hello from the Python backend!"


def get_session_storage_type():
"""
Returns the type of session storage from the PLATFORM_RELATIONSHIPS environment variable.
If the variable is not set or if it does not contain the SERVICE_RELATIONSHIP, it returns "file"
If the variable is set and contains the SERVICE_RELATIONSHIP, it returns "redis".
If there is an error decoding the PLATFORM_RELATIONSHIPS variable, it returns "file".
"""
platform_relationships_data = os.environ.get("PLATFORM_RELATIONSHIPS")

if not platform_relationships_data:
return "file"

try:
platform_relationships = json.loads(
base64.b64decode(platform_relationships_data)
)

if SERVICE_RELATIONSHIP in platform_relationships:
return "redis"

return "file"
except (json.JSONDecodeError, TypeError, ValueError):
# Catching potential exceptions due to invalid JSON or other issues
return "file"


def get_platform_environment():
"""
Returns the type of the environment from PLATFORM_ENVIRONMENT_TYPE environment variable.
If the variable is not set, it returns "local".
"""
return os.environ.get("PLATFORM_ENVIRONMENT_TYPE", "local")
Loading
Loading