Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
6cdb59c
Update README.md
wolfrat-b Oct 28, 2025
eb209fe
Update README.md
wolfrat-b Oct 28, 2025
2426efe
feat: Add personal identifier to README
wolfrat-b Oct 28, 2025
157e831
feat: setup basic GitHub Actions CI workflow
wolfrat-b Oct 28, 2025
89ac04a
fix: Replace force failure with Go version check
wolfrat-b Oct 28, 2025
7d3bc28
feat: setup CI tests and deliberately break GetAPIKey to test failure
wolfrat-b Oct 29, 2025
ddc3d3a
fix: restore GetAPIKey to working state; CI now passing
wolfrat-b Oct 29, 2025
55dad00
ci: Add -cover flag to go test to track unit test coverage
wolfrat-b Oct 29, 2025
a5de0ee
docs: Add dynamic CI status badge to README
wolfrat-b Oct 29, 2025
6375463
docs: Add dynamic CI status badge to README
wolfrat-b Oct 29, 2025
88853d5
docs: Add dynamic CI status badge to README
wolfrat-b Oct 29, 2025
f9a4f71
docs: Add dynamic CI status badge to README
wolfrat-b Oct 29, 2025
0e41ec1
docs: correct CI badge URL in README
wolfrat-b Oct 29, 2025
4de7265
docs: correct CI badge URL in README
wolfrat-b Oct 29, 2025
aa97844
Merge pull request #2 from wolfrat-b/addtests
wolfrat-b Oct 29, 2025
118932d
ci: Add parallel 'Style' job for go fmt checks
wolfrat-b Oct 30, 2025
36d39de
feat: Add staticcheck to CI and commit temporary linter failure
wolfrat-b Oct 30, 2025
9cbea7e
Merge pull request #3 from wolfrat-b/addtests
wolfrat-b Oct 30, 2025
50b6680
final fix: ensuring all code is clean and CI passes
wolfrat-b Oct 30, 2025
339b004
fix: Corrected YAML indentation for parallel 'Style' job
wolfrat-b Oct 30, 2025
5bfd9a1
Merge pull request #4 from wolfrat-b/main
wolfrat-b Oct 30, 2025
900b75c
feat: Add gosec security analysis to Tests job (expect failure)
wolfrat-b Oct 30, 2025
303b9cf
fix: Resolve Gosec security vulnerabilities (G112 & G104)
wolfrat-b Oct 30, 2025
cf80d31
fix: Corrected syntax and formatting error in json.go
wolfrat-b Oct 30, 2025
315f807
fix: Final syntax correction for error handling in json.go
wolfrat-b Oct 30, 2025
426bd89
fix: Corrected shell quoting error in CI Style job
wolfrat-b Oct 30, 2025
28a18ff
fix: Final syntax correction for G112 server timeouts
wolfrat-b Oct 30, 2025
8d1f0bd
fix: Final syntax correction for main.go server struct
wolfrat-b Oct 30, 2025
7ab9c04
fix: Final formatting pass to satisfy CI style job
wolfrat-b Oct 30, 2025
769d8bf
feat: Add Continuous Deployment workflow (cd.yml)
wolfrat-b Nov 2, 2025
fe32ca8
feat(cd): Add Cloud Build and Artifact Registry push step
wolfrat-b Nov 3, 2025
686c04c
feat: Updated welcome message and finalized CI/CD pipeline
wolfrat-b Nov 4, 2025
a6bc938
feat: Automated database migrations in CD pipeline
wolfrat-b Nov 4, 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
84 changes: 84 additions & 0 deletions .github/workflows/cd.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
name: Continuous Deployment

# Triggers the workflow on a push to the 'main' branch
on:
push:
branches:
- main
# Allows manual triggering from the GitHub Actions UI (optional but useful)
workflow_dispatch:

jobs:
Deploy:
name: Deploy
runs-on: ubuntu-latest

# 1. ADD THIS SECTION
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}

steps:
# Step 1: Checkout the code
- name: Checkout Repository
uses: actions/checkout@v4

# 2. ADD THIS NEW STEP
- name: Install Goose Migrator
run: go install github.com/pressly/goose/v3/cmd/goose@latest

# Step 2: Set up the Go toolchain
- name: Set up Go
uses: actions/setup-go@v5
with:
# Use your required Go version (e.g., 1.22)
go-version: "1.22"

# Step 3: Build the production binary
# This step builds the Linux-ready executable
- name: Build Production App
run: ./scripts/buildprod.sh

# 1. AUTHENTICATION: Authenticate GitHub Actions with GCP
- name: Authenticate Google Cloud
uses: google-github-actions/auth@v1
with:
# Uses the sensitive JSON key stored as a GitHub Repository Secret
credentials_json: ${{ secrets.GCP_CREDENTIALS }}

# 2. CONFIGURATION: Set up gcloud CLI and Project ID
- name: Set up gcloud CLI
uses: google-github-actions/setup-gcloud@v1
with:
# Setting the project ID for all subsequent gcloud commands
project_id: infinite-bruin-477014-h1

# 3. BUILD & PUSH: Execute the Cloud Build submission command
- name: Build and Push Docker Image to Artifact Registry
run: |
# Use your specific, validated image path directly in the command.
IMAGE_URI="us-central1-docker.pkg.dev/infinite-bruin-477014-h1/notely-ar-repo/notely:latest"

echo "Submitting build to Cloud Build for image: ${IMAGE_URI}"

# This command triggers the Cloud Build service to build the Dockerfile
# in the current directory and push the resulting image to Artifact Registry.
gcloud builds submit --tag "${IMAGE_URI}" .

# 4. RUN MIGRATIONS: Execute database migrations
- name: Run Database Migrations
run: ./scripts/migrateup.sh

# Add this step to the 'deploy' job in your workflow file (.yml)
- name: Deploy to Cloud Run
run: |
# The image path, project ID, and region are configured here
IMAGE_PATH="us-central1-docker.pkg.dev/infinite-bruin-477014-h1/notely-ar-repo/notely:latest"
PROJECT_ID="infinite-bruin-477014-h1"
REGION="us-central1"

gcloud run deploy notely \
--image $IMAGE_PATH \
--region $REGION \
--project $PROJECT_ID \
--allow-unauthenticated \
--max-instances=4
57 changes: 57 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
name: ci

on:
pull_request:
branches: [main]

jobs:
tests:
name: Tests
runs-on: ubuntu-latest

steps:
- name: Check out code
uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
# Keep this Go version consistent with the other job
go-version: "1.25.1"

- name: Run Go Unit Tests
run: go test -cover ./...

# START of new gosec steps
- name: Install gosec
run: go install github.com/securego/gosec/v2/cmd/gosec@latest

- name: Run gosec security check
# This command runs the security analyzer on all Go code.
run: gosec ./...
# END of new gosec steps

# START of the new 'style' job for parallel execution
style:
name: Style
runs-on: ubuntu-latest

steps:
- name: Check out code
uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: "1.25.1" # Uses your defined version

# New Step: Install Staticcheck
- name: Install Staticcheck
run: go install honnef.co/go/tools/cmd/staticcheck@latest

# Modified Step: Run Staticcheck after installation
- name: Run Staticcheck
run: staticcheck ./...

- name: Check for Formatting Issues # Your existing formatting check
run: test -z "$(go fmt ./...)"
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
![CI Status](https://github.com/wolfrat-b/learn-cicd-starter/actions/workflows/ci.yml/badge.svg)
# learn-cicd-starter (Notely)

This repo contains the starter code for the "Notely" application for the "Learn CICD" course on [Boot.dev](https://boot.dev).
Expand All @@ -21,3 +22,4 @@ go build -o notely && ./notely
*This starts the server in non-database mode.* It will serve a simple webpage at `http://localhost:8080`.

You do *not* need to set up a database or any interactivity on the webpage yet. Instructions for that will come later in the course!
Wolfrat's version of Boot.dev's Notely app.
61 changes: 61 additions & 0 deletions internal/auth/auth_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package auth

import (
"net/http"
"reflect"
"testing"
)

// TestGetAPIKey uses the table-driven test idiom to ensure the function
// correctly extracts the API key under various conditions.
func TestGetAPIKey(t *testing.T) {
// Define the test table structure
tests := map[string]struct {
// Input
headers http.Header
// Expected Output
want string
// Expected Error (non-nil is expected to fail)
wantErr bool
}{
"ValidKey": {
// Correctly formatted header with a key
headers: map[string][]string{
"Authorization": {"ApiKey my-valid-key"},
},
want: "my-valid-key",
wantErr: false,
},
"NoAuthHeader": {
// Missing the Authorization header entirely
headers: map[string][]string{},
want: "",
wantErr: true,
},
"BadFormat": {
// Header is present but not in "ApiKey {key}" format
headers: map[string][]string{
"Authorization": {"Bearer token"},
},
want: "",
wantErr: true,
},
}

for name, tc := range tests {
// Use t.Run to execute each test case as an independent subtest
t.Run(name, func(t *testing.T) {
got, err := GetAPIKey(tc.headers)

// 1. Check for expected error
if (err != nil) != tc.wantErr {
t.Fatalf("GetAPIKey() error = %v, wantErr %v", err, tc.wantErr)
}

// 2. Check for expected result
if !reflect.DeepEqual(tc.want, got) {
t.Fatalf("GetAPIKey() got = %v, want %v", got, tc.want)
}
})
}
}
5 changes: 4 additions & 1 deletion json.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,8 @@ func respondWithJSON(w http.ResponseWriter, code int, payload interface{}) {
return
}
w.WriteHeader(code)
w.Write(dat)
_, err = w.Write(dat)
if err != nil {
log.Printf("error writing response: %v", err)
}
}
5 changes: 5 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"log"
"net/http"
"os"
"time"

"github.com/go-chi/chi"
"github.com/go-chi/cors"
Expand Down Expand Up @@ -91,6 +92,10 @@ func main() {
srv := &http.Server{
Addr: ":" + port,
Handler: router,
// Gosec Fix: Add secure timeouts (G112)
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 120 * time.Second,
}

log.Printf("Serving on port: %s\n", port)
Expand Down
Loading