From 6c035fc391a2643d9e3bfc8236c279bf24bc6245 Mon Sep 17 00:00:00 2001 From: juls0730 <62722391+juls0730@users.noreply.github.com> Date: Mon, 9 Dec 2024 06:03:45 -0600 Subject: [PATCH] small bug fixes and organization --- .gitignore | 2 + README.md | 17 +-- cmd/{cli => flux}/config.json | 0 cmd/{cli => flux}/main.go | 2 +- cmd/{daemon => fluxd}/main.go | 2 +- go.mod | 2 +- server/app.go | 239 ++++++++++++++++++++++++++++++++++ server/container.go | 15 ++- server/deploy.go | 89 +++---------- server/deployment.go | 206 +---------------------------- server/proxy.go | 2 +- server/server.go | 2 +- 12 files changed, 289 insertions(+), 289 deletions(-) rename cmd/{cli => flux}/config.json (100%) rename cmd/{cli => flux}/main.go (99%) rename cmd/{daemon => fluxd}/main.go (95%) create mode 100644 server/app.go diff --git a/.gitignore b/.gitignore index 23435eb..bffa5f4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ fluxd flux +!cmd/flux +!cmd/fluxd fluxdd/ \ No newline at end of file diff --git a/README.md b/README.md index 3b550b8..57a2bb8 100644 --- a/README.md +++ b/README.md @@ -7,23 +7,22 @@ Flux is a lightweight self-hosted pseudo-paas for golang web apps that emphasize ## Usage To get started you'll want [ZQDGR](https://github.com/juls0730/zqdgr), and you can start the daemon either with: + ``` zqdgr build:daemon sudo ./fluxd ``` -or with + +or with + ``` FLUXD_ROOT_DIR=$PWD/fluxdd zqdgr run:daemon ``` -To get started with the cli you can run either -``` -zqdgr build:cli -./flux list -``` -or +To get started with the cli you can run + ``` -zqdgr run:cli -- list +go install github.com/juls0730/flux/cmd/flux@latest ``` TODO: `go install` instructions and a docker image (sowwy) @@ -76,7 +75,9 @@ flux.json is the configuration file for a project, it contains the name of the p - [Docker](https://docs.docker.com/get-docker/) (daemon only) ## Contributing + Found a bug, or have something you think would make Flux better? Submit an issue or pull request. ## License + Flux is licensed with the MIT license diff --git a/cmd/cli/config.json b/cmd/flux/config.json similarity index 100% rename from cmd/cli/config.json rename to cmd/flux/config.json diff --git a/cmd/cli/main.go b/cmd/flux/main.go similarity index 99% rename from cmd/cli/main.go rename to cmd/flux/main.go index 9e0b728..7053206 100644 --- a/cmd/cli/main.go +++ b/cmd/flux/main.go @@ -20,7 +20,7 @@ import ( "time" "github.com/briandowns/spinner" - "github.com/juls0730/fluxd/pkg" + "github.com/juls0730/flux/pkg" ) //go:embed config.json diff --git a/cmd/daemon/main.go b/cmd/fluxd/main.go similarity index 95% rename from cmd/daemon/main.go rename to cmd/fluxd/main.go index 1c25b70..7c62577 100644 --- a/cmd/daemon/main.go +++ b/cmd/fluxd/main.go @@ -5,7 +5,7 @@ import ( "net/http" _ "net/http/pprof" - "github.com/juls0730/fluxd/server" + "github.com/juls0730/flux/server" ) func main() { diff --git a/go.mod b/go.mod index 45dc159..4d9fd96 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/juls0730/fluxd +module github.com/juls0730/flux go 1.23.3 diff --git a/server/app.go b/server/app.go new file mode 100644 index 0000000..5d52441 --- /dev/null +++ b/server/app.go @@ -0,0 +1,239 @@ +package server + +import ( + "context" + "database/sql" + "fmt" + "log" + "os" + "path/filepath" + "sync" + + "github.com/juls0730/flux/pkg" +) + +type App struct { + ID int64 `json:"id,omitempty"` + Deployment Deployment `json:"-"` + Name string `json:"name,omitempty"` + DeploymentID int64 `json:"deployment_id,omitempty"` +} + +func CreateApp(ctx context.Context, imageName string, projectPath string, projectConfig pkg.ProjectConfig) (*App, error) { + app := &App{ + Name: projectConfig.Name, + } + log.Printf("Creating deployment %s...\n", app.Name) + + container, err := CreateDockerContainer(ctx, imageName, projectPath, projectConfig) + if err != nil || container == nil { + + return nil, fmt.Errorf("Failed to create container: %v", err) + } + + deployment, err := CreateDeployment(*container, projectConfig.Port, projectConfig.Url, Flux.db) + app.Deployment = deployment + if err != nil { + log.Printf("Failed to create deployment: %v", err) + return nil, err + } + + if appInsertStmt == nil { + appInsertStmt, err = Flux.db.Prepare("INSERT INTO apps (name, deployment_id) VALUES ($1, $2) RETURNING id, name, deployment_id") + if err != nil { + return nil, fmt.Errorf("Failed to prepare statement: %v", err) + } + } + + // create app in the database + err = appInsertStmt.QueryRow(projectConfig.Name, deployment.ID).Scan(&app.ID, &app.Name, &app.DeploymentID) + if err != nil { + return nil, fmt.Errorf("Failed to insert app: %v", err) + } + + err = deployment.Start(ctx) + if err != nil { + return nil, fmt.Errorf("Failed to start deployment: %v", err) + } + + var headContainer *Container + for _, container := range deployment.Containers { + if container.Head { + headContainer = &container + } + } + + deployment.Proxy, err = NewDeploymentProxy(&deployment, headContainer) + if err != nil { + return nil, fmt.Errorf("Failed to create deployment proxy: %v", err) + } + + Flux.proxy.AddDeployment(&deployment) + + Flux.appManager.AddApp(app.Name, app) + + return app, nil +} + +func (app *App) Upgrade(ctx context.Context, projectConfig pkg.ProjectConfig, imageName string, projectPath string) error { + log.Printf("Upgrading deployment %s...\n", app.Name) + + // if deploy is not started, start it + deploymentStatus, err := app.Deployment.Status(ctx) + if deploymentStatus != "running" || err != nil { + err = app.Deployment.Start(ctx) + if err != nil { + return fmt.Errorf("Failed to start deployment: %v", err) + } + } + + err = app.Deployment.Upgrade(ctx, projectConfig, imageName, projectPath) + if err != nil { + return fmt.Errorf("Failed to upgrade deployment: %v", err) + } + + return nil +} + +func (app *App) Remove(ctx context.Context) error { + err := app.Deployment.Remove(ctx) + if err != nil { + log.Printf("Failed to remove deployment: %v\n", err) + return err + } + + _, err = Flux.db.Exec("DELETE FROM apps WHERE id = ?", app.ID) + if err != nil { + log.Printf("Failed to delete app: %v\n", err) + return err + } + + projectPath := filepath.Join(Flux.rootDir, "apps", app.Name) + err = os.RemoveAll(projectPath) + if err != nil { + return fmt.Errorf("Failed to remove project directory: %v", err) + } + + return nil +} + +type AppManager struct { + sync.Map +} + +func (am *AppManager) GetApp(name string) *App { + app, exists := am.Load(name) + if !exists { + return nil + } + + return app.(*App) +} + +func (am *AppManager) GetAllApps() []*App { + var apps []*App + am.Range(func(key, value interface{}) bool { + if app, ok := value.(*App); ok { + apps = append(apps, app) + } + return true + }) + return apps +} + +func (am *AppManager) AddApp(name string, app *App) { + am.Store(name, app) +} + +func (am *AppManager) DeleteApp(name string) error { + app := am.GetApp(name) + if app == nil { + return fmt.Errorf("App not found") + } + + err := app.Remove(context.Background()) + if err != nil { + return err + } + + am.Delete(name) + + return nil +} + +func (am *AppManager) Init(db *sql.DB) { + log.Printf("Initializing deployments...\n") + + if db == nil { + log.Panicf("DB is nil") + } + + rows, err := db.Query("SELECT id, name, deployment_id FROM apps") + if err != nil { + log.Printf("Failed to query apps: %v\n", err) + return + } + defer rows.Close() + + var apps []App + for rows.Next() { + var app App + if err := rows.Scan(&app.ID, &app.Name, &app.DeploymentID); err != nil { + log.Printf("Failed to scan app: %v\n", err) + return + } + apps = append(apps, app) + } + + for _, app := range apps { + var deployment Deployment + var headContainer *Container + db.QueryRow("SELECT id, url, port FROM deployments WHERE id = ?", app.DeploymentID).Scan(&deployment.ID, &deployment.URL, &deployment.Port) + deployment.Containers = make([]Container, 0) + + rows, err = db.Query("SELECT id, container_id, deployment_id, head FROM containers WHERE deployment_id = ?", app.DeploymentID) + if err != nil { + log.Printf("Failed to query containers: %v\n", err) + return + } + defer rows.Close() + + for rows.Next() { + var container Container + var containerIDString string + rows.Scan(&container.ID, &containerIDString, &container.DeploymentID, &container.Head) + container.Deployment = &deployment + copy(container.ContainerID[:], containerIDString) + + if container.Head { + headContainer = &container + } + + deployment.Containers = append(deployment.Containers, container) + } + + for i, container := range deployment.Containers { + var volumes []Volume + rows, err := db.Query("SELECT id, volume_id, container_id FROM volumes WHERE container_id = ?", container.ID) + if err != nil { + log.Printf("Failed to query volumes: %v\n", err) + return + } + defer rows.Close() + + for rows.Next() { + var volume Volume + rows.Scan(&volume.ID, &volume.VolumeID, &volume.ContainerID) + volumes = append(volumes, volume) + } + + deployment.Containers[i].Volumes = volumes + } + + deployment.Proxy, _ = NewDeploymentProxy(&deployment, headContainer) + + app.Deployment = deployment + + am.AddApp(app.Name, &app) + } +} diff --git a/server/container.go b/server/container.go index 3fa6217..4a5fd48 100644 --- a/server/container.go +++ b/server/container.go @@ -15,7 +15,7 @@ import ( "github.com/docker/docker/api/types/volume" "github.com/docker/docker/client" "github.com/joho/godotenv" - "github.com/juls0730/fluxd/pkg" + "github.com/juls0730/flux/pkg" ) var dockerClient *client.Client @@ -45,7 +45,7 @@ func init() { } } -func CreateVolume(ctx context.Context, name string) (vol *Volume, err error) { +func CreateDockerVolume(ctx context.Context, name string) (vol *Volume, err error) { dockerVolume, err := dockerClient.VolumeCreate(ctx, volume.CreateOptions{ Driver: "local", DriverOpts: map[string]string{}, @@ -86,7 +86,7 @@ func CreateDockerContainer(ctx context.Context, imageName, projectPath string, p } } - vol, err := CreateVolume(ctx, fmt.Sprintf("flux_%s-volume", projectConfig.Name)) + vol, err := CreateDockerVolume(ctx, fmt.Sprintf("flux_%s-volume", projectConfig.Name)) log.Printf("Creating container %s...\n", containerName) resp, err := dockerClient.ContainerCreate(ctx, &container.Config{ @@ -177,6 +177,15 @@ func (c *Container) Wait(ctx context.Context, port uint16) error { return WaitForDockerContainer(ctx, string(c.ContainerID[:]), port) } +func (c *Container) Status(ctx context.Context) (string, error) { + containerJSON, err := dockerClient.ContainerInspect(ctx, string(c.ContainerID[:])) + if err != nil { + return "", err + } + + return containerJSON.State.Status, nil +} + // RemoveContainer stops and removes a container, but be warned that this will not remove the container from the database func RemoveDockerContainer(ctx context.Context, containerID string) error { if err := dockerClient.ContainerStop(ctx, containerID, container.StopOptions{}); err != nil { diff --git a/server/deploy.go b/server/deploy.go index ce61813..9f52a08 100644 --- a/server/deploy.go +++ b/server/deploy.go @@ -9,7 +9,7 @@ import ( "net/http" "os/exec" - "github.com/juls0730/fluxd/pkg" + "github.com/juls0730/flux/pkg" ) var ( @@ -98,84 +98,16 @@ func (s *FluxServer) DeployHandler(w http.ResponseWriter, r *http.Request) { app := Flux.appManager.GetApp(projectConfig.Name) if app == nil { - app = &App{ - Name: projectConfig.Name, - } - log.Printf("Creating deployment %s...\n", app.Name) - - container, err := CreateDockerContainer(r.Context(), imageName, projectPath, projectConfig) - if err != nil || container == nil { - log.Printf("Failed to create container: %v\n", err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - deployment, err := CreateDeployment(*container, projectConfig.Port, projectConfig.Url, s.db) - app.Deployment = deployment + app, err = CreateApp(r.Context(), imageName, projectPath, projectConfig) if err != nil { - log.Printf("Failed to create deployment: %v\n", err) + log.Printf("Failed to create app: %v", err) http.Error(w, err.Error(), http.StatusInternalServerError) return } - - if appInsertStmt == nil { - appInsertStmt, err = s.db.Prepare("INSERT INTO apps (name, deployment_id) VALUES ($1, $2) RETURNING id, name, deployment_id") - if err != nil { - log.Printf("Failed to prepare statement: %v\n", err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - } - - // create app in the database - err = appInsertStmt.QueryRow(projectConfig.Name, deployment.ID).Scan(&app.ID, &app.Name, &app.DeploymentID) - if err != nil { - log.Printf("Failed to insert app: %v\n", err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - err = deployment.Start(r.Context()) - if err != nil { - log.Printf("Failed to start deployment: %v\n", err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - var headContainer *Container - for _, container := range deployment.Containers { - if container.Head { - headContainer = &container - } - } - - deployment.Proxy, err = NewDeploymentProxy(&deployment, headContainer) - if err != nil { - log.Printf("Failed to create deployment proxy: %v\n", err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - Flux.proxy.AddDeployment(&deployment) - - Flux.appManager.AddApp(app.Name, app) } else { - log.Printf("Upgrading deployment %s...\n", app.Name) - - // if deploy is not started, start it - deploymentStatus, err := app.Deployment.Status(r.Context()) - if deploymentStatus != "running" || err != nil { - err = app.Deployment.Start(r.Context()) - if err != nil { - log.Printf("Failed to start deployment: %v\n", err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - } - - err = app.Deployment.Upgrade(r.Context(), projectConfig, imageName, projectPath) + err = app.Upgrade(r.Context(), projectConfig, imageName, projectPath) if err != nil { - log.Printf("Failed to upgrade deployment: %v\n", err) + log.Printf("Failed to upgrade deployment: %v", err) http.Error(w, err.Error(), http.StatusInternalServerError) return } @@ -214,6 +146,17 @@ func (s *FluxServer) StartDeployHandler(w http.ResponseWriter, r *http.Request) return } + if app.Deployment.Proxy == nil { + var headContainer *Container + for _, container := range app.Deployment.Containers { + if container.Head { + headContainer = &container + } + } + + app.Deployment.Proxy, _ = NewDeploymentProxy(&app.Deployment, headContainer) + } + w.WriteHeader(http.StatusOK) } diff --git a/server/deployment.go b/server/deployment.go index 313ad15..d256ef2 100644 --- a/server/deployment.go +++ b/server/deployment.go @@ -5,11 +5,8 @@ import ( "database/sql" "fmt" "log" - "os" - "path/filepath" - "sync" - "github.com/juls0730/fluxd/pkg" + "github.com/juls0730/flux/pkg" ) var ( @@ -19,39 +16,6 @@ var ( updateVolumeStmt *sql.Stmt ) -type AppManager struct { - sync.Map -} - -type App struct { - ID int64 `json:"id,omitempty"` - Deployment Deployment `json:"-"` - Name string `json:"name,omitempty"` - DeploymentID int64 `json:"deployment_id,omitempty"` -} - -func (app *App) Remove(ctx context.Context) error { - err := app.Deployment.Remove(ctx) - if err != nil { - log.Printf("Failed to remove deployment: %v\n", err) - return err - } - - _, err = Flux.db.Exec("DELETE FROM apps WHERE id = ?", app.ID) - if err != nil { - log.Printf("Failed to delete app: %v\n", err) - return err - } - - projectPath := filepath.Join(Flux.rootDir, "apps", app.Name) - err = os.RemoveAll(projectPath) - if err != nil { - return fmt.Errorf("Failed to remove project directory: %v", err) - } - - return nil -} - type Deployment struct { ID int64 `json:"id"` Containers []Container `json:"-"` @@ -60,127 +24,6 @@ type Deployment struct { Port uint16 `json:"port"` } -func (am *AppManager) GetApp(name string) *App { - app, exists := am.Load(name) - if !exists { - return nil - } - - return app.(*App) -} - -func (am *AppManager) GetAllApps() []*App { - var apps []*App - am.Range(func(key, value interface{}) bool { - if app, ok := value.(*App); ok { - apps = append(apps, app) - } - return true - }) - return apps -} - -func (am *AppManager) AddApp(name string, app *App) { - am.Store(name, app) -} - -func (am *AppManager) DeleteApp(name string) error { - app := am.GetApp(name) - if app == nil { - return fmt.Errorf("App not found") - } - - err := app.Remove(context.Background()) - if err != nil { - return err - } - - am.Delete(name) - - return nil -} - -func (am *AppManager) Init(db *sql.DB) { - log.Printf("Initializing deployments...\n") - - if db == nil { - log.Panicf("DB is nil") - } - - rows, err := db.Query("SELECT id, name, deployment_id FROM apps") - if err != nil { - log.Printf("Failed to query apps: %v\n", err) - return - } - defer rows.Close() - - var apps []App - for rows.Next() { - var app App - if err := rows.Scan(&app.ID, &app.Name, &app.DeploymentID); err != nil { - log.Printf("Failed to scan app: %v\n", err) - return - } - apps = append(apps, app) - } - - for _, app := range apps { - var deployment Deployment - var headContainer *Container - db.QueryRow("SELECT id, url, port FROM deployments WHERE id = ?", app.DeploymentID).Scan(&deployment.ID, &deployment.URL, &deployment.Port) - deployment.Containers = make([]Container, 0) - - rows, err = db.Query("SELECT id, container_id, deployment_id, head FROM containers WHERE deployment_id = ?", app.DeploymentID) - if err != nil { - log.Printf("Failed to query containers: %v\n", err) - return - } - defer rows.Close() - - for rows.Next() { - var container Container - var containerIDString string - rows.Scan(&container.ID, &containerIDString, &container.DeploymentID, &container.Head) - container.Deployment = &deployment - copy(container.ContainerID[:], containerIDString) - - if container.Head { - headContainer = &container - } - - deployment.Containers = append(deployment.Containers, container) - } - - for i, container := range deployment.Containers { - var volumes []Volume - rows, err := db.Query("SELECT id, volume_id, container_id FROM volumes WHERE container_id = ?", container.ID) - if err != nil { - log.Printf("Failed to query volumes: %v\n", err) - return - } - defer rows.Close() - - for rows.Next() { - var volume Volume - rows.Scan(&volume.ID, &volume.VolumeID, &volume.ContainerID) - volumes = append(volumes, volume) - } - - deployment.Containers[i].Volumes = volumes - } - - deployment.Proxy, err = NewDeploymentProxy(&deployment, headContainer) - if err != nil { - log.Printf("Failed to create deployment proxy: %v\n", err) - return - } - - app.Deployment = deployment - - am.AddApp(app.Name, &app) - } -} - // Creates a deployment and containers in the database func CreateDeployment(container Container, port uint16, appUrl string, db *sql.DB) (Deployment, error) { var deployment Deployment @@ -301,7 +144,7 @@ func (deployment *Deployment) Upgrade(ctx context.Context, projectConfig pkg.Pro copy(container.ContainerID[:], containerIDString) deployment.Containers = append(deployment.Containers, container) - log.Printf("Starting container %s...\n", container.ContainerID[:]) + log.Printf("Starting container %s...\n", container.ContainerID[:12]) err = container.Start(ctx) if err != nil { log.Printf("Failed to start container: %v\n", err) @@ -313,26 +156,8 @@ func (deployment *Deployment) Upgrade(ctx context.Context, projectConfig pkg.Pro return err } - tx, err := Flux.db.Begin() - if err != nil { - log.Printf("Failed to begin transaction: %v\n", err) - return err - } - - if _, err := tx.Exec("UPDATE deployments SET url = ?, port = ? WHERE id = ?", projectConfig.Url, projectConfig.Port, deployment.ID); err != nil { + if _, err := Flux.db.Exec("UPDATE deployments SET url = ?, port = ? WHERE id = ?", projectConfig.Url, projectConfig.Port, deployment.ID); err != nil { log.Printf("Failed to update deployment: %v\n", err) - tx.Rollback() - return err - } - - if _, err := tx.Exec("UPDATE apps SET deployment_id = ? WHERE name = ?", deployment.ID, projectConfig.Name); err != nil { - log.Printf("Failed to update app: %v\n", err) - tx.Rollback() - return err - } - - if err := tx.Commit(); err != nil { - log.Printf("Failed to commit transaction: %v\n", err) return err } @@ -344,7 +169,7 @@ func (deployment *Deployment) Upgrade(ctx context.Context, projectConfig pkg.Pro return err } - tx, err = Flux.db.Begin() + tx, err := Flux.db.Begin() if err != nil { log.Printf("Failed to begin transaction: %v\n", err) return err @@ -354,7 +179,7 @@ func (deployment *Deployment) Upgrade(ctx context.Context, projectConfig pkg.Pro var oldContainers []*Container for _, container := range deployment.Containers { if existingContainers[string(container.ContainerID[:])] { - log.Printf("Deleting container from db: %s\n", container.ContainerID[0:12]) + log.Printf("Deleting container from db: %s\n", container.ContainerID[:12]) _, err = tx.Exec("DELETE FROM containers WHERE id = ?", container.ID) oldContainers = append(oldContainers, &container) @@ -393,16 +218,6 @@ func (deployment *Deployment) Upgrade(ctx context.Context, projectConfig pkg.Pro return nil } -func arrayContains(arr []string, str string) bool { - for _, a := range arr { - if a == str { - return true - } - } - - return false -} - func (d *Deployment) Remove(ctx context.Context) error { for _, container := range d.Containers { err := container.Remove(ctx) @@ -445,15 +260,6 @@ func (d *Deployment) Stop(ctx context.Context) error { return nil } -func (c *Container) GetStatus(ctx context.Context) (string, error) { - containerJSON, err := dockerClient.ContainerInspect(ctx, string(c.ContainerID[:])) - if err != nil { - return "", err - } - - return containerJSON.State.Status, nil -} - func (d *Deployment) Status(ctx context.Context) (string, error) { var status string if d == nil { @@ -467,7 +273,7 @@ func (d *Deployment) Status(ctx context.Context) (string, error) { } for _, container := range d.Containers { - containerStatus, err := container.GetStatus(ctx) + containerStatus, err := container.Status(ctx) if err != nil { log.Printf("Failed to get container status: %v\n", err) return "", err diff --git a/server/proxy.go b/server/proxy.go index b224469..2571f40 100644 --- a/server/proxy.go +++ b/server/proxy.go @@ -50,7 +50,7 @@ func NewDeploymentProxy(deployment *Deployment, head *Container) (*DeploymentPro } if containerJSON.NetworkSettings.IPAddress == "" { - return nil, fmt.Errorf("No IP address found for container %s", head.ContainerID[0:12]) + return nil, fmt.Errorf("No IP address found for container %s", head.ContainerID[:12]) } containerUrl, err := url.Parse(fmt.Sprintf("http://%s:%d", containerJSON.NetworkSettings.IPAddress, deployment.Port)) diff --git a/server/server.go b/server/server.go index 25bfd3d..84dbf92 100644 --- a/server/server.go +++ b/server/server.go @@ -14,7 +14,7 @@ import ( _ "embed" - "github.com/juls0730/fluxd/pkg" + "github.com/juls0730/flux/pkg" _ "github.com/mattn/go-sqlite3" )