From 4ff490806c826cf2da4c2291ed924f0a49383fce Mon Sep 17 00:00:00 2001 From: Julien Dessaux Date: Mon, 30 Sep 2024 00:58:49 +0200 Subject: feat(tfstated): implement GET and POST methods, states are encrypted in a sqlite3 database --- cmd/tfstated/get.go | 35 +++++++++++++++++++++++++++++++++++ cmd/tfstated/main.go | 21 +++++++++++++++++++++ cmd/tfstated/post.go | 32 ++++++++++++++++++++++++++++++++ cmd/tfstated/routes.go | 13 +++++++++++-- 4 files changed, 99 insertions(+), 2 deletions(-) create mode 100644 cmd/tfstated/get.go create mode 100644 cmd/tfstated/post.go (limited to 'cmd') diff --git a/cmd/tfstated/get.go b/cmd/tfstated/get.go new file mode 100644 index 0000000..6de78a5 --- /dev/null +++ b/cmd/tfstated/get.go @@ -0,0 +1,35 @@ +package main + +import ( + "database/sql" + "errors" + "fmt" + "net/http" + + "git.adyxax.org/adyxax/tfstated/pkg/database" +) + +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") + w.Header().Set("Content-Type", "application/json") + + if r.URL.Path == "/" { + w.WriteHeader(http.StatusBadRequest) + _, _ = w.Write([]byte("{\"msg\": \"No state path provided, cannot GET /\"}")) + return + } + + if data, err := db.GetState(r.URL.Path); err != nil { + if errors.Is(err, sql.ErrNoRows) { + w.WriteHeader(http.StatusNotFound) + } else { + w.WriteHeader(http.StatusInternalServerError) + } + _, _ = w.Write([]byte(fmt.Sprintf("{\"msg\": \"%+v\"}", err))) + } else { + w.WriteHeader(http.StatusOK) + _, _ = w.Write(data) + } + }) +} diff --git a/cmd/tfstated/main.go b/cmd/tfstated/main.go index e0b33a5..61248bc 100644 --- a/cmd/tfstated/main.go +++ b/cmd/tfstated/main.go @@ -12,6 +12,8 @@ import ( "os/signal" "sync" "time" + + "git.adyxax.org/adyxax/tfstated/pkg/database" ) type Config struct { @@ -22,6 +24,7 @@ type Config struct { func run( ctx context.Context, config *Config, + db *database.DB, args []string, getenv func(string) string, stdin io.Reader, @@ -30,10 +33,20 @@ func run( ctx, cancel := signal.NotifyContext(ctx, os.Interrupt) defer cancel() + dataEncryptionKey := getenv("DATA_ENCRYPTION_KEY") + if dataEncryptionKey == "" { + return fmt.Errorf("the DATA_ENCRYPTION_KEY environment variable is not set") + } + if err := db.SetDataEncryptionKey(dataEncryptionKey); err != nil { + return err + } + mux := http.NewServeMux() addRoutes( mux, + db, ) + httpServer := &http.Server{ Addr: net.JoinHostPort(config.Host, config.Port), Handler: mux, @@ -79,9 +92,17 @@ func main() { Port: "8080", } + db, err := database.NewDB(ctx, "./tfstate.db?_txlock=immediate") + if err != nil { + fmt.Fprintf(os.Stderr, "database init error: %+v\n", err) + os.Exit(1) + } + defer db.Close() + if err := run( ctx, &config, + db, os.Args, os.Getenv, os.Stdin, diff --git a/cmd/tfstated/post.go b/cmd/tfstated/post.go new file mode 100644 index 0000000..e47f5ad --- /dev/null +++ b/cmd/tfstated/post.go @@ -0,0 +1,32 @@ +package main + +import ( + "fmt" + "io" + "net/http" + + "git.adyxax.org/adyxax/tfstated/pkg/database" +) + +func handlePost(db *database.DB) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + if r.URL.Path == "/" { + w.WriteHeader(http.StatusBadRequest) + _, _ = w.Write([]byte("{\"msg\": \"No state path provided, cannot POST /\"}")) + return + } + data, err := io.ReadAll(r.Body) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + _, _ = w.Write([]byte(fmt.Sprintf("{\"msg\": \"failed to read request body: %+v\"}", err))) + return + } + if err := db.SetState(r.URL.Path, data); err != nil { + w.WriteHeader(http.StatusInternalServerError) + _, _ = w.Write([]byte(fmt.Sprintf("{\"msg\": \"%+v\"}", err))) + } else { + w.WriteHeader(http.StatusOK) + } + }) +} diff --git a/cmd/tfstated/routes.go b/cmd/tfstated/routes.go index f583fc7..32ab111 100644 --- a/cmd/tfstated/routes.go +++ b/cmd/tfstated/routes.go @@ -1,7 +1,16 @@ package main -import "net/http" +import ( + "net/http" -func addRoutes(mux *http.ServeMux) { + "git.adyxax.org/adyxax/tfstated/pkg/database" +) + +func addRoutes( + mux *http.ServeMux, + db *database.DB, +) { mux.Handle("GET /healthz", handleHealthz()) + mux.Handle("GET /", handleGet(db)) + mux.Handle("POST /", handlePost(db)) } -- cgit v1.2.3