Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,16 @@ COPY web/robots.txt ./web/robots.txt

RUN go build -trimpath -mod=readonly -buildvcs=false -ldflags="-s -w" \
-o /out/secret-api ./cmd/server
RUN go build -trimpath -mod=readonly -buildvcs=false -ldflags="-s -w" \
-o /out/healthcheck ./cmd/healthcheck

# runtime
FROM gcr.io/distroless/base:nonroot@sha256:746b9dbe3065a124395d4a7698241dbd6f3febbf01b73e48f942aabd7b8e5eac

WORKDIR /app

COPY --from=builder --chown=nonroot:nonroot /out/secret-api /app/secret-api
COPY --from=builder --chown=nonroot:nonroot /out/healthcheck /app/healthcheck
COPY --from=builder --chown=nonroot:nonroot /src/web/static /app/web/static
COPY --from=builder --chown=nonroot:nonroot /src/web/robots.txt /app/web/robots.txt

Expand Down
37 changes: 37 additions & 0 deletions cmd/healthcheck/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package main

import (
"fmt"
"net/http"
"os"
"time"
)

func check(port string) error {
url := fmt.Sprintf("http://localhost:%s/health", port)

client := &http.Client{Timeout: 3 * time.Second}
resp, err := client.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
return fmt.Errorf(
"unexpected status code: %d", resp.StatusCode,
)
}
return nil
}

func main() {
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}

if err := check(port); err != nil {
os.Exit(1)
}
}
54 changes: 54 additions & 0 deletions cmd/healthcheck/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package main

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

func testServer(
t *testing.T, status int,
) string {
t.Helper()

mux := http.NewServeMux()
mux.HandleFunc("/health", func(
w http.ResponseWriter, r *http.Request,
) {
w.WriteHeader(status)
})

ln, err := net.Listen("tcp", "localhost:0")
if err != nil {
t.Fatal(err)
}

srv := &http.Server{Handler: mux}
go func() { _ = srv.Serve(ln) }()
t.Cleanup(func() { srv.Close() })

_, port, _ := net.SplitHostPort(ln.Addr().String())
return port
}

func TestCheck(t *testing.T) {
t.Run("returns nil when server is healthy", func(t *testing.T) {
port := testServer(t, http.StatusOK)
if err := check(port); err != nil {
t.Fatalf("expected healthy, got error: %v", err)
}
})

t.Run("returns error on unhealthy status", func(t *testing.T) {
port := testServer(t, http.StatusServiceUnavailable)
if err := check(port); err == nil {
t.Fatal("expected error for unhealthy status")
}
})

t.Run("returns error when no server is running", func(t *testing.T) {
if err := check("0"); err == nil {
t.Fatal("expected error when no server running")
}
})
}
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ services:
ports:
- "8080:8080"
healthcheck:
test: ["CMD", "wget", "--spider", "http://localhost:8080/health/"]
test: ["CMD", "/app/healthcheck"]
interval: 30s
timeout: 5s
retries: 3
Expand Down
2 changes: 1 addition & 1 deletion web/frontend/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
color-scheme: light;
}

[data-theme="dark"] {
[data-theme='dark'] {
--primary-color: #e8e8e8;
--secondary-color: #111;
--background-color: #111;
Expand Down
16 changes: 13 additions & 3 deletions web/frontend/components/Layout/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,21 @@ export function Layout({ children, onToggleTheme }: LayoutProps) {
return (
<div class={styles.container}>
<div class={styles.nav}>
<a href="/" class={styles.navLink}>create</a>
<a href="/about" class={styles.navLink}>about</a>
<a href="/" class={styles.navLink}>
create
</a>
<a href="/about" class={styles.navLink}>
about
</a>
<span>{`secretapi \u00A9 ${new Date().getFullYear()}`}</span>
<button class={styles.themeToggle} onClick={onToggleTheme} aria-label="Toggle theme">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg
width="14"
height="14"
viewBox="0 0 14 14"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<circle cx="7" cy="7" r="6" stroke="currentColor" stroke-width="1.5" />
<path d="M7 1 A6 6 0 0 0 7 13 Z" fill="currentColor" />
</svg>
Expand Down