Skip to content

Repository files navigation

SaaS Cloud DevOps

Bootstrap CI/CD Status

This repository provides production-grade multi-stack DevOps implementation integrating:

  • AWS Serverless architecture (Lambda, API Gateway, CloudFormation)
  • End-to-end CI/CD automation via GitHub Actions with Infrastructure as Code validation
  • Cross-platform scripting and observability with Node.js, Python, and AWS Powertools

The goal of this project is to establish a scalable, secure, and maintainable DevOps environment that aligns with enterprise-grade delivery standards and cross-cloud best practices, combining AWS automation within a unified workflow.


Project Overview

Phase 01: API Gateway Request Validation - CloudFormation

Foundation sourceAWS CloudFormation template of a sample API with basic request validation.

First Project Increment

Built basic REST API у API Gateway (REST API) with request validation through Models and RequestValidators in CloudFormation. On this stage without Lambda - integration HTTP_PROXY based on PetStore demo.

Intent - showcase backend input control and pure IaC implementation.

Base

  • Official example used: AWS - API Gateway request validation sample (CloudFormation).
  • Key elements reproduced: model for body POST, validator for GET (query) and POST (body), resource /validation, Deployment і Stage.
  • Added custom tests and screenshots.

Implemented Report

Resource: /validation

  • GET /validation?q1=... - mandatory query-parameter q1 (validation at the level API GW)
  • POST /validation - перевірка JSON-body by Model (required fields, ranges)
  • Integration: HTTP_PROXY → PetStore (Lambda will implement on Phase 02)
  • IaC: one CFN-template aws-cloudformation/apigw-request-validation.yaml

Files

  • aws-cloudformation/apigw-request-validation.yaml
  • docs/screenshots/*.jpg

Deployment and checks

Check IaC

cfn-lint aws-cloudformation/apigw-request-validation.yaml
Screenshot Check IaC cfn-lint ok

Deploy

aws cloudformation deploy `
  --template-file aws-cloudformation/apigw-request-validation.yaml `
  --stack-name req-validators-sample `
  --capabilities CAPABILITY_IAM `
  --parameter-overrides StageName=v1

After creating the stack get an Invoke URL from Outputs:

aws cloudformation describe-stacks --stack-name req-validators-sample `
  --query "Stacks[0].Outputs[?OutputKey=='ApiRootUrl'].OutputValue" --output text
Screenshot Stack Creation Output ApiRootUrl

Test

GET - parameter validation

# 200 OK - parameter present
curl.exe "$env:API_ROOT/validation?q1=dog"
Screenshot GET 200 with q1 GET 200 with q1

# 400 Bad Request - parameter missing
curl.exe "$env:API_ROOT/validation"
Screenshot GET 400 missing q1 GET 400 missing q1

# 400 Bad Request - empty parameter
curl.exe "$env:API_ROOT/validation?q1="
Screenshot GET 400 blank q1 GET 400 blank q1

POST - JSON body validation

# 200 OK - valid body
curl.exe -X POST "$env:API_ROOT/validation" `
  -H "Content-Type: application/json" `
  -d '{"type":"dog","name":"Buddy","price":100,"id":123}'
Screenshot POST 200 valid body POST 200 valid body

# 400 Bad Request - missing mandatory 'price'
curl.exe -X POST "$env:API_ROOT/validation" `
  -H "Content-Type: application/json" `
  -d '{"type":"dog","name":"Buddy","id":123}'
Screenshot POST 400 missing price POST 400 missing price

# 400 Bad Request - 'price' out of model range (min 25)
curl.exe -X POST "$env:API_ROOT/validation" `
  -H "Content-Type: application/json" `
  -d '{"type":"dog","name":"Buddy","price":10,"id":123}'
Screenshot POST 400 price out of range POST 400 price out of range

Clean-up

aws cloudformation delete-stack --stack-name req-validators-sample
aws cloudformation wait stack-delete-complete --stack-name req-validators-sample
>Screenshot check Deletion Delete stack and check

Phase 02: Minimal REST /tickets - API Gateway + Lambda

Second Increment: delivered the execution to the AWS Lambda and built a minimal REST /tickets (GET/POST) through API Gateway (REST API) with integration AWS_PROXY. At the input saved the validation of the body for POST (Model + RequestValidator). The goal is to get a working endpoint that accepts and returns JSON without intermediate stubs.

What exactly has been implemented

  • GET /tickets -> Lambda getTicket-a2 (demo list).
  • POST /tickets -> Lambda createTicket-a2 (takes { "title": "...", "priority": ... }, returns created ticket).
  • Validation (POST): JSON Schema Model with mandatory title.
  • CORS: preflight OPTIONS on the resource and CORS headers in Lambda responses.
  • IaC: one SAM/CFN-template з API, Method, Model/Validator, Deployment/Stage, Lambda Permissions.

Files

  • infrastructure/cloudformation/template.yaml - main SAM/CFN-template.
  • src/handlers/getTicket/index.js - handler for GET.
  • src/handlers/createTicket/index.js - handler for POST.
  • aws-cloudformation/packaged.yaml - exit cloudformation package.

Package & Deploy

S3 bucket for artifacts already created in the previous step.

aws cloudformation package `
  --template-file infrastructure/cloudformation/template.yaml `
  --s3-bucket serhii-saas-devops-artifacts-eu-west-1 `
  --output-template-file aws-cloudformation/packaged.yaml
Screenshot – package template package ok

aws cloudformation deploy `
  --template-file aws-cloudformation/packaged.yaml `
  --stack-name tickets-api-a2 `
  --capabilities CAPABILITY_IAM CAPABILITY_AUTO_EXPAND `
  --parameter-overrides ApiName=tickets-api StageName=v1
Screenshot – deploy stack deploy ok

Get Invoke URL:

$env:TICKETS_URL = (aws cloudformation describe-stacks --stack-name tickets-api-a2 `
  --query "Stacks[0].Outputs[?OutputKey=='TicketsInvokeUrl'].OutputValue" --output text)
$env:TICKETS_URL

Test

GET /tickets

Invoke-RestMethod -Uri $env:TICKETS_URL -Method GET
Screenshot – GET 200 GET 200

POST /tickets (valid)

$body = @{ title = "New ticket from A2"; priority = "HIGH" } | ConvertTo-Json
Invoke-RestMethod -Uri $env:TICKETS_URL -Method POST -ContentType "application/json" -Body $body
Screenshot – POST 201 POST 201

POST /tickets (invalid)

$body = @{ title = ""; priority = "LOW" } | ConvertTo-Json
try {
  Invoke-RestMethod -Uri $env:TICKETS_URL -Method POST -ContentType "application/json" -Body $body
  } catch { $_.Exception.Response.StatusCode.value__ }  
Screenshot – POST 400 POST 400

OPTIONS (CORS preflight)

curl.exe -i -X OPTIONS "$env:TICKETS_URL"
Screenshot – OPTIONS CORS OPTIONS CORS

Clean-up

aws cloudformation delete-stack --stack-name tickets-api-a2
aws cloudformation wait stack-delete-complete --stack-name tickets-api-a2
Screenshot – delete stack delete ok

Node.js environment configuration

To align local development with the AWS Lambda runtime, several Node.js adjustments were introduced during Phase 03.

Local dependencies per handler

Each Lambda function (getTicket, createTicket) now contains its own package.json, package-lock.json and node_modules folder.
This mirrors AWS packaging logic and ensures correct deployment with CloudFormation.

Project configuration (jsconfig.json)

A jsconfig.json file was added to define a pure Node/Lambda environment and remove DOM type conflicts in VS Code. This prevents false event warnings and enables IntelliSense for AWS types.

{   "compilerOptions": {
    "target": "ES2022",
    "module": "commonjs",
    "lib": ["ES2022"],
    "types": ["node", "aws-lambda"] } }

Outcome

After restarting the TypeScript server, all module and type reconfigured, no warnings in environment. The project now matches a production-grade Node.js Lambda structure, according with best practices for AWS.

Phase 03: Raise the level - hardening /tickets

Intent - raise the minimum REST from the previous phase to the production-baseline: input validation (GET/POST), correct CORS, access control via API Key + Usage Plan, minimum IAM rights, observability (AWS Lambda Powertools + X-Ray), managed log retention, throttling on API Gateway, contract in the form of OpenAPI.

What added compared to Phase 02

  • Validation: GET /tickets - mandatory ?limit= through RequestValidator + RequestParameters; POST /tickets - model Ticket with title as required і priorityas {LOW, MEDIUM, HIGH}.
  • CORS: OPTIONS on the resource + consistent headers in Lambda responses (including x-api-key).
  • Security: least-privilege ролі Lambda (AWSLambdaBasicExecutionRole, AWSXRayDaemonWriteAccess), ApiKey + UsagePlan (key is required for POST).
  • Observability: @aws-lambda-powertools/logger and metrics in both function; Tracing: Active (X-Ray).
  • Performance/Scale: memory tuning (get:128MB/5s, post:256MB/6s), throttling on Stage (rate/burst).
  • Ergonomics: export OpenAPI (OAS 3.0) with API Gateway.

Modified files

  • infrastructure/cloudformation/template.yaml - Validators/Model, CORS, API Key+Usage Plan, throttling, Log Retention, Tracing.
  • src/handlers/getTicket/index.js - Powertools, reading limit, metrics TicketsListed, CORS-headings.
  • src/handlers/createTicket/index.js - Powertools, validation in code, metrics TicketCreated, CORS-headings.

aws cloudformation package `
  --template-file infrastructure/cloudformation/template.yaml `
  --s3-bucket serhii-saas-devops-artifacts-eu-west-1 `
  --output-template-file aws-cloudformation/packaged.yaml
Screenshot - package package ok

aws cloudformation deploy `
  --template-file aws-cloudformation/packaged.yaml `
  --stack-name tickets-a3 `
  --capabilities CAPABILITY_IAM CAPABILITY_NAMED_IAM `
  --parameter-overrides ApiName=tickets-api StageName=v1 ApiKeyValue=dev-key-a3-001-1550da9fdf67 `
  --s3-bucket serhii-saas-devops-artifacts-eu-west-1
Screenshot - deploy ok deploy ok

After deploying, check the Outputs of the stack:

aws cloudformation describe-stacks --stack-name tickets-a3 --query "Stacks[0].Outputs" --output table
Screenshot - check outputs check outputs

Test

GET /tickets - 200 OK

Invoke-RestMethod -Uri ($env:TICKETS_URL + "?limit=2") -Method GET
Screenshot - GET 200 (limit) GET 200 limit

GET /tickets - 400 Bad Request (missing limit)

curl.exe $env:TICKETS_URL
Screenshot - GET 400 (no limit) GET 400 no limit

POST /tickets - 201 Created

$body = @{ title = "A3 ticket"; priority = "MEDIUM" } | ConvertTo-Json
Invoke-RestMethod -Uri $env:TICKETS_URL -Method POST `
  -Headers @{ "x-api-key" = $env:API_KEY; "Content-Type" = "application/json" } `
  -Body $body
Screenshot - POST 201 POST 201

POST /tickets - 400 Bad Request

$body = @{ title = ""; priority = "LOW" } | ConvertTo-Json
Invoke-RestMethod -Uri $TICKETS_URL -Method POST `
  -Headers @{ "x-api-key" = $API_KEY; "Content-Type" = "application/json" } `
  -Body $body
Screenshot - POST 400 (invalid body) POST 400 invalid body

OPTIONS /tickets - CORS preflight

curl.exe -i -X OPTIONS $env:TICKETS_URL
Screenshot - OPTIONS CORS OPTIONS CORS

OpenAPI (OAS 3.0) export

mkdir docs\openapi -Force | Out-Null
aws apigateway get-export `
  --rest-api-id $env:REST_ID `
  --stage-name v1 `
  --export-type oas30 `
  --parameters extensions=integrations `
  --accept application/json docs/openapi/tickets-oas30-v1.json
Screenshot - OpenAPI export openapi export

Clean-up

aws cloudformation delete-stack --stack-name tickets-a3
aws cloudformation wait stack-delete-complete --stack-name tickets-a3
Screenshot - delete stack delete stack ok

About

DevOps project integrating AWS CloudFormation

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages