summaryrefslogtreecommitdiff
path: root/idvoc-2025
diff options
context:
space:
mode:
Diffstat (limited to 'idvoc-2025')
-rw-r--r--idvoc-2025/CommentsEngine/.gitignore1
-rw-r--r--idvoc-2025/CommentsEngine/Dockerfile22
-rw-r--r--idvoc-2025/CommentsEngine/README.md5
-rw-r--r--idvoc-2025/CommentsEngine/go.mod22
-rw-r--r--idvoc-2025/CommentsEngine/go.sum34
-rw-r--r--idvoc-2025/CommentsEngine/main.go128
-rw-r--r--idvoc-2025/CommentsEngine/redisc/redis.go83
-rw-r--r--idvoc-2025/CommentsInteractor/.gitignore1
-rw-r--r--idvoc-2025/CommentsInteractor/Dockerfile24
-rw-r--r--idvoc-2025/CommentsInteractor/README.md5
-rw-r--r--idvoc-2025/CommentsInteractor/go.mod18
-rw-r--r--idvoc-2025/CommentsInteractor/go.sum26
-rw-r--r--idvoc-2025/CommentsInteractor/main.go147
-rw-r--r--idvoc-2025/docker-compose.yml48
-rw-r--r--idvoc-2025/metrics/README.md6
-rw-r--r--idvoc-2025/metrics/docker-compose.yml25
-rw-r--r--idvoc-2025/metrics/prometheus.yml16
-rwxr-xr-xidvoc-2025/metrics/start.sh10
18 files changed, 621 insertions, 0 deletions
diff --git a/idvoc-2025/CommentsEngine/.gitignore b/idvoc-2025/CommentsEngine/.gitignore
new file mode 100644
index 0000000..a18b076
--- /dev/null
+++ b/idvoc-2025/CommentsEngine/.gitignore
@@ -0,0 +1 @@
+CommentsEngine
diff --git a/idvoc-2025/CommentsEngine/Dockerfile b/idvoc-2025/CommentsEngine/Dockerfile
new file mode 100644
index 0000000..b57c112
--- /dev/null
+++ b/idvoc-2025/CommentsEngine/Dockerfile
@@ -0,0 +1,22 @@
+FROM reg.undercloud.cri.epita.fr/docker/golang:1.24.3-alpine
+
+LABEL org.opencontainers.image.authors="martial.simon@epita.fr"
+
+RUN apk --no-cache add curl bash
+
+WORKDIR /usr/local/app
+
+COPY go.mod go.sum . ./
+
+# Setup an app user so the container doesn't run as the root user
+RUN go install && adduser -D app
+USER app
+
+EXPOSE 8000
+
+ENV REDIS_ENDPOINT="redis:6379"
+ENV HOST="0.0.0.0"
+ENV PORT=8000
+
+# Run the app when starting a container
+CMD ["CommentsEngine"]
diff --git a/idvoc-2025/CommentsEngine/README.md b/idvoc-2025/CommentsEngine/README.md
new file mode 100644
index 0000000..4381739
--- /dev/null
+++ b/idvoc-2025/CommentsEngine/README.md
@@ -0,0 +1,5 @@
+## Env variables
+
+`REDIS_ENDPOINT`: The endpoint (host + port) on which redis can be contacted
+`HOST`: The host on which to bind+listen to
+`PORT`: The port on which to bind+listen to
diff --git a/idvoc-2025/CommentsEngine/go.mod b/idvoc-2025/CommentsEngine/go.mod
new file mode 100644
index 0000000..a2d1f24
--- /dev/null
+++ b/idvoc-2025/CommentsEngine/go.mod
@@ -0,0 +1,22 @@
+module CommentsEngine
+
+go 1.21.5
+
+require (
+ github.com/gorilla/handlers v1.5.2
+ github.com/redis/go-redis/v9 v9.3.1
+)
+
+require (
+ github.com/beorn7/perks v1.0.1 // indirect
+ github.com/cespare/xxhash/v2 v2.2.0 // indirect
+ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
+ github.com/felixge/httpsnoop v1.0.3 // indirect
+ github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
+ github.com/prometheus/client_golang v1.18.0 // indirect
+ github.com/prometheus/client_model v0.5.0 // indirect
+ github.com/prometheus/common v0.45.0 // indirect
+ github.com/prometheus/procfs v0.12.0 // indirect
+ golang.org/x/sys v0.15.0 // indirect
+ google.golang.org/protobuf v1.31.0 // indirect
+)
diff --git a/idvoc-2025/CommentsEngine/go.sum b/idvoc-2025/CommentsEngine/go.sum
new file mode 100644
index 0000000..fd15fe6
--- /dev/null
+++ b/idvoc-2025/CommentsEngine/go.sum
@@ -0,0 +1,34 @@
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
+github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
+github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
+github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
+github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
+github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
+github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
+github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
+github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE=
+github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w=
+github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg=
+github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k=
+github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk=
+github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA=
+github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
+github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
+github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM=
+github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY=
+github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
+github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
+github.com/redis/go-redis/v9 v9.3.1 h1:KqdY8U+3X6z+iACvumCNxnoluToB+9Me+TvyFa21Mds=
+github.com/redis/go-redis/v9 v9.3.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
+golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
+golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
+google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
diff --git a/idvoc-2025/CommentsEngine/main.go b/idvoc-2025/CommentsEngine/main.go
new file mode 100644
index 0000000..9fb8105
--- /dev/null
+++ b/idvoc-2025/CommentsEngine/main.go
@@ -0,0 +1,128 @@
+package main
+
+import (
+ "CommentsEngine/redisc"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "github.com/gorilla/handlers"
+ "github.com/prometheus/client_golang/prometheus"
+ "github.com/prometheus/client_golang/prometheus/promauto"
+ "github.com/prometheus/client_golang/prometheus/promhttp"
+ "github.com/redis/go-redis/v9"
+ "io"
+ "log"
+ "net/http"
+ "os"
+)
+
+func getenv(key, fallback string) string {
+ value := os.Getenv(key)
+ if len(value) == 0 {
+ return fallback
+ }
+ return value
+}
+
+var (
+ httpHits = promauto.NewCounterVec(prometheus.CounterOpts{
+ Name: "comments_engine_http_hits_total",
+ Help: "The total number of hits on a given route",
+ }, []string{"route", "method"})
+ commentsReceived = promauto.NewCounter(prometheus.CounterOpts{
+ Name: "comments_engine_comments_received_total",
+ Help: "The total number of comments received",
+ })
+ errorsSent = promauto.NewCounter(prometheus.CounterOpts{
+ Name: "comments_interactor_errors_sent_total",
+ Help: "The total number of errors returned",
+ })
+)
+
+func getRoot(w http.ResponseWriter, r *http.Request) {
+ httpHits.With(prometheus.Labels{"route": "/", "method": r.Method}).Inc()
+ io.WriteString(w, "CommentsEngine answering here !\n")
+}
+
+func sendError(w http.ResponseWriter, err error, additionalInfo string) {
+ errorsSent.Inc()
+ w.WriteHeader(http.StatusInternalServerError)
+ resp := make(map[string]string)
+ resp["message"] = fmt.Sprint(err)
+ resp["additionalInfo"] = additionalInfo
+ jsonResp, err := json.Marshal(resp)
+ if err != nil {
+ log.Fatalf("Error happened in JSON marshal. Err: %s", err)
+ }
+ w.Write(jsonResp)
+}
+
+func sendMessage(w http.ResponseWriter, message string) {
+ commentsReceived.Inc()
+ resp := make(map[string]string)
+ resp["message"] = message
+ jsonResp, err := json.Marshal(resp)
+ if err != nil {
+ log.Fatalf("Error happened in JSON marshal. Err: %s", err)
+ }
+ w.Write(jsonResp)
+}
+
+func getLatestComments(w http.ResponseWriter, r *http.Request) {
+ httpHits.With(prometheus.Labels{"route": "/latest", "method": r.Method}).Inc()
+ w.Header().Set("Content-Type", "application/json")
+ messages, err := redisc.GetLatestComments()
+ if err == nil {
+ io.WriteString(w, messages)
+ } else {
+ sendError(w, err, "Maybe there is a problem with redis backend ?")
+ }
+}
+
+type Comment struct {
+ Comment string `json:"comment"`
+}
+
+func sendComment(w http.ResponseWriter, r *http.Request) {
+ httpHits.With(prometheus.Labels{"route": "/comment", "method": r.Method}).Inc()
+ w.Header().Set("Content-Type", "application/json")
+ if r.Method != http.MethodPost {
+ sendError(w, errors.New("POST not used"), "HTTP method was not POST")
+ return
+ }
+ comment := &Comment{}
+ err := json.NewDecoder(r.Body).Decode(comment)
+ if err != nil {
+ sendError(w, err, "The data sent was likely incorrect")
+ return
+ }
+ _, err = redisc.StoreComment(comment.Comment)
+ if err != nil {
+ sendError(w, err, "Problem with redis maybe ?")
+ return
+ }
+ sendMessage(w, "stored !")
+}
+
+func main() {
+ http.HandleFunc("/", getRoot)
+ http.HandleFunc("/latest", getLatestComments)
+ http.HandleFunc("/comment", sendComment)
+ http.Handle("/metrics", promhttp.Handler())
+
+ redisc.Client = redis.NewClient(&redis.Options{
+ Addr: getenv("REDIS_ENDPOINT", "localhost:6379"),
+ Password: "", // no password set
+ DB: 0, // use default DB
+ })
+
+ listeningHostPort := getenv("HOST", "127.0.0.1") + ":" + getenv("PORT", "8000")
+ fmt.Printf("Starting the server on %s\n", listeningHostPort)
+ err := http.ListenAndServe(listeningHostPort, handlers.LoggingHandler(os.Stdout, http.DefaultServeMux))
+ if errors.Is(err, http.ErrServerClosed) {
+ fmt.Printf("server closed\n")
+ } else if err != nil {
+ fmt.Printf("error starting server: %s\n", err)
+ os.Exit(1)
+ }
+}
diff --git a/idvoc-2025/CommentsEngine/redisc/redis.go b/idvoc-2025/CommentsEngine/redisc/redis.go
new file mode 100644
index 0000000..88d9a08
--- /dev/null
+++ b/idvoc-2025/CommentsEngine/redisc/redis.go
@@ -0,0 +1,83 @@
+package redisc
+
+import (
+ "context"
+ "encoding/json"
+ "github.com/redis/go-redis/v9"
+ "log"
+ "strconv"
+)
+
+var Client *redis.Client
+
+func getIndex(ctx context.Context) (int, error) {
+ val, err := Client.Get(ctx, "index").Result()
+ var i int
+ if err != nil && err != redis.Nil {
+ log.Printf("Problem with redis: %s", err)
+ return -1, err
+ } else if err == redis.Nil {
+ i = 0
+ } else {
+ i, err = strconv.Atoi(val)
+ if err != nil {
+ log.Printf("Problem in stored data: %s", err)
+ return -1, err
+ }
+ }
+ return i, nil
+}
+
+func getIndexAndBump(ctx context.Context) (int, error) {
+ i, err := getIndex(ctx)
+ if err != nil {
+ return -1, err
+ }
+ err = Client.Set(ctx, "index", strconv.Itoa(i+1), 0).Err()
+ if err != nil {
+ log.Printf("Problem with redis while trying to store data: %s", err)
+ return -1, err
+ }
+ return i + 1, err
+}
+
+func StoreComment(comment string) (int, error) {
+ ctx := context.Background()
+ i, err := getIndexAndBump(ctx)
+ if err != nil {
+ return -1, err
+ }
+ err = Client.Set(ctx, "comment"+strconv.Itoa(i), comment, 0).Err()
+ if err != nil {
+ log.Printf("Problem with redis while trying to store data: %s", err)
+ return -1, err
+ }
+ return i, err
+}
+
+func GetLatestComments() (string, error) {
+ messages := make([]string, 0)
+ ctx := context.Background()
+ index, err := getIndex(ctx)
+ if err != nil {
+ return "", err
+ }
+ for i := 0; i < 10; i++ {
+ if index-i <= 0 {
+ break
+ }
+ val, err := Client.Get(ctx, "comment"+strconv.Itoa(index-i)).Result()
+ if err != nil && err != redis.Nil {
+ log.Printf("Problem with redis while trying to retrieve data: %s", err)
+ return "", err
+ } else if err == redis.Nil {
+ continue
+ }
+ messages = append(messages, val)
+ }
+ msg, err := json.Marshal(messages)
+ if err != nil {
+ log.Fatalf("Json issue")
+ }
+ return string(msg), err
+}
diff --git a/idvoc-2025/CommentsInteractor/.gitignore b/idvoc-2025/CommentsInteractor/.gitignore
new file mode 100644
index 0000000..f832480
--- /dev/null
+++ b/idvoc-2025/CommentsInteractor/.gitignore
@@ -0,0 +1 @@
+CommentsInteractor
diff --git a/idvoc-2025/CommentsInteractor/Dockerfile b/idvoc-2025/CommentsInteractor/Dockerfile
new file mode 100644
index 0000000..15f9412
--- /dev/null
+++ b/idvoc-2025/CommentsInteractor/Dockerfile
@@ -0,0 +1,24 @@
+FROM reg.undercloud.cri.epita.fr/docker/golang:1.24.3-alpine
+
+LABEL org.opencontainers.image.authors="martial.simon@epita.fr"
+
+RUN apk --no-cache add curl bash
+
+WORKDIR /usr/local/app
+
+COPY go.mod go.sum main.go ./
+
+RUN go install
+
+# Setup an app user so the container doesn't run as the root user
+RUN adduser -D app
+USER app
+
+EXPOSE 9000
+
+ENV COMMENTS_ENGINE_ENDPOINT="commentsengine:8000"
+ENV HOST="0.0.0.0"
+ENV PORT=9000
+
+# Run the app when starting a container
+CMD ["CommentsInteractor"]
diff --git a/idvoc-2025/CommentsInteractor/README.md b/idvoc-2025/CommentsInteractor/README.md
new file mode 100644
index 0000000..0d896bc
--- /dev/null
+++ b/idvoc-2025/CommentsInteractor/README.md
@@ -0,0 +1,5 @@
+## Env variables
+
+`COMMENTS_ENGINE_ENDPOINT`: The endpoint (host + port) where to find CommentsEngine
+`HOST`: The host on which to bind+listen to
+`PORT`: The port on which to bind+listen to
diff --git a/idvoc-2025/CommentsInteractor/go.mod b/idvoc-2025/CommentsInteractor/go.mod
new file mode 100644
index 0000000..dcebe1f
--- /dev/null
+++ b/idvoc-2025/CommentsInteractor/go.mod
@@ -0,0 +1,18 @@
+module CommentsInteractor
+
+go 1.21.5
+
+require github.com/gorilla/handlers v1.5.2
+
+require (
+ github.com/beorn7/perks v1.0.1 // indirect
+ github.com/cespare/xxhash/v2 v2.2.0 // indirect
+ github.com/felixge/httpsnoop v1.0.3 // indirect
+ github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
+ github.com/prometheus/client_golang v1.18.0 // indirect
+ github.com/prometheus/client_model v0.5.0 // indirect
+ github.com/prometheus/common v0.45.0 // indirect
+ github.com/prometheus/procfs v0.12.0 // indirect
+ golang.org/x/sys v0.15.0 // indirect
+ google.golang.org/protobuf v1.31.0 // indirect
+)
diff --git a/idvoc-2025/CommentsInteractor/go.sum b/idvoc-2025/CommentsInteractor/go.sum
new file mode 100644
index 0000000..33b6970
--- /dev/null
+++ b/idvoc-2025/CommentsInteractor/go.sum
@@ -0,0 +1,26 @@
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
+github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
+github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE=
+github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w=
+github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg=
+github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k=
+github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk=
+github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA=
+github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
+github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
+github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM=
+github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY=
+github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
+github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
+golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
+golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
+google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
diff --git a/idvoc-2025/CommentsInteractor/main.go b/idvoc-2025/CommentsInteractor/main.go
new file mode 100644
index 0000000..9a19243
--- /dev/null
+++ b/idvoc-2025/CommentsInteractor/main.go
@@ -0,0 +1,147 @@
+package main
+
+import (
+ "bytes"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "github.com/gorilla/handlers"
+ "github.com/prometheus/client_golang/prometheus"
+ "github.com/prometheus/client_golang/prometheus/promauto"
+ "github.com/prometheus/client_golang/prometheus/promhttp"
+ "io"
+ "log"
+ "net/http"
+ "os"
+ "strconv"
+)
+
+func getenv(key, fallback string) string {
+ value := os.Getenv(key)
+ if len(value) == 0 {
+ return fallback
+ }
+ return value
+}
+
+type Comment struct {
+ Comment string `json:"comment"`
+}
+
+type EngineError struct {
+ Message string `json:"message"`
+ AdditionalInfo string `json:"additionalInfo"`
+}
+
+var comments_engine_endpoint string
+var (
+ httpHits = promauto.NewCounterVec(prometheus.CounterOpts{
+ Name: "comments_interactor_http_hits_total",
+ Help: "The total number of hits on a given route",
+ }, []string{"route", "method"})
+ commentsReceived = promauto.NewCounter(prometheus.CounterOpts{
+ Name: "comments_interactor_comments_received_total",
+ Help: "The total number of comments received (successfully posted or not)",
+ })
+ commentsPosted = promauto.NewCounter(prometheus.CounterOpts{
+ Name: "comments_interactor_comments_posted_total",
+ Help: "The total number of comments posted (successfully posted)",
+ })
+)
+
+func sendComment(w http.ResponseWriter, r *http.Request) {
+ commentsReceived.Inc()
+ if err := r.ParseForm(); err != nil {
+ fmt.Fprintf(w, "<h1>error</h1><p>ParseForm() err: %v</p>", err)
+ return
+ }
+ var comment Comment
+ comment.Comment = r.FormValue("comment")
+ if comment.Comment == "" {
+ fmt.Fprintf(w, "<h1>error</h1><p>Empty comment or malformed</p>")
+ return
+ }
+ obj, err := json.Marshal(comment)
+ if err != nil {
+ fmt.Fprintf(w, "<h1>error</h1><p>Empty comment or malformed</p>")
+ return
+ }
+ _, err = http.Post("http://"+comments_engine_endpoint+"/comment", "application/json", bytes.NewReader(obj))
+ if err == nil {
+ fmt.Fprintf(w, "<h1>Success !</h1><p>Comment posted</p>")
+ commentsPosted.Inc()
+ } else {
+ fmt.Fprintf(w, "<h1>Error</h1><p>Error while trying to send a comment: %s", err)
+ }
+}
+
+func showDashboard(w http.ResponseWriter, r *http.Request) {
+ io.WriteString(w, "<h1>Latest comments</h1>")
+ io.WriteString(w, "<p>")
+ resp, err := http.Get("http://" + comments_engine_endpoint + "/latest")
+ if err != nil {
+ log.Printf("Could not HTTP get on CommentsEngine: %s", err)
+ io.WriteString(w, fmt.Sprintf("Error contacting the backend, %s", err))
+ } else {
+ defer resp.Body.Close()
+ body, err := io.ReadAll(resp.Body)
+ var comments []string
+ err = json.Unmarshal(body, &comments)
+ if err != nil {
+ var engineError EngineError
+ err = json.Unmarshal(body, &engineError)
+ if err != nil {
+ log.Printf("CommentsEngine returned unexpected value: %s", err)
+ io.WriteString(w, fmt.Sprintf("CommentsEngine returned badly formated values, %s", err))
+ } else {
+ io.WriteString(w, fmt.Sprintf("CommentsEngine returned an error: %s<br/>Additional info: %s", engineError.Message, engineError.AdditionalInfo))
+ }
+ } else {
+ for i, comment := range comments {
+ io.WriteString(w, "<div><h3>comment "+strconv.Itoa(i)+"</h3><p>"+comment+"</p></div>")
+ }
+ }
+ }
+ io.WriteString(w, "</p>")
+ io.WriteString(w, "<h1>Send comment</h1>")
+ io.WriteString(w, "<form method=\"post\"><textarea name=\"comment\" autofocus=\"true\" placeholder=\"your comment ...\" rows=\"5\" cols=\"80\"></textarea><br/><input type=\"submit\" value=\"send comment\"></form>")
+}
+
+func getRoot(w http.ResponseWriter, r *http.Request) {
+ httpHits.With(prometheus.Labels{"route": "/", "method": r.Method}).Inc()
+ io.WriteString(w, "<!doctype html><html><head><title>CommentsInteractor</title></head><body>")
+ if r.Method == http.MethodPost {
+ sendComment(w, r)
+ }
+ showDashboard(w, r)
+ io.WriteString(w, "</body></html>")
+}
+
+func sendError(w http.ResponseWriter, err error, additionalInfo string) {
+ w.WriteHeader(http.StatusInternalServerError)
+ resp := make(map[string]string)
+ resp["message"] = fmt.Sprint(err)
+ resp["additionalInfo"] = additionalInfo
+ jsonResp, err := json.Marshal(resp)
+ if err != nil {
+ log.Fatalf("Error happened in JSON marshal. Err: %s", err)
+ }
+ w.Write(jsonResp)
+}
+
+func main() {
+ http.HandleFunc("/", getRoot)
+ prometheus.Unregister(prometheus.NewGoCollector())
+ http.Handle("/metrics", promhttp.Handler())
+
+ listeningHostPort := getenv("HOST", "127.0.0.1") + ":" + getenv("PORT", "9000")
+ comments_engine_endpoint = getenv("COMMENTS_ENGINE_ENDPOINT", "127.0.0.1:8000")
+ fmt.Printf("Starting the server on %s\n", listeningHostPort)
+ err := http.ListenAndServe(listeningHostPort, handlers.LoggingHandler(os.Stdout, http.DefaultServeMux))
+ if errors.Is(err, http.ErrServerClosed) {
+ fmt.Printf("server closed\n")
+ } else if err != nil {
+ fmt.Printf("error starting server: %s\n", err)
+ os.Exit(1)
+ }
+}
diff --git a/idvoc-2025/docker-compose.yml b/idvoc-2025/docker-compose.yml
new file mode 100644
index 0000000..624b349
--- /dev/null
+++ b/idvoc-2025/docker-compose.yml
@@ -0,0 +1,48 @@
+version: "3"
+
+services:
+ commentsinteractor:
+ container_name: commentsinteractor
+ hostname: commentsinteractor
+ build:
+ context: ./CommentsInteractor
+ dockerfile: Dockerfile
+ ports:
+ - 80:9000
+ depends_on:
+ - commentsengine
+ networks:
+ - interactor
+ deploy: &maxence
+ resources:
+ limits:
+ cpus: '2'
+ memory: 150M
+ commentsengine:
+ container_name: commentsengine
+ hostname: commentsengine
+ build:
+ context: CommentsEngine
+ dockerfile: Dockerfile
+ depends_on:
+ - redis
+ networks:
+ - interactor
+ - database
+ deploy: *maxence
+ redis:
+ container_name: redis
+ hostname: redis
+ image: reg.undercloud.cri.epita.fr/docker/redis:8.0.1-alpine
+ volumes:
+ - mon_volume_nomme_pas_compris_comment_ca_marche:/data
+ networks:
+ - database
+ deploy: *maxence
+
+volumes:
+ mon_volume_nomme_pas_compris_comment_ca_marche:
+
+networks:
+ interactor:
+ database:
diff --git a/idvoc-2025/metrics/README.md b/idvoc-2025/metrics/README.md
new file mode 100644
index 0000000..02b4566
--- /dev/null
+++ b/idvoc-2025/metrics/README.md
@@ -0,0 +1,6 @@
+# Metrics
+
+Use ./start.sh to start this small metrics stack. You can do it manually if
+needed, peek a look at start.sh.
+
+You'll have to edit prometheus.yml and docker-compose.yml to fix the project
diff --git a/idvoc-2025/metrics/docker-compose.yml b/idvoc-2025/metrics/docker-compose.yml
new file mode 100644
index 0000000..4a7af07
--- /dev/null
+++ b/idvoc-2025/metrics/docker-compose.yml
@@ -0,0 +1,25 @@
+---
+
+version: "3"
+
+services:
+ prometheus:
+ image: prom/prometheus:v2.45.2
+ container_name: prometheus
+ # FIXME Provide prometheus.yml
+ volumes:
+ - /tmp/prometheus.yml:/data
+ # FIXME Expose prometheus locally
+ ports: 9090
+ # FIXME Make prometheus able to reach CommentsEngine and CommentsInteractor
+
+ grafana:
+ image: grafana/grafana:10.2.3
+ container_name: grafana
+ ports:
+ - 127.0.0.1:3000:3000
+ volumes:
+ - grafana-storage:/var/lib/grafana
+
+volumes:
+ grafana-storage: {}
diff --git a/idvoc-2025/metrics/prometheus.yml b/idvoc-2025/metrics/prometheus.yml
new file mode 100644
index 0000000..27def20
--- /dev/null
+++ b/idvoc-2025/metrics/prometheus.yml
@@ -0,0 +1,16 @@
+global:
+ scrape_interval: 5s # Set the scrape interval to every 15 seconds. Default is every 1 minute.
+
+# A scrape configuration containing exactly one endpoint to scrape:
+# Here it's Prometheus itself.
+scrape_configs:
+ # The job name is added as a label `job=<job_name>` to any timeseries scraped from this config.
+ - job_name: "prometheus"
+
+ # metrics_path defaults to '/metrics'
+ # scheme defaults to 'http'.
+
+ static_configs:
+ - targets: ["localhost:9090"]
+
+ # FIXME Expand scrape_configs here
diff --git a/idvoc-2025/metrics/start.sh b/idvoc-2025/metrics/start.sh
new file mode 100755
index 0000000..e1bab25
--- /dev/null
+++ b/idvoc-2025/metrics/start.sh
@@ -0,0 +1,10 @@
+#!/usr/bin/env bash
+
+set -eExuo pipefail
+
+# This basic script is needed to cope with the PIE's limitation regarding
+# docker and each student's home directory. One cannot mount with docker
+# files from home directory.
+
+cp prometheus.yml /tmp/prometheus.yml
+docker compose up -d || docker-compose up -d