diff --git a/Dockerfile b/Dockerfile index 313e749..2e32105 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,7 +16,7 @@ ENV GOCACHE=/root/.cache/go-build RUN --mount=type=cache,target="/root/.cache/go-build" CGO_ENABLED=0 GOOS=linux go build -o rapid-go . # Stage 2: Final stage -FROM alpine:edge +FROM alpine:edge as server # Set the working directory WORKDIR /app diff --git a/Tiltfile b/Tiltfile index f7c612d..2926214 100644 --- a/Tiltfile +++ b/Tiltfile @@ -1,5 +1,8 @@ +load('ext://helm_resource', 'helm_resource', 'helm_repo') + deployment = local("cue export deploy/kube.cue -e deployment --out yaml") service = local("cue export deploy/kube.cue -e service --out yaml") +migration = local("cue export deploy/kube.cue -T -e migration --out yaml") docker_build( 'rapid-go', @@ -7,5 +10,9 @@ docker_build( dockerfile='./Dockerfile', ) -k8s_yaml([deployment, service]) -k8s_resource(workload='api', port_forwards=32400) \ No newline at end of file +k8s_yaml([deployment, service, migration]) +k8s_resource(workload='api', port_forwards=32400) + +helm_repo('bitnami', 'https://charts.bitnami.com/bitnami') +helm_resource('postgresql', 'bitnami/postgresql', resource_deps=['bitnami'], flags=['--set=global.postgresql.auth.password=password']) +k8s_resource(workload='postgresql', port_forwards=5432) \ No newline at end of file diff --git a/cmd/root.go b/cmd/root.go index e3bfbc5..e07daa1 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -16,14 +16,16 @@ limitations under the License. package cmd import ( + "database/sql" "fmt" "log/slog" "net/http" "os" "time" + "github.com/bristolgolang/rapid-go/internal/server" + _ "github.com/lib/pq" "github.com/spf13/cobra" - "github.com/spf13/viper" ) @@ -36,8 +38,25 @@ var rootCmd = &cobra.Command{ // Uncomment the following line if your bare application // has an action associated with it: RunE: func(cmd *cobra.Command, args []string) error { + connStr := viper.GetString("postgres_connection_string") + slog.Info("connecting to database", slog.String("connection_string", connStr)) + db, err := sql.Open("postgres", connStr) + if err != nil { + slog.Error("failed to connect to database", slog.String("error", err.Error())) + return err + } + defer db.Close() + + err = db.PingContext(cmd.Context()) + if err != nil { + slog.Error("failed to ping database", slog.String("error", err.Error())) + return err + } + + s := server.NewServer(db) + router := http.NewServeMux() - router.Handle("GET /greet/{name}", loggingMiddleware(http.HandlerFunc(helloUser))) + router.Handle("GET /greet/{name}", loggingMiddleware(http.HandlerFunc(s.Greet))) router.HandleFunc("GET /ready", ready) server := &http.Server{ @@ -59,12 +78,6 @@ func loggingMiddleware(next http.Handler) http.Handler { }) } -func helloUser(w http.ResponseWriter, r *http.Request) { - name := r.PathValue("name") - msg := fmt.Sprintf("Hello, %s!", name) - w.Write([]byte(msg)) -} - func ready(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) } @@ -88,7 +101,10 @@ func init() { // when this action is called directly. rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") + viper.SetEnvPrefix("rapid_go") viper.SetDefault("port", "32400") + viper.BindEnv("port") + viper.BindEnv("postgres_connection_string") } // initConfig reads in config file and ENV variables if set. @@ -107,7 +123,8 @@ func initConfig() { viper.SetConfigName(".rapid-go") } - viper.AutomaticEnv() // read in environment variables that match + viper.SetEnvPrefix("rapid_go") // set environment variable prefix + viper.AutomaticEnv() // read in environment variables that match // If a config file is found, read it in. if err := viper.ReadInConfig(); err == nil { diff --git a/deploy/kube.cue b/deploy/kube.cue index fc76303..2cee4e0 100644 --- a/deploy/kube.cue +++ b/deploy/kube.cue @@ -25,8 +25,11 @@ deployment: { image: "rapid-go" imagePullPolicy: "Never" env: [{ - name: "PORT" + name: "RAPID_GO_PORT" value: #port + }, { + name: "RAPID_GO_POSTGRES_CONNECTION_STRING" + value: "host=postgresql port=5432 user=postgres password=password dbname=postgres sslmode=disable" }] ports: [{ containerPort: strconv.Atoi(#port) @@ -52,3 +55,43 @@ service: { }] } } + +#migration_labels: { + app: "rapid-go-migration" +} + +#current_directory: string @tag(dir,var=cwd) + +migration: { + apiVersion: "batch/v1" + kind: "Job" + metadata: { + name: "migration" + labels: #migration_labels + } + spec: { + template: { + metadata: labels: #migration_labels + spec: { + containers: [{ + name: "migrations" + image: "migrate/migrate" + imagePullPolicy: "IfNotPresent" + args: ["-path=/migrations/", "-database", "postgres://postgres:password@postgresql:5432/postgres?sslmode=disable", "up"] + volumeMounts: [{ + name: "migrations" + mountPath: "/migrations" + }] + }] + restartPolicy: "Never" + volumes: [{ + name: "migrations" + hostPath: { + path: #current_directory + "/migrations" + type: "Directory" + } + }] + } + } + } +} diff --git a/go.mod b/go.mod index 02f1b17..42fe11e 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/bristolgolang/rapid-go go 1.22 require ( + github.com/lib/pq v1.10.9 github.com/spf13/cobra v1.8.0 github.com/spf13/viper v1.18.2 ) diff --git a/go.sum b/go.sum index b6a7dcc..125fa41 100644 --- a/go.sum +++ b/go.sum @@ -17,6 +17,8 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= diff --git a/internal/server/server.go b/internal/server/server.go new file mode 100644 index 0000000..7bc642e --- /dev/null +++ b/internal/server/server.go @@ -0,0 +1,30 @@ +package server + +import ( + "database/sql" + "fmt" + "net/http" +) + +type server struct { + dbConn *sql.DB +} + +func NewServer(dbConn *sql.DB) *server { + return &server{ + dbConn: dbConn, + } +} + +func (s *server) Greet(w http.ResponseWriter, r *http.Request) { + name := r.PathValue("name") + + _, err := s.dbConn.ExecContext(r.Context(), "INSERT INTO users (name) VALUES ($1)", name) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + msg := fmt.Sprintf("Hello, %s!", name) + w.Write([]byte(msg)) +} diff --git a/migrations/000001_create_users_table.down.sql b/migrations/000001_create_users_table.down.sql new file mode 100644 index 0000000..cc1f647 --- /dev/null +++ b/migrations/000001_create_users_table.down.sql @@ -0,0 +1 @@ +DROP TABLE users; diff --git a/migrations/000001_create_users_table.up.sql b/migrations/000001_create_users_table.up.sql new file mode 100644 index 0000000..c775404 --- /dev/null +++ b/migrations/000001_create_users_table.up.sql @@ -0,0 +1,4 @@ +CREATE TABLE users ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL +);