summaryrefslogtreecommitdiff
path: root/pkg
diff options
context:
space:
mode:
authorJulien Dessaux2024-12-30 23:07:00 +0100
committerJulien Dessaux2024-12-30 23:07:00 +0100
commit36e3d473f2126e920061218996cfa5cfecade7d6 (patch)
treec77078316db50cd5da7e68ddb3a8cb830ba5c612 /pkg
parentchore(tfstated): refactor setting last login date time on successful HTTP bas... (diff)
downloadtfstated-36e3d473f2126e920061218996cfa5cfecade7d6.tar.gz
tfstated-36e3d473f2126e920061218996cfa5cfecade7d6.tar.bz2
tfstated-36e3d473f2126e920061218996cfa5cfecade7d6.zip
chore(tfstated): refactor backend code to a dedicated package
Diffstat (limited to 'pkg')
-rw-r--r--pkg/backend/delete.go28
-rw-r--r--pkg/backend/get.go28
-rw-r--r--pkg/backend/healthz.go12
-rw-r--r--pkg/backend/lock.go61
-rw-r--r--pkg/backend/post.go40
-rw-r--r--pkg/backend/routes.go22
-rw-r--r--pkg/backend/run.go51
-rw-r--r--pkg/backend/unlock.go32
8 files changed, 274 insertions, 0 deletions
diff --git a/pkg/backend/delete.go b/pkg/backend/delete.go
new file mode 100644
index 0000000..61007c4
--- /dev/null
+++ b/pkg/backend/delete.go
@@ -0,0 +1,28 @@
+package backend
+
+import (
+ "fmt"
+ "net/http"
+
+ "git.adyxax.org/adyxax/tfstated/pkg/database"
+ "git.adyxax.org/adyxax/tfstated/pkg/helpers"
+)
+
+func handleDelete(db *database.DB) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if r.URL.Path == "/" {
+ helpers.ErrorResponse(w, http.StatusBadRequest,
+ fmt.Errorf("no state path provided, cannot DELETE /"))
+ return
+ }
+
+ if success, err := db.DeleteState(r.URL.Path); err != nil {
+ helpers.ErrorResponse(w, http.StatusInternalServerError, err)
+ } else if success {
+ w.WriteHeader(http.StatusOK)
+ } else {
+ helpers.ErrorResponse(w, http.StatusNotFound,
+ fmt.Errorf("state path not found: %s", r.URL.Path))
+ }
+ })
+}
diff --git a/pkg/backend/get.go b/pkg/backend/get.go
new file mode 100644
index 0000000..ca9b2c0
--- /dev/null
+++ b/pkg/backend/get.go
@@ -0,0 +1,28 @@
+package backend
+
+import (
+ "fmt"
+ "net/http"
+
+ "git.adyxax.org/adyxax/tfstated/pkg/database"
+ "git.adyxax.org/adyxax/tfstated/pkg/helpers"
+)
+
+func handleGet(db *database.DB) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Cache-Control", "no-store, no-cache")
+
+ if r.URL.Path == "/" {
+ helpers.ErrorResponse(w, http.StatusBadRequest,
+ fmt.Errorf("no state path provided, cannot GET /"))
+ return
+ }
+
+ if data, err := db.GetState(r.URL.Path); err != nil {
+ helpers.ErrorResponse(w, http.StatusInternalServerError, err)
+ } else {
+ w.WriteHeader(http.StatusOK)
+ _, _ = w.Write(data)
+ }
+ })
+}
diff --git a/pkg/backend/healthz.go b/pkg/backend/healthz.go
new file mode 100644
index 0000000..70ece68
--- /dev/null
+++ b/pkg/backend/healthz.go
@@ -0,0 +1,12 @@
+package backend
+
+import "net/http"
+
+func handleHealthz() http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Cache-Control", "no-store, no-cache")
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusOK)
+ _, _ = w.Write([]byte("{}"))
+ })
+}
diff --git a/pkg/backend/lock.go b/pkg/backend/lock.go
new file mode 100644
index 0000000..ef62198
--- /dev/null
+++ b/pkg/backend/lock.go
@@ -0,0 +1,61 @@
+package backend
+
+import (
+ "fmt"
+ "net/http"
+ "regexp"
+ "time"
+
+ "git.adyxax.org/adyxax/tfstated/pkg/database"
+ "git.adyxax.org/adyxax/tfstated/pkg/helpers"
+)
+
+type lockRequest struct {
+ Created time.Time `json:"Created"`
+ ID string `json:"ID"`
+ Info string `json:"Info"`
+ Operation string `json:"Operation"`
+ Path string `json:"Path"`
+ Version string `json:"Version"`
+ Who string `json:"Who"`
+}
+
+var (
+ validID = regexp.MustCompile("[a-f0-9]{8}-(?:[a-f0-9]{4}-){3}[a-f0-9]{12}")
+)
+
+func (l *lockRequest) valid() []error {
+ err := make([]error, 0)
+ if !validID.MatchString(l.ID) {
+ err = append(err, fmt.Errorf("invalid ID"))
+ }
+ return err
+}
+
+func handleLock(db *database.DB) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if r.URL.Path == "/" {
+ _ = helpers.Encode(w, http.StatusBadRequest,
+ fmt.Errorf("no state path provided, cannot LOCK /"))
+ return
+ }
+
+ var lock lockRequest
+ if err := helpers.Decode(r, &lock); err != nil {
+ _ = helpers.Encode(w, http.StatusBadRequest, err)
+ return
+ }
+ if errs := lock.valid(); len(errs) > 0 {
+ _ = helpers.Encode(w, http.StatusBadRequest,
+ fmt.Errorf("invalid lock: %+v", errs))
+ return
+ }
+ if success, err := db.SetLockOrGetExistingLock(r.URL.Path, &lock); err != nil {
+ helpers.ErrorResponse(w, http.StatusInternalServerError, err)
+ } else if success {
+ w.WriteHeader(http.StatusOK)
+ } else {
+ _ = helpers.Encode(w, http.StatusConflict, lock)
+ }
+ })
+}
diff --git a/pkg/backend/post.go b/pkg/backend/post.go
new file mode 100644
index 0000000..8271022
--- /dev/null
+++ b/pkg/backend/post.go
@@ -0,0 +1,40 @@
+package backend
+
+import (
+ "fmt"
+ "io"
+ "net/http"
+
+ "git.adyxax.org/adyxax/tfstated/pkg/database"
+ "git.adyxax.org/adyxax/tfstated/pkg/helpers"
+ "git.adyxax.org/adyxax/tfstated/pkg/model"
+)
+
+func handlePost(db *database.DB) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if r.URL.Path == "/" {
+ helpers.ErrorResponse(w, http.StatusBadRequest,
+ fmt.Errorf("no state path provided, cannot POST /"),
+ )
+ return
+ }
+
+ id := r.URL.Query().Get("ID")
+
+ data, err := io.ReadAll(r.Body)
+ if err != nil || len(data) == 0 {
+ helpers.ErrorResponse(w, http.StatusBadRequest, err)
+ return
+ }
+ account := r.Context().Value(model.AccountContextKey{}).(*model.Account)
+ if idMismatch, err := db.SetState(r.URL.Path, account.Id, data, id); err != nil {
+ if idMismatch {
+ helpers.ErrorResponse(w, http.StatusConflict, err)
+ } else {
+ helpers.ErrorResponse(w, http.StatusInternalServerError, err)
+ }
+ } else {
+ w.WriteHeader(http.StatusOK)
+ }
+ })
+}
diff --git a/pkg/backend/routes.go b/pkg/backend/routes.go
new file mode 100644
index 0000000..058febd
--- /dev/null
+++ b/pkg/backend/routes.go
@@ -0,0 +1,22 @@
+package backend
+
+import (
+ "net/http"
+
+ "git.adyxax.org/adyxax/tfstated/pkg/basic_auth"
+ "git.adyxax.org/adyxax/tfstated/pkg/database"
+)
+
+func addRoutes(
+ mux *http.ServeMux,
+ db *database.DB,
+) {
+ mux.Handle("GET /healthz", handleHealthz())
+
+ basicAuth := basic_auth.Middleware(db)
+ mux.Handle("DELETE /", basicAuth(handleDelete(db)))
+ mux.Handle("GET /", basicAuth(handleGet(db)))
+ mux.Handle("LOCK /", basicAuth(handleLock(db)))
+ mux.Handle("POST /", basicAuth(handlePost(db)))
+ mux.Handle("UNLOCK /", basicAuth(handleUnlock(db)))
+}
diff --git a/pkg/backend/run.go b/pkg/backend/run.go
new file mode 100644
index 0000000..dd7f3bf
--- /dev/null
+++ b/pkg/backend/run.go
@@ -0,0 +1,51 @@
+package backend
+
+import (
+ "context"
+ "fmt"
+ "io"
+ "log"
+ "net"
+ "net/http"
+
+ "git.adyxax.org/adyxax/tfstated/pkg/database"
+ "git.adyxax.org/adyxax/tfstated/pkg/logger"
+)
+
+func Run(
+ ctx context.Context,
+ db *database.DB,
+ //args []string,
+ getenv func(string) string,
+ //stdin io.Reader,
+ //stdout io.Writer,
+ stderr io.Writer,
+) *http.Server {
+ mux := http.NewServeMux()
+ addRoutes(
+ mux,
+ db,
+ )
+
+ host := getenv("TFSTATED_HOST")
+ if host == "" {
+ host = "127.0.0.1"
+ }
+ port := getenv("TFSTATED_PORT")
+ if port == "" {
+ port = "8080"
+ }
+
+ httpServer := &http.Server{
+ Addr: net.JoinHostPort(host, port),
+ Handler: logger.Middleware(mux, false),
+ }
+ go func() {
+ log.Printf("listening on %s\n", httpServer.Addr)
+ if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
+ _, _ = fmt.Fprintf(stderr, "error listening and serving: %+v\n", err)
+ }
+ }()
+
+ return httpServer
+}
diff --git a/pkg/backend/unlock.go b/pkg/backend/unlock.go
new file mode 100644
index 0000000..bc601f0
--- /dev/null
+++ b/pkg/backend/unlock.go
@@ -0,0 +1,32 @@
+package backend
+
+import (
+ "fmt"
+ "net/http"
+
+ "git.adyxax.org/adyxax/tfstated/pkg/database"
+ "git.adyxax.org/adyxax/tfstated/pkg/helpers"
+)
+
+func handleUnlock(db *database.DB) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if r.URL.Path == "/" {
+ _ = helpers.Encode(w, http.StatusBadRequest,
+ fmt.Errorf("no state path provided, cannot LOCK /"))
+ return
+ }
+
+ var lock lockRequest
+ if err := helpers.Decode(r, &lock); err != nil {
+ _ = helpers.Encode(w, http.StatusBadRequest, err)
+ return
+ }
+ if success, err := db.Unlock(r.URL.Path, &lock); err != nil {
+ helpers.ErrorResponse(w, http.StatusInternalServerError, err)
+ } else if success {
+ w.WriteHeader(http.StatusOK)
+ } else {
+ _ = helpers.Encode(w, http.StatusConflict, lock)
+ }
+ })
+}