diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml new file mode 100644 index 0000000000..34d7ff9628 --- /dev/null +++ b/.github/workflows/cd.yml @@ -0,0 +1,41 @@ +# new workflow for continuous deployment +# the ci workflow runs when a pull request is opened, +# but a cd workflow runs when a pull request is merged +# (or when code is pushed directly to the main branch). + +on: + push: + branches: [addtests] # zum Testen [addtests], für Produktion: [main] + +jobs: + deploy: + name: Deploy + runs-on: ubuntu-latest + + steps: + - name: Check out code + uses: actions/checkout@v4 + + - name: Setup Go toolchain + uses: actions/setup-go@v5 + with: + go-version: "1.25.1" + + - name: Build the app + run: ./scripts/buildprod.sh + + - name: Run database migrations + env: + DATABASE_URL: ${{ secrets.DATABASE_URL }} + run: | + echo "Running database migrations..." + ./scripts/migrate.sh + # oder z.B. go run cmd/migrate/main.go + + - name: Deploy to Cloud Run + run: gcloud run deploy notely \ + --image us-central1-docker.pkg.dev/tensile-core-474113-t6/notely-ar-repo/notely:latest \ + --region us-central1 \ + --allow-unauthenticated \ + --project tensile-core-474113-t6 \ + --max-instances=4 \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000000..be08ec4cab --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,60 @@ +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: + go-version: "1.25.1" + + - name: Run Go Tests + run: go test ./... + + - name: Run Go test with coverage + run: go test -cover ./... + + - name: Install gosec + run: go install github.com/securego/gosec/v2/cmd/gosec@latest + + - name: Run gosec + run: gosec ./... + + + 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" + + - name: Run Test -z + run: | + test -z "$(go fmt ./...)" + go fmt ./... + + #- name: Run Go Formatting + # run: go fmt ./... + + - name: Install staticcheck + run: go install honnef.co/go/tools/cmd/staticcheck@latest + + - name: Run Go Linting + run: staticcheck ./... + \ No newline at end of file diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000000..28c9b0625e --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,39 @@ +# Die Datei, die bei jedem Push auf den main-Branch automatisch: +# 1. Mit GCP authentifiziert +# 2. Das Docker-Image baut +# 3. Es in die Artifact Registry hochlaedt + +name: Build and Push to Artifact Registry + +on: + push: + branches: + - main + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Authenticate to Google Cloud + uses: google-github-actions/auth@v1 + with: + credentials_json: ${{ secrets.GCP_CREDENTIALS }} + + - name: Set up gcloud CLI + uses: google-github-actions/setup-gcloud@v1 + with: + project_id: tensile-core-474113-t6 + + - name: Show gcloud config + run: gcloud config list + + - name: Who am I + run: gcloud auth list + + - name: Build and push Docker image + run: | + gcloud builds submit --tag us-central1-docker.pkg.dev/tensile-core-474113-t6/notely-ar-repo/notely:latest . \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 2be3d18b81..5d411a379c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,5 @@ -FROM --platform=linux/amd64 debian:stable-slim +#FROM --platform=linux/amd64 debian:stable-slim +FROM debian:stable-slim RUN apt-get update && apt-get install -y ca-certificates diff --git a/README.md b/README.md index c2bec0368b..9711e7d41a 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,10 @@ +# README Badge for Tests +[![ci](https://github.com/DorisLederle/learn-cicd-starter/actions/workflows/ci.yml/badge.svg)](https://github.com/DorisLederle/learn-cicd-starter/actions/workflows/ci.yml) + + + + + # 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). @@ -21,3 +28,5 @@ 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! + +Doris Lederle's version of Boot.dev's Notely app. \ No newline at end of file diff --git a/internal/auth/auth.go b/internal/auth/auth.go index f969aacf63..5d632791a3 100644 --- a/internal/auth/auth.go +++ b/internal/auth/auth.go @@ -11,6 +11,7 @@ var ErrNoAuthHeaderIncluded = errors.New("no authorization header included") // GetAPIKey - func GetAPIKey(headers http.Header) (string, error) { authHeader := headers.Get("Authorization") + if authHeader == "" { return "", ErrNoAuthHeaderIncluded } @@ -20,4 +21,5 @@ func GetAPIKey(headers http.Header) (string, error) { } return splitAuth[1], nil + //return "wrong-key", nil //temporarily broken code for testing } diff --git a/internal/auth/auth_test.go b/internal/auth/auth_test.go new file mode 100644 index 0000000000..4047953468 --- /dev/null +++ b/internal/auth/auth_test.go @@ -0,0 +1,84 @@ +package auth + +import ( + "net/http" + "testing" +) + +func TestGetAPIKey_ValidHeader(t *testing.T) { + headers := http.Header{} + headers.Set("Authorization", "ApiKey abc123") + //headers.Set("Authorization", "abc123") + + got, err := GetAPIKey(headers) + want := "abc123" + //want := "wrongkey" + + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + if got != want { + t.Errorf("Expected API key %q, got %q", want, got) + } +} + +func TestGetAPIKey_MissingHeader(t *testing.T) { + headers := http.Header{} + + _, err := GetAPIKey(headers) + if err != ErrNoAuthHeaderIncluded { + t.Errorf("Expected error %v, got %v", ErrNoAuthHeaderIncluded, err) + } +} + +func TestGetAPIKey_MalformedHeader(t *testing.T) { + headers := http.Header{} + headers.Set("Authorization", "Bearer abc123") + + _, err := GetAPIKey(headers) + if err == nil || err.Error() != "malformed authorization header" { + t.Errorf("Expected malformed header error, got %v", err) + } +} + +func TestGetAPIKey_EmptyKey(t *testing.T) { + headers := http.Header{} + headers.Set("Authorization", "ApiKey ") + + got, err := GetAPIKey(headers) + if err != nil { + t.Fatalf("Expected no error for empty key, got %v", err) + } + if got != "" { + t.Errorf("Expected empty string, got %q", got) + } +} + +/* +func TestGetAPIKey_ExtraSpaces(t *testing.T) { + headers := http.Header{} + headers.Set("Authorization", "ApiKey abc123") + + got, err := GetAPIKey(headers) + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + if got != "abc123" { + t.Errorf("Expected 'abc123', got %q", got) + } +} + + +func TestGetAPIKey_MultipleParts(t *testing.T) { + headers := http.Header{} + headers.Set("Authorization", "ApiKey abc123 extra") + + got, err := GetAPIKey(headers) + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + if got != "abc123" { + t.Errorf("Expected 'abc123', got %q", got) + } +} +*/ diff --git a/json.go b/json.go index 1e6e7985e1..0ca35ff6ac 100644 --- a/json.go +++ b/json.go @@ -30,5 +30,8 @@ func respondWithJSON(w http.ResponseWriter, code int, payload interface{}) { return } w.WriteHeader(code) - w.Write(dat) + //w.Write(dat) //gosec: Errorhandling hinzufügen + if _, err := w.Write(dat); err != nil { + log.Printf("error writing response: %v", err) + } } diff --git a/main.go b/main.go index 19d7366c5f..3959bdf608 100644 --- a/main.go +++ b/main.go @@ -7,6 +7,7 @@ import ( "log" "net/http" "os" + "time" "github.com/go-chi/chi" "github.com/go-chi/cors" @@ -17,9 +18,7 @@ import ( _ "github.com/tursodatabase/libsql-client-go/libsql" ) -type apiConfig struct { - DB *database.Queries -} +type apiConfig struct{ DB *database.Queries } //go:embed static/* var staticFiles embed.FS @@ -89,10 +88,16 @@ func main() { router.Mount("/v1", v1Router) srv := &http.Server{ - Addr: ":" + port, - Handler: router, + Addr: ":" + port, + Handler: router, + ReadHeaderTimeout: 5 * time.Second, //gosec: schuetzt vor Slowlories } log.Printf("Serving on port: %s\n", port) log.Fatal(srv.ListenAndServe()) } + +/*func unused() { + // this function does nothing + // and is called nowhere +}*/ diff --git a/static/index.html b/static/index.html index 72be101028..5d4ad73c09 100644 --- a/static/index.html +++ b/static/index.html @@ -7,7 +7,7 @@ -

Notely

+

Welcome to Notely

diff --git a/test.txt b/test.txt new file mode 100644 index 0000000000..276e7895a0 --- /dev/null +++ b/test.txt @@ -0,0 +1 @@ +test text \ No newline at end of file