Initial revision
This commit is contained in:
25
Dockerfile
Normal file
25
Dockerfile
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Use the official Golang image to create a build artifact.
|
||||||
|
FROM golang:1.25.9 as builder
|
||||||
|
|
||||||
|
# Create and change to the app directory.
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Retrieve application dependencies.
|
||||||
|
COPY go.* ./
|
||||||
|
RUN go mod download
|
||||||
|
|
||||||
|
# Copy local code to the container image.
|
||||||
|
COPY . ./
|
||||||
|
|
||||||
|
# Build the binary.
|
||||||
|
RUN CGO_ENABLED=0 GOOS=linux go build -v -o server
|
||||||
|
|
||||||
|
# Use a Docker multi-stage build to create a lean production image.
|
||||||
|
FROM alpine:3.23
|
||||||
|
RUN apk add --no-cache ca-certificates
|
||||||
|
|
||||||
|
# Copy the binary to the production image from the builder stage.
|
||||||
|
COPY --from=builder /app/server /server
|
||||||
|
|
||||||
|
# Run the web service on container startup.
|
||||||
|
CMD ["/server"]
|
||||||
106
Taskfile.yaml
Normal file
106
Taskfile.yaml
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
# This taskfile handles building and deploying the petname-service
|
||||||
|
version: '3'
|
||||||
|
|
||||||
|
vars:
|
||||||
|
DEFAULT_PROJECT_ID: "infinite-deck-479516-g6"
|
||||||
|
REGION: "us-east4"
|
||||||
|
SERVICE_NAME: "petname"
|
||||||
|
DOMAIN: "proto-hype.net"
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
login:
|
||||||
|
desc: Login w/ gcloud
|
||||||
|
summary: |
|
||||||
|
Initializes gcloud and sets the project, region, and zone. You can optionally provide
|
||||||
|
a project ID as an argument, otherwise it defaults to the pre-existing project.
|
||||||
|
|
||||||
|
Example: task init
|
||||||
|
cmds:
|
||||||
|
- |
|
||||||
|
PROJECT_ID="${1:-{{.DEFAULT_PROJECT_ID}}}"
|
||||||
|
gcloud auth login --update-adc
|
||||||
|
gcloud config set project "$PROJECT_ID"
|
||||||
|
gcloud config set compute/region {{.REGION}}
|
||||||
|
gcloud config set compute/zone {{.REGION}}-c
|
||||||
|
gcloud auth application-default set-quota-project "$PROJECT_ID"
|
||||||
|
|
||||||
|
setup:
|
||||||
|
desc: Enable required Google Cloud APIs
|
||||||
|
summary: |
|
||||||
|
Enables the Cloud Run, Cloud Build, and Artifact Registry APIs required for deployment.
|
||||||
|
cmds:
|
||||||
|
- |
|
||||||
|
gcloud services enable run.googleapis.com cloudbuild.googleapis.com artifactregistry.googleapis.com
|
||||||
|
|
||||||
|
deploy:
|
||||||
|
desc: Deploy the service to Google Cloud Run
|
||||||
|
summary: |
|
||||||
|
Deploys the source code directly to Cloud Run using Cloud Build (Option A).
|
||||||
|
|
||||||
|
Example: task deploy
|
||||||
|
cmds:
|
||||||
|
- |
|
||||||
|
gcloud run deploy {{.SERVICE_NAME}} \
|
||||||
|
--source . \
|
||||||
|
--region {{.REGION}} \
|
||||||
|
--allow-unauthenticated
|
||||||
|
|
||||||
|
map-domain:
|
||||||
|
desc: Map a custom domain to the Cloud Run service
|
||||||
|
summary: |
|
||||||
|
Map <service>.<domain> to the deployed service. This requires the service
|
||||||
|
to be deployed in a region which support domain-mappints (like us-east4).
|
||||||
|
|
||||||
|
Example: task map-domain
|
||||||
|
cmds:
|
||||||
|
- |
|
||||||
|
gcloud beta run domain-mappings create \
|
||||||
|
--service {{.SERVICE_NAME }} \
|
||||||
|
--domain {{ .SERVICE_NAME }}.{{ .DOMAIN }} \
|
||||||
|
--region {{.REGION}}
|
||||||
|
|
||||||
|
logs:
|
||||||
|
desc: Tail the real-time logs for the service
|
||||||
|
summary: |
|
||||||
|
Streams the live logs from Cloud Run to your terminal. Useful for debugging
|
||||||
|
or watching traffic hit the service. Press Ctrl+C to stop.
|
||||||
|
|
||||||
|
Example: task logs
|
||||||
|
cmds:
|
||||||
|
- |
|
||||||
|
gcloud beta run services logs tail {{.SERVICE_NAME}} \
|
||||||
|
--region {{.REGION}}
|
||||||
|
|
||||||
|
test:
|
||||||
|
desc: Test the deployed service
|
||||||
|
summary: |
|
||||||
|
Tests the deployed service by sending a request to the mapped domain. Make sure
|
||||||
|
to replace <service> and <domain> with your actual service name and domain.
|
||||||
|
|
||||||
|
Example: task test
|
||||||
|
silent: true
|
||||||
|
cmds:
|
||||||
|
- |
|
||||||
|
function test_service() {
|
||||||
|
local path="$1"
|
||||||
|
|
||||||
|
# Use curl to capture both HTTP status code and response body
|
||||||
|
local response=$(curl -s -w "%{http_code}" https://{{.SERVICE_NAME}}.{{.DOMAIN}}/$path)
|
||||||
|
|
||||||
|
# Extract status code (last 3 chars) and body (everything else)
|
||||||
|
local status_code="${response:${#response}-3}"
|
||||||
|
local body="${response:0:${#response}-3}"
|
||||||
|
|
||||||
|
if [ "$status_code" -lt 399 ]; then
|
||||||
|
printf "GET /%s -> [SUCCESS] Status: %s, Body: %s\n" "$path" "$status_code" "$body"
|
||||||
|
else
|
||||||
|
printf "GET /%s -> [FAILURE] Status: %s, Body: %s\n" "$path" "$status_code" "$body"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Explicitly return 0 so the task does not fail if a test fails
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
test_service ""
|
||||||
|
test_service "?words=4"
|
||||||
|
test_service "?separator=."
|
||||||
5
go.mod
Normal file
5
go.mod
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
module petname-service
|
||||||
|
|
||||||
|
go 1.25.9
|
||||||
|
|
||||||
|
require github.com/dustinkirkland/golang-petname v0.0.0-20231002161417-6a283f1aaaf2
|
||||||
2
go.sum
Normal file
2
go.sum
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
github.com/dustinkirkland/golang-petname v0.0.0-20231002161417-6a283f1aaaf2 h1:S6Dco8FtAhEI/qkg/00H6RdEGC+MCy5GPiQ+xweNRFE=
|
||||||
|
github.com/dustinkirkland/golang-petname v0.0.0-20231002161417-6a283f1aaaf2/go.mod h1:8AuBTZBRSFqEYBPYULd+NN474/zZBLP+6WeT5S9xlAc=
|
||||||
50
main.go
Normal file
50
main.go
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"math/rand"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/dustinkirkland/golang-petname"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Seed the random number generator
|
||||||
|
rand.Seed(time.Now().UnixNano())
|
||||||
|
|
||||||
|
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Default to 2 words, can be overridden by query param "words"
|
||||||
|
words := 2
|
||||||
|
if wParam := r.URL.Query().Get("words"); wParam != "" {
|
||||||
|
if parsed, err := strconv.Atoi(wParam); err == nil && parsed > 0 {
|
||||||
|
words = parsed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default separator is hyphen, can be overridden by query param "separator"
|
||||||
|
separator := "-"
|
||||||
|
if sParam := r.URL.Query().Get("separator"); sParam != "" {
|
||||||
|
separator = sParam
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate petname
|
||||||
|
name := petname.Generate(words, separator)
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "text/plain")
|
||||||
|
fmt.Fprint(w, name)
|
||||||
|
})
|
||||||
|
|
||||||
|
port := os.Getenv("PORT")
|
||||||
|
if port == "" {
|
||||||
|
port = "8080"
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Listening on port %s", port)
|
||||||
|
if err := http.ListenAndServe(":"+port, nil); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user