From a362e1cdfab9a40a23881aa1e9fa022ec7e59f98 Mon Sep 17 00:00:00 2001 From: Weston Clark Date: Wed, 4 Feb 2026 13:24:06 -0500 Subject: [PATCH] test: ci cd pipeline --- .github/actions/deploy-to-ec2/action.yml | 92 ++++++++++++++++ .../install-python-requirements/action.yml | 33 ++++++ .github/workflows/ci.yml | 30 +++++ .github/workflows/deploy.yml | 43 ++++++++ .github/workflows/test-and-deploy.yml | 104 ------------------ .github/workflows/test-cloud-services.yml | 46 ++++++++ .github/workflows/test-web-client.yml | 53 +++++++++ 7 files changed, 297 insertions(+), 104 deletions(-) create mode 100644 .github/actions/deploy-to-ec2/action.yml create mode 100644 .github/actions/install-python-requirements/action.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/deploy.yml delete mode 100644 .github/workflows/test-and-deploy.yml create mode 100644 .github/workflows/test-cloud-services.yml create mode 100644 .github/workflows/test-web-client.yml diff --git a/.github/actions/deploy-to-ec2/action.yml b/.github/actions/deploy-to-ec2/action.yml new file mode 100644 index 0000000..5322ace --- /dev/null +++ b/.github/actions/deploy-to-ec2/action.yml @@ -0,0 +1,92 @@ +name: 'Deploy to EC2' +description: 'Deploy application to EC2 using SSH with venv support' + +inputs: + ec2_pem_key: + description: 'EC2 PEM key content for SSH authentication' + required: true + ec2_host: + description: 'EC2 host address' + required: true + ec2_user: + description: 'EC2 SSH user' + required: true + repo_base_dir: + description: 'Base directory on EC2 where cloud-services and web-client are located' + required: false + default: '/home/ubuntu' + branch: + description: 'Git branch to deploy' + required: false + default: 'main' + +runs: + using: 'composite' + steps: + - name: Configure SSH + shell: bash + run: | + mkdir -p ~/.ssh + echo "${{ inputs.ec2_pem_key }}" > ~/.ssh/ec2_deploy.pem + chmod 600 ~/.ssh/ec2_deploy.pem + cat >> ~/.ssh/config << EOF + Host ec2-deploy + HostName ${{ inputs.ec2_host }} + User ${{ inputs.ec2_user }} + IdentityFile ~/.ssh/ec2_deploy.pem + StrictHostKeyChecking no + EOF + + - name: Deploy to EC2 + shell: bash + run: | + ssh ec2-deploy << 'DEPLOY_EOF' + set -e + + BASE_DIR="${{ inputs.repo_base_dir }}" + BRANCH="${{ inputs.branch }}" + + # Deploy Web Client + echo "Deploying web-client..." + cd "$BASE_DIR/web-client" + + # Update repository + git fetch origin + git checkout "$BRANCH" + git pull origin "$BRANCH" + + # Build web client + npm ci + npm run build + + # Restart frontend service + echo "Restarting frontend service..." + sudo systemctl restart frontend + + # Deploy Cloud Services API with venv + echo "Deploying cloud-services..." + cd "$BASE_DIR/cloud-services" + + # Update repository + git fetch origin + git checkout "$BRANCH" + git pull origin "$BRANCH" + + # Create venv if it doesn't exist + if [ ! -d "venv" ]; then + echo "Creating virtual environment..." + python3 -m venv venv + fi + + # Activate venv and install dependencies + source venv/bin/activate + python -m pip install --upgrade pip setuptools wheel + pip install -r requirements.txt + deactivate + + # Restart backend service + echo "Restarting backend service..." + sudo systemctl restart backend + + echo "✅ Deployment completed successfully!" + DEPLOY_EOF diff --git a/.github/actions/install-python-requirements/action.yml b/.github/actions/install-python-requirements/action.yml new file mode 100644 index 0000000..ee414de --- /dev/null +++ b/.github/actions/install-python-requirements/action.yml @@ -0,0 +1,33 @@ +name: 'Install Python Requirements' +description: 'Install Python dependencies from requirements.txt with proper path handling' + +inputs: + requirements_path: + description: 'Path to requirements.txt file (relative to working_directory)' + required: false + default: 'requirements.txt' + working_directory: + description: 'Working directory where requirements.txt is located' + required: false + default: '.' + python_version: + description: 'Python version to use' + required: false + default: '3.11' + +runs: + using: 'composite' + steps: + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ inputs.python_version }} + cache: 'pip' + cache-dependency-path: ${{ inputs.working_directory }}/${{ inputs.requirements_path }} + + - name: Install dependencies + shell: bash + working-directory: ${{ inputs.working_directory }} + run: | + python -m pip install --upgrade pip setuptools wheel + pip install -r ${{ inputs.requirements_path }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..8d2178e --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,30 @@ +name: CI/CD Pipeline + +on: + pull_request: + branches: [ "main" ] + types: [opened, synchronize, reopened, closed] + +jobs: + # Run tests on PRs (not on merge) + test-api: + if: github.event.action != 'closed' + uses: ./.github/workflows/test-cloud-services.yml + with: + python_version: '3.11' + + test-web: + if: github.event.action != 'closed' + uses: ./.github/workflows/test-web-client.yml + with: + node_version: '20' + + # Deploy when PR is merged to main + deploy: + if: github.event.pull_request.merged == true && github.event.action == 'closed' + needs: [test-api, test-web] + uses: ./.github/workflows/deploy.yml + with: + branch: 'main' + repo_base_dir: '/home/ubuntu' + secrets: inherit diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..98e6ff2 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,43 @@ +name: Deploy to EC2 + +on: + workflow_call: + inputs: + branch: + description: 'Git branch to deploy' + required: false + type: string + default: 'main' + repo_base_dir: + description: 'Base directory on EC2 where cloud-services and web-client are located' + required: false + type: string + default: '/home/ubuntu' + secrets: + EC2_PEM_KEY: + description: 'EC2 PEM key for SSH authentication' + required: true + EC2_HOST: + description: 'EC2 host address' + required: true + EC2_USER: + description: 'EC2 SSH user' + required: true + +jobs: + deploy: + runs-on: ubuntu-latest + name: Deploy to EC2 + + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Deploy to EC2 + uses: ./.github/actions/deploy-to-ec2 + with: + ec2_pem_key: ${{ secrets.EC2_PEM_KEY }} + ec2_host: ${{ secrets.EC2_HOST }} + ec2_user: ${{ secrets.EC2_USER }} + repo_base_dir: ${{ inputs.repo_base_dir }} + branch: ${{ inputs.branch }} diff --git a/.github/workflows/test-and-deploy.yml b/.github/workflows/test-and-deploy.yml deleted file mode 100644 index 4d878a9..0000000 --- a/.github/workflows/test-and-deploy.yml +++ /dev/null @@ -1,104 +0,0 @@ -name: Test and Deploy to EC2 - -on: - pull_request: - branches: [ "main" ] - types: [opened, synchronize, reopened, closed] - -jobs: - test-api: - if: github.event.action != 'closed' - runs-on: ubuntu-latest - name: Test Cloud Services API - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: '3.11' - - - name: Install API dependencies - run: | - cd cloud-services - python -m pip install --upgrade pip - pip install -r requirements.txt - pip install pytest pytest-asyncio httpx - - - name: Run API tests - run: | - cd cloud-services - pytest tests/ -v --tb=short - - test-web: - if: github.event.action != 'closed' - runs-on: ubuntu-latest - name: Test Web Client - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Node - uses: actions/setup-node@v4 - with: - node-version: '20' - - - name: Install web dependencies - run: | - cd web-client - npm ci - - - name: Run TypeScript type check - run: | - cd web-client - npm run type-check - - - name: Run linter - run: | - cd web-client - npm run lint - - - name: Build web client - run: | - cd web-client - npm run build - - deploy: - if: github.event.pull_request.merged == true && github.event.action == 'closed' - needs: [test-api, test-web] - runs-on: ubuntu-latest - name: Deploy to EC2 - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Configure SSH - run: | - mkdir -p ~/.ssh - echo "${{ secrets.EC2_PEM_KEY }}" > ~/.ssh/CULoop.pem - chmod 600 ~/.ssh/CULoop.pem - printf "Host ec2\n HostName ${{ secrets.EC2_HOST }}\n User ${{ secrets.EC2_USER }}\n IdentityFile ~/.ssh/CULoop.pem\n StrictHostKeyChecking no\n" >> ~/.ssh/config - - - name: Deploy to EC2 - run: | - ssh ec2 << 'EOF' - cd ~ - - # Update web client - cd web-client - git pull origin main - npm ci - npm run build - sudo systemctl restart frontend - - # Update API - cd ../cloud-services - git pull origin main - python -m pip install --upgrade pip - pip install -r requirements.txt - sudo systemctl restart backend - EOF \ No newline at end of file diff --git a/.github/workflows/test-cloud-services.yml b/.github/workflows/test-cloud-services.yml new file mode 100644 index 0000000..017a0a2 --- /dev/null +++ b/.github/workflows/test-cloud-services.yml @@ -0,0 +1,46 @@ +name: Test Cloud Services + +on: + workflow_call: + inputs: + python_version: + description: 'Python version to use' + required: false + type: string + default: '3.11' + +jobs: + test: + runs-on: ubuntu-latest + name: Test Cloud Services API + + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Install Python dependencies + uses: ./.github/actions/install-python-requirements + with: + requirements_path: requirements.txt + working_directory: cloud-services + python_version: ${{ inputs.python_version }} + + - name: Install test dependencies + shell: bash + working-directory: cloud-services + run: | + pip install pytest pytest-asyncio httpx + + - name: Run API tests + shell: bash + working-directory: cloud-services + run: | + pytest tests/ -v --tb=short + + - name: Upload test results + if: always() + uses: actions/upload-artifact@v4 + with: + name: pytest-results-cloud-services + path: cloud-services/junit/ + if-no-files-found: ignore diff --git a/.github/workflows/test-web-client.yml b/.github/workflows/test-web-client.yml new file mode 100644 index 0000000..4cc59a4 --- /dev/null +++ b/.github/workflows/test-web-client.yml @@ -0,0 +1,53 @@ +name: Test Web Client + +on: + workflow_call: + inputs: + node_version: + description: 'Node.js version to use' + required: false + type: string + default: '20' + +jobs: + test: + runs-on: ubuntu-latest + name: Test Web Client + + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ inputs.node_version }} + cache: 'npm' + cache-dependency-path: web-client/package-lock.json + + - name: Install dependencies + shell: bash + working-directory: web-client + run: npm ci + + - name: Run TypeScript type check + shell: bash + working-directory: web-client + run: npm run type-check + + - name: Run linter + shell: bash + working-directory: web-client + run: npm run lint + + - name: Build web client + shell: bash + working-directory: web-client + run: npm run build + + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: web-client-build + path: web-client/dist/ + if-no-files-found: ignore