almost there

This commit is contained in:
Zoe
2024-12-07 02:35:38 -06:00
parent f1ad13a216
commit d27cc71f1d
12 changed files with 707 additions and 586 deletions

View File

@@ -15,25 +15,30 @@ import (
"github.com/docker/docker/api/types/volume"
"github.com/docker/docker/client"
"github.com/joho/godotenv"
"github.com/juls0730/fluxd/models"
"github.com/juls0730/fluxd/pkg"
)
type ContainerManager struct {
dockerClient *client.Client
var dockerClient *client.Client
type Container struct {
ID int64 `json:"id"`
Head bool `json:"head"` // if the container is the head of the deployment
Deployment *Deployment
ContainerID [64]byte `json:"container_id"`
DeploymentID int64 `json:"deployment_id"`
}
func NewContainerManager() *ContainerManager {
dockerClient, err := client.NewClientWithOpts(client.FromEnv)
func init() {
log.Printf("Initializing Docker client...\n")
var err error
dockerClient, err = client.NewClientWithOpts(client.FromEnv)
if err != nil {
log.Fatalf("Failed to create Docker client: %v", err)
}
return &ContainerManager{
dockerClient: dockerClient,
}
}
func (cm *ContainerManager) CreateContainer(ctx context.Context, imageName, projectPath string, projectConfig models.ProjectConfig) (string, error) {
func CreateDockerContainer(ctx context.Context, imageName, projectPath string, projectConfig pkg.ProjectConfig) (string, error) {
log.Printf("Deploying container with image %s\n", imageName)
containerName := fmt.Sprintf("%s-%s", projectConfig.Name, time.Now().Format("20060102-150405"))
@@ -55,10 +60,10 @@ func (cm *ContainerManager) CreateContainer(ctx context.Context, imageName, proj
}
}
vol, err := cm.dockerClient.VolumeCreate(ctx, volume.CreateOptions{
vol, err := dockerClient.VolumeCreate(ctx, volume.CreateOptions{
Driver: "local",
DriverOpts: map[string]string{},
Name: fmt.Sprintf("%s-volume", projectConfig.Name),
Name: fmt.Sprintf("flux_%s-volume", projectConfig.Name),
})
if err != nil {
return "", fmt.Errorf("Failed to create volume: %v", err)
@@ -67,25 +72,15 @@ func (cm *ContainerManager) CreateContainer(ctx context.Context, imageName, proj
log.Printf("Volume %s created at %s\n", vol.Name, vol.Mountpoint)
log.Printf("Creating container %s...\n", containerName)
resp, err := cm.dockerClient.ContainerCreate(ctx, &container.Config{
resp, err := dockerClient.ContainerCreate(ctx, &container.Config{
Image: imageName,
Env: projectConfig.Environment,
// ExposedPorts: nat.PortSet{
// nat.Port(fmt.Sprintf("%d/tcp", projectConfig.Port)): {},
// },
Volumes: map[string]struct{}{
vol.Name: {},
},
},
&container.HostConfig{
// PortBindings: nat.PortMap{
// nat.Port(fmt.Sprintf("%d/tcp", projectConfig.Port)): []nat.PortBinding{
// {
// HostIP: "0.0.0.0",
// HostPort: strconv.Itoa(projectConfig.Port),
// },
// },
// },
NetworkMode: "bridge",
Mounts: []mount.Mount{
{
Type: mount.TypeVolume,
@@ -107,28 +102,37 @@ func (cm *ContainerManager) CreateContainer(ctx context.Context, imageName, proj
return resp.ID, nil
}
func (cm *ContainerManager) StartContainer(ctx context.Context, containerID string) error {
return cm.dockerClient.ContainerStart(ctx, containerID, container.StartOptions{})
func (c *Container) Start(ctx context.Context) error {
return dockerClient.ContainerStart(ctx, string(c.ContainerID[:]), container.StartOptions{})
}
func (cm *ContainerManager) StopContainer(ctx context.Context, containerID string) error {
return cm.dockerClient.ContainerStop(ctx, containerID, container.StopOptions{})
func (c *Container) Stop(ctx context.Context) error {
return dockerClient.ContainerStop(ctx, string(c.ContainerID[:]), container.StopOptions{})
}
func (c *Container) Remove(ctx context.Context) error {
return RemoveDockerContainer(ctx, string(c.ContainerID[:]))
}
func (c *Container) Wait(ctx context.Context, port uint16) error {
return WaitForDockerContainer(ctx, string(c.ContainerID[:]), port)
}
// RemoveContainer stops and removes a container, but be warned that this will not remove the container from the database
func (cm *ContainerManager) RemoveContainer(ctx context.Context, containerID string) error {
if err := cm.dockerClient.ContainerStop(ctx, containerID, container.StopOptions{}); err != nil {
func RemoveDockerContainer(ctx context.Context, containerID string) error {
if err := dockerClient.ContainerStop(ctx, containerID, container.StopOptions{}); err != nil {
return fmt.Errorf("Failed to stop existing container: %v", err)
}
if err := cm.dockerClient.ContainerRemove(ctx, containerID, container.RemoveOptions{}); err != nil {
if err := dockerClient.ContainerRemove(ctx, containerID, container.RemoveOptions{}); err != nil {
return fmt.Errorf("Failed to remove existing container: %v", err)
}
return nil
}
func (cm *ContainerManager) WaitForContainer(ctx context.Context, containerID string, containerPort int) error {
// scuffed af "health check" for docker containers
func WaitForDockerContainer(ctx context.Context, containerID string, containerPort uint16) error {
ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()
@@ -138,7 +142,7 @@ func (cm *ContainerManager) WaitForContainer(ctx context.Context, containerID st
return fmt.Errorf("container failed to become ready in time")
default:
containerJSON, err := cm.dockerClient.ContainerInspect(ctx, containerID)
containerJSON, err := dockerClient.ContainerInspect(ctx, containerID)
if err != nil {
return err
}
@@ -155,9 +159,9 @@ func (cm *ContainerManager) WaitForContainer(ctx context.Context, containerID st
}
}
func (cm *ContainerManager) GracefullyRemoveContainer(ctx context.Context, containerID string) error {
func GracefullyRemoveDockerContainer(ctx context.Context, containerID string) error {
timeout := 30
err := cm.dockerClient.ContainerStop(ctx, containerID, container.StopOptions{
err := dockerClient.ContainerStop(ctx, containerID, container.StopOptions{
Timeout: &timeout,
})
if err != nil {
@@ -170,15 +174,15 @@ func (cm *ContainerManager) GracefullyRemoveContainer(ctx context.Context, conta
for {
select {
case <-ctx.Done():
return cm.dockerClient.ContainerRemove(ctx, containerID, container.RemoveOptions{})
return dockerClient.ContainerRemove(ctx, containerID, container.RemoveOptions{})
default:
containerJSON, err := cm.dockerClient.ContainerInspect(ctx, containerID)
containerJSON, err := dockerClient.ContainerInspect(ctx, containerID)
if err != nil {
return err
}
if !containerJSON.State.Running {
return cm.dockerClient.ContainerRemove(ctx, containerID, container.RemoveOptions{})
return dockerClient.ContainerRemove(ctx, containerID, container.RemoveOptions{})
}
time.Sleep(time.Second)
@@ -186,26 +190,26 @@ func (cm *ContainerManager) GracefullyRemoveContainer(ctx context.Context, conta
}
}
func (cm *ContainerManager) RemoveVolume(ctx context.Context, volumeID string) error {
if err := cm.dockerClient.VolumeRemove(ctx, volumeID, true); err != nil {
func RemoveVolume(ctx context.Context, volumeID string) error {
if err := dockerClient.VolumeRemove(ctx, volumeID, true); err != nil {
return fmt.Errorf("Failed to remove existing volume: %v", err)
}
return nil
}
func (cm *ContainerManager) findExistingContainers(ctx context.Context, containerPrefix string) ([]string, error) {
containers, err := cm.dockerClient.ContainerList(ctx, container.ListOptions{
func findExistingDockerContainers(ctx context.Context, containerPrefix string) (map[string]bool, error) {
containers, err := dockerClient.ContainerList(ctx, container.ListOptions{
All: true,
})
if err != nil {
return nil, err
}
var existingContainers []string
var existingContainers map[string]bool = make(map[string]bool)
for _, container := range containers {
if strings.HasPrefix(container.Names[0], fmt.Sprintf("/%s-", containerPrefix)) {
existingContainers = append(existingContainers, container.ID)
existingContainers[container.ID] = true
}
}

View File

@@ -1,14 +1,21 @@
package server
import (
"database/sql"
"encoding/json"
"fmt"
"log"
"mime/multipart"
"net/http"
"os"
"os/exec"
"path/filepath"
"github.com/juls0730/fluxd/models"
"github.com/juls0730/fluxd/pkg"
)
var (
appInsertStmt *sql.Stmt
)
type DeployRequest struct {
@@ -17,7 +24,7 @@ type DeployRequest struct {
}
type DeployResponse struct {
App models.App `json:"app"`
App App `json:"app"`
}
func (s *FluxServer) DeployHandler(w http.ResponseWriter, r *http.Request) {
@@ -44,25 +51,15 @@ func (s *FluxServer) DeployHandler(w http.ResponseWriter, r *http.Request) {
}
defer deployRequest.Code.Close()
var projectConfig models.ProjectConfig
var projectConfig pkg.ProjectConfig
if err := json.NewDecoder(deployRequest.Config).Decode(&projectConfig); err != nil {
log.Printf("Failed to decode config: %v\n", err)
http.Error(w, "Invalid flux.json", http.StatusBadRequest)
return
}
if projectConfig.Name == "" {
http.Error(w, "No project name specified", http.StatusBadRequest)
return
}
if projectConfig.Url == "" {
http.Error(w, "No deployment url specified", http.StatusBadRequest)
return
}
if projectConfig.Port == 0 {
http.Error(w, "No port specified", http.StatusBadRequest)
if projectConfig.Name == "" || projectConfig.Url == "" || projectConfig.Port == 0 {
http.Error(w, "Invalid flux.json, a name, url, and port must be specified", http.StatusBadRequest)
return
}
@@ -86,7 +83,7 @@ func (s *FluxServer) DeployHandler(w http.ResponseWriter, r *http.Request) {
}
log.Printf("Building image for project %s...\n", projectConfig.Name)
imageName := fmt.Sprintf("%s-image", projectConfig.Name)
imageName := fmt.Sprintf("flux_%s-image", projectConfig.Name)
buildCmd := exec.Command("pack", "build", imageName, "--builder", s.config.Builder)
buildCmd.Dir = projectPath
err = buildCmd.Run()
@@ -96,59 +93,61 @@ func (s *FluxServer) DeployHandler(w http.ResponseWriter, r *http.Request) {
return
}
var app models.App
s.db.QueryRow("SELECT id, name, deployment_id FROM apps WHERE name = ?", projectConfig.Name).Scan(&app.ID, &app.Name, &app.DeploymentID)
app := Apps.GetApp(projectConfig.Name)
if app.ID == 0 {
configBytes, err := json.Marshal(projectConfig)
if err != nil {
log.Printf("Failed to marshal project config: %v\n", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
if app == nil {
app = &App{
Name: projectConfig.Name,
}
log.Printf("Creating deployment %s...\n", app.Name)
containerID, err := s.containerManager.CreateContainer(r.Context(), imageName, projectPath, projectConfig)
containerID, err := CreateDockerContainer(r.Context(), imageName, projectPath, projectConfig)
if err != nil {
log.Printf("Failed to create container: %v\n", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
deploymentID, err := s.CreateDeployment(r.Context(), projectConfig, containerID)
deployment, err := CreateDeployment(containerID, projectConfig.Port, projectConfig.Url, s.db)
app.Deployment = deployment
if err != nil {
log.Printf("Failed to create deployment: %v\n", 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
appResult, err := s.db.Exec("INSERT INTO apps (name, image, project_path, project_config, deployment_id) VALUES (?, ?, ?, ?, ?)", projectConfig.Name, imageName, projectPath, configBytes, deploymentID)
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
}
appID, err := appResult.LastInsertId()
if err != nil {
log.Printf("Failed to get app id: %v\n", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
s.db.QueryRow("SELECT id, name, deployment_id FROM apps WHERE id = ?", appID).Scan(&app.ID, &app.Name, &app.DeploymentID)
err = s.StartDeployment(r.Context(), app.DeploymentID)
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
}
Apps.AddApp(app.Name, app)
} else {
log.Printf("Upgrading deployment %s...\n", app.Name)
// if deploy is not started, start it
deploymentStatus, err := s.GetDeploymentStatus(r.Context(), app.DeploymentID)
if deploymentStatus != "started" || err != nil {
err = s.StartDeployment(r.Context(), app.DeploymentID)
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)
@@ -156,7 +155,7 @@ func (s *FluxServer) DeployHandler(w http.ResponseWriter, r *http.Request) {
}
}
err = s.UpgradeDeployment(r.Context(), app.DeploymentID, projectConfig, imageName, projectPath)
err = app.Deployment.Upgrade(r.Context(), projectConfig, imageName, projectPath, s)
if err != nil {
log.Printf("Failed to upgrade deployment: %v\n", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
@@ -167,26 +166,31 @@ func (s *FluxServer) DeployHandler(w http.ResponseWriter, r *http.Request) {
log.Printf("App %s deployed successfully!\n", app.Name)
json.NewEncoder(w).Encode(DeployResponse{
App: app,
App: *app,
})
}
func (s *FluxServer) StartDeployHandler(w http.ResponseWriter, r *http.Request) {
name := r.PathValue("name")
var app struct {
id int64
name string
deployment_id int64
}
s.db.QueryRow("SELECT id, name, deployment_id FROM apps WHERE name = ?", name).Scan(&app.id, &app.name, &app.deployment_id)
if app.id == 0 {
app := Apps.GetApp(name)
if app == nil {
http.Error(w, "App not found", http.StatusNotFound)
return
}
err := s.StartDeployment(r.Context(), app.deployment_id)
status, err := app.Deployment.Status(r.Context())
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if status == "running" {
http.Error(w, "App is already running", http.StatusBadRequest)
return
}
err = app.Deployment.Start(r.Context())
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
@@ -198,19 +202,24 @@ func (s *FluxServer) StartDeployHandler(w http.ResponseWriter, r *http.Request)
func (s *FluxServer) StopDeployHandler(w http.ResponseWriter, r *http.Request) {
name := r.PathValue("name")
var app struct {
id int64
name string
deployment_id int64
}
s.db.QueryRow("SELECT id, name, deployment_id FROM apps WHERE name = ?", name).Scan(&app.id, &app.name, &app.deployment_id)
if app.id == 0 {
app := Apps.GetApp(name)
if app == nil {
http.Error(w, "App not found", http.StatusNotFound)
return
}
err := s.StopDeployment(r.Context(), app.deployment_id)
status, err := app.Deployment.Status(r.Context())
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if status == "stopped" {
http.Error(w, "App is already stopped", http.StatusBadRequest)
return
}
err = app.Deployment.Stop(r.Context())
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
@@ -221,47 +230,32 @@ func (s *FluxServer) StopDeployHandler(w http.ResponseWriter, r *http.Request) {
func (s *FluxServer) DeleteDeployHandler(w http.ResponseWriter, r *http.Request) {
name := r.PathValue("name")
var err error
var app struct {
id int
name string
deployment_id int
}
s.db.QueryRow("SELECT id, name, deployment_id FROM apps WHERE name = ?", name).Scan(&app.id, &app.name, &app.deployment_id)
if app.id == 0 {
app := Apps.GetApp(name)
if app == nil {
http.Error(w, "App not found", http.StatusNotFound)
return
}
var containerId []string
rows, err := s.db.Query("SELECT container_id FROM containers WHERE deployment_id = ?", app.deployment_id)
if err != nil {
log.Printf("Failed to query containers: %v\n", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer rows.Close()
log.Printf("Deleting deployment %s...\n", name)
for rows.Next() {
var newContainerId string
if err := rows.Scan(&newContainerId); err != nil {
log.Printf("Failed to scan container id: %v\n", err)
for _, container := range app.Deployment.Containers {
err = RemoveDockerContainer(r.Context(), string(container.ContainerID[:]))
if err != nil {
log.Printf("Failed to remove container: %v\n", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
containerId = append(containerId, newContainerId)
}
log.Printf("Deleting deployment %s...\n", name)
for _, container := range containerId {
s.containerManager.RemoveContainer(r.Context(), container)
err = RemoveVolume(r.Context(), fmt.Sprintf("flux_%s-volume", name))
if err != nil {
log.Printf("Failed to remove volume: %v\n", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
s.containerManager.RemoveVolume(r.Context(), fmt.Sprintf("%s-volume", name))
tx, err := s.db.Begin()
if err != nil {
log.Printf("Failed to begin transaction: %v\n", err)
@@ -269,7 +263,7 @@ func (s *FluxServer) DeleteDeployHandler(w http.ResponseWriter, r *http.Request)
return
}
_, err = tx.Exec("DELETE FROM deployments WHERE id = ?", app.deployment_id)
_, err = tx.Exec("DELETE FROM deployments WHERE id = ?", app.DeploymentID)
if err != nil {
tx.Rollback()
log.Printf("Failed to delete deployment: %v\n", err)
@@ -277,7 +271,7 @@ func (s *FluxServer) DeleteDeployHandler(w http.ResponseWriter, r *http.Request)
return
}
_, err = tx.Exec("DELETE FROM containers WHERE deployment_id = ?", app.deployment_id)
_, err = tx.Exec("DELETE FROM containers WHERE deployment_id = ?", app.DeploymentID)
if err != nil {
tx.Rollback()
log.Printf("Failed to delete containers: %v\n", err)
@@ -285,7 +279,7 @@ func (s *FluxServer) DeleteDeployHandler(w http.ResponseWriter, r *http.Request)
return
}
_, err = tx.Exec("DELETE FROM apps WHERE id = ?", app.id)
_, err = tx.Exec("DELETE FROM apps WHERE id = ?", app.ID)
if err != nil {
tx.Rollback()
log.Printf("Failed to delete app: %v\n", err)
@@ -299,48 +293,109 @@ func (s *FluxServer) DeleteDeployHandler(w http.ResponseWriter, r *http.Request)
return
}
projectPath := filepath.Join(s.rootDir, "apps", name)
err = os.RemoveAll(projectPath)
if err != nil {
log.Printf("Failed to remove project directory: %v\n", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
Apps.DeleteApp(name)
w.WriteHeader(http.StatusOK)
}
func (s *FluxServer) DeleteAllDeploymentsHandler(w http.ResponseWriter, r *http.Request) {
var err error
for _, app := range Apps.GetAllApps() {
for _, container := range app.Deployment.Containers {
err = RemoveDockerContainer(r.Context(), string(container.ContainerID[:]))
if err != nil {
log.Printf("Failed to remove container: %v\n", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
err = RemoveVolume(r.Context(), fmt.Sprintf("flux_%s-volume", app.Name))
if err != nil {
log.Printf("Failed to remove volume: %v\n", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
tx, err := s.db.Begin()
if err != nil {
log.Printf("Failed to begin transaction: %v\n", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
_, err = tx.Exec("DELETE FROM deployments")
if err != nil {
tx.Rollback()
log.Printf("Failed to delete deployments: %v\n", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
_, err = tx.Exec("DELETE FROM containers")
if err != nil {
tx.Rollback()
log.Printf("Failed to delete containers: %v\n", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
_, err = tx.Exec("DELETE FROM apps")
if err != nil {
tx.Rollback()
log.Printf("Failed to delete apps: %v\n", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if err := tx.Commit(); err != nil {
log.Printf("Failed to commit transaction: %v\n", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if err := os.RemoveAll(filepath.Join(s.rootDir, "apps")); err != nil {
log.Printf("Failed to remove apps directory: %v\n", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if err := os.RemoveAll(filepath.Join(s.rootDir, "deployments")); err != nil {
log.Printf("Failed to remove deployments directory: %v\n", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
}
func (s *FluxServer) ListAppsHandler(w http.ResponseWriter, r *http.Request) {
var apps []models.App
rows, err := s.db.Query("SELECT * FROM apps")
if err != nil {
log.Printf("Failed to query apps: %v\n", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer rows.Close()
for rows.Next() {
var app models.App
var configBytes string
if err := rows.Scan(&app.ID, &app.Name, &app.Image, &app.ProjectPath, &configBytes, &app.DeploymentID, &app.CreatedAt); err != nil {
log.Printf("Failed to scan app: %v\n", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
err = json.Unmarshal([]byte(configBytes), &app.ProjectConfig)
if err != nil {
log.Printf("Failed to unmarshal project config: %v\n", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
apps = append(apps, app)
}
// for each app, get the deployment status
for i, app := range apps {
deploymentStatus, err := s.GetDeploymentStatus(r.Context(), app.DeploymentID)
var apps []*pkg.App
for _, app := range Apps.GetAllApps() {
var extApp pkg.App
deploymentStatus, err := app.Deployment.Status(r.Context())
if err != nil {
log.Printf("Failed to get deployment status: %v\n", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
apps[i].DeploymentStatus = deploymentStatus
extApp.ID = app.ID
extApp.Name = app.Name
extApp.DeploymentID = app.DeploymentID
extApp.DeploymentStatus = deploymentStatus
apps = append(apps, &extApp)
}
w.Header().Set("Content-Type", "application/json")

View File

@@ -2,98 +2,291 @@ package server
import (
"context"
"encoding/json"
"database/sql"
"fmt"
"log"
"sync"
"time"
"github.com/juls0730/fluxd/models"
"github.com/juls0730/fluxd/pkg"
)
// Creates a deployment and containers in the database
func (s *FluxServer) CreateDeployment(ctx context.Context, projectConfig models.ProjectConfig, containerID string) (int64, error) {
deploymentResult, err := s.db.Exec("INSERT INTO deployments (url) VALUES (?)", projectConfig.Url)
if err != nil {
log.Printf("Failed to insert deployment: %v\n", err)
return 0, err
}
var (
Apps *AppManager = new(AppManager)
deploymentInsertStmt *sql.Stmt
containerInsertStmt *sql.Stmt
)
deploymentID, err := deploymentResult.LastInsertId()
if err != nil {
log.Printf("Failed to get deployment id: %v\n", err)
return 0, err
}
_, err = s.db.Exec("INSERT INTO containers (container_id, deployment_id, head) VALUES (?, ?, ?)", containerID, deploymentID, true)
if err != nil {
log.Printf("Failed to get container id: %v\n", err)
return 0, err
}
return deploymentID, nil
type AppManager struct {
sync.Map
}
func (s *FluxServer) UpgradeDeployment(ctx context.Context, deploymentID int64, projectConfig models.ProjectConfig, imageName string, projectPath string) error {
configBytes, err := json.Marshal(projectConfig)
if err != nil {
log.Printf("Failed to marshal project config: %v\n", err)
return err
type App struct {
ID int64 `json:"id,omitempty"`
Deployment Deployment `json:"-"`
Name string `json:"name,omitempty"`
DeploymentID int64 `json:"deployment_id,omitempty"`
}
type Deployment struct {
ID int64 `json:"id"`
Containers []Container `json:"-"`
Proxy *DeploymentProxy `json:"-"`
URL string `json:"url"`
Port uint16 `json:"port"`
}
func (am *AppManager) GetApp(name string) *App {
app, exists := am.Load(name)
if !exists {
return nil
}
existingContainers, err := s.containerManager.findExistingContainers(ctx, projectConfig.Name)
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) {
am.Delete(name)
}
func (am *AppManager) Init() {
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
}
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)
}
deployment.Proxy = &DeploymentProxy{
deployment: &deployment,
currentHead: headContainer,
gracePeriod: time.Second * 30,
activeRequests: 0,
}
app.Deployment = deployment
Apps.AddApp(app.Name, &app)
}
}
// Creates a deployment and containers in the database
func CreateDeployment(containerID string, port uint16, appUrl string, db *sql.DB) (Deployment, error) {
var deployment Deployment
var err error
if deploymentInsertStmt == nil {
deploymentInsertStmt, err = db.Prepare("INSERT INTO deployments (url, port) VALUES ($1, $2) RETURNING id, url, port")
if err != nil {
log.Printf("Failed to prepare statement: %v\n", err)
return Deployment{}, err
}
}
err = deploymentInsertStmt.QueryRow(appUrl, port).Scan(&deployment.ID, &deployment.URL, &deployment.Port)
if err != nil {
log.Printf("Failed to insert deployment: %v\n", err)
return Deployment{}, err
}
var container Container
if containerInsertStmt == nil {
containerInsertStmt, err = db.Prepare("INSERT INTO containers (container_id, deployment_id, head) VALUES ($1, $2, $3) RETURNING id, container_id, deployment_id, head")
if err != nil {
log.Printf("Failed to prepare statement: %v\n", err)
return Deployment{}, err
}
}
var containerIDString string
err = containerInsertStmt.QueryRow(containerID, deployment.ID, true).Scan(&container.ID, &containerIDString, &container.DeploymentID, &container.Head)
if err != nil {
log.Printf("Failed to get container id: %v\n", err)
return Deployment{}, err
}
copy(container.ContainerID[:], containerIDString)
deployment.Proxy = &DeploymentProxy{
deployment: &deployment,
currentHead: &container,
gracePeriod: time.Second * 30,
activeRequests: 0,
}
container.Deployment = &deployment
deployment.Containers = append(deployment.Containers, container)
ReverseProxy.AddDeployment(&deployment)
return deployment, nil
}
func (deployment *Deployment) Upgrade(ctx context.Context, projectConfig pkg.ProjectConfig, imageName string, projectPath string, s *FluxServer) error {
existingContainers, err := findExistingDockerContainers(ctx, projectConfig.Name)
if err != nil {
return fmt.Errorf("Failed to find existing containers: %v", err)
}
fmt.Printf("There are %d existing containers\n", len(existingContainers))
// Deploy new container before deleting old one
containerID, err := s.containerManager.CreateContainer(ctx, imageName, projectPath, projectConfig)
containerID, err := CreateDockerContainer(ctx, imageName, projectPath, projectConfig)
if err != nil {
log.Printf("Failed to create container: %v\n", err)
return err
}
// calls AddContainer in proxy
err = s.containerManager.StartContainer(ctx, containerID)
var container Container
if containerInsertStmt == nil {
containerInsertStmt, err = DB.Prepare("INSERT INTO containers (container_id, deployment_id, head) VALUES ($1, $2, $3) RETURNING id, container_id, deployment_id, head")
if err != nil {
log.Printf("Failed to prepare statement: %v\n", err)
return err
}
}
var containerIDString string
err = containerInsertStmt.QueryRow(containerID, deployment.ID, true).Scan(&container.ID, &containerIDString, &container.DeploymentID, &container.Head)
if err != nil {
log.Printf("Failed to get container id: %v\n", err)
return err
}
container.Deployment = deployment
copy(container.ContainerID[:], containerIDString)
deployment.Containers = append(deployment.Containers, container)
log.Printf("Starting container %s...\n", containerID)
err = container.Start(ctx)
if err != nil {
log.Printf("Failed to start container: %v\n", err)
return err
}
if err := s.containerManager.WaitForContainer(ctx, containerID, projectConfig.Port); err != nil {
if err := container.Wait(ctx, projectConfig.Port); err != nil {
log.Printf("Failed to wait for container: %v\n", err)
return err
}
if _, err := s.db.Exec("INSERT INTO containers (container_id, deployment_id, head) VALUES (?, ?, ?)", containerID, deploymentID, true); err != nil {
log.Printf("Failed to insert container: %v\n", err)
tx, err := s.db.Begin()
if err != nil {
log.Printf("Failed to begin transaction: %v\n", err)
return err
}
// update app in the database
if _, err := s.db.Exec("UPDATE apps SET project_config = ?, deployment_id = ? WHERE name = ?", configBytes, deploymentID, projectConfig.Name); err != nil {
if _, err := tx.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
}
// TODO: swap containers if they are running and have the same image so that we can have a constant uptime
tx, err := s.db.Begin()
if err := tx.Commit(); err != nil {
log.Printf("Failed to commit transaction: %v\n", err)
return err
}
tx, err = s.db.Begin()
if err != nil {
log.Printf("Failed to begin transaction: %v\n", err)
return err
}
for _, existingContainer := range existingContainers {
log.Printf("Stopping existing container: %s\n", existingContainer[0:12])
tx.Exec("DELETE FROM containers WHERE container_id = ?", existingContainer)
err = s.containerManager.GracefullyRemoveContainer(ctx, existingContainer)
if err != nil {
tx.Rollback()
return err
}
// Create a new proxy that points to the new head, and replace the old one, but ensure that the old one is gracefully shutdown
oldProxy := deployment.Proxy
deployment.Proxy = &DeploymentProxy{
deployment: deployment,
currentHead: &container,
gracePeriod: time.Second * 30,
activeRequests: 0,
}
var containers []Container
var oldContainers []*Container
for _, container := range deployment.Containers {
if existingContainers[string(container.ContainerID[:])] {
log.Printf("Stopping existing container: %s\n", container.ContainerID[0:12])
_, err = tx.Exec("DELETE FROM containers WHERE container_id = ?", string(container.ContainerID[:]))
oldContainers = append(oldContainers, &container)
if err != nil {
tx.Rollback()
return err
}
continue
}
containers = append(containers, container)
}
if oldProxy != nil {
go oldProxy.GracefulShutdown(oldContainers)
}
deployment.Containers = containers
ReverseProxy.AddDeployment(deployment)
if err := tx.Commit(); err != nil {
log.Printf("Failed to commit transaction: %v\n", err)
return err
@@ -102,112 +295,42 @@ func (s *FluxServer) UpgradeDeployment(ctx context.Context, deploymentID int64,
return nil
}
func (s *FluxServer) StartDeployment(ctx context.Context, deploymentID int64) error {
var containerIds []string
rows, err := s.db.Query("SELECT container_id FROM containers WHERE deployment_id = ?", deploymentID)
if err != nil {
log.Printf("Failed to query containers: %v\n", err)
return err
}
defer rows.Close()
for rows.Next() {
var newContainerId string
if err := rows.Scan(&newContainerId); err != nil {
log.Printf("Failed to scan container id: %v\n", err)
return err
func arrayContains(arr []string, str string) bool {
for _, a := range arr {
if a == str {
return true
}
containerIds = append(containerIds, newContainerId)
}
var projectConfigStr []byte
s.db.QueryRow("SELECT project_config FROM apps WHERE deployment_id = ?", deploymentID).Scan(&projectConfigStr)
var projectConfig models.ProjectConfig
if err := json.Unmarshal(projectConfigStr, &projectConfig); err != nil {
return err
}
if projectConfig.Name == "" {
return fmt.Errorf("No project config found for deployment")
}
return false
}
for _, containerId := range containerIds {
err := s.containerManager.StartContainer(ctx, containerId)
s.Proxy.AddContainer(projectConfig, containerId)
func (d *Deployment) Start(ctx context.Context) error {
for _, container := range d.Containers {
err := container.Start(ctx)
if err != nil {
log.Printf("Failed to start container: %v\n", err)
return err
}
}
tx, err := s.db.Begin()
if err != nil {
log.Printf("Failed to begin transaction: %v\n", err)
return err
}
if err := tx.Commit(); err != nil {
log.Printf("Failed to commit transaction: %v\n", err)
return err
}
return nil
}
func (s *FluxServer) StopDeployment(ctx context.Context, deploymentID int64) error {
var containerIds []string
rows, err := s.db.Query("SELECT container_id FROM containers WHERE deployment_id = ?", deploymentID)
if err != nil {
log.Printf("Failed to query containers: %v\n", err)
return err
}
defer rows.Close()
for rows.Next() {
var newContainerId string
if err := rows.Scan(&newContainerId); err != nil {
log.Printf("Failed to scan container id: %v\n", err)
return err
}
containerIds = append(containerIds, newContainerId)
}
var projectConfigStr []byte
s.db.QueryRow("SELECT project_config FROM apps WHERE deployment_id = ?", deploymentID).Scan(&projectConfigStr)
var projectConfig models.ProjectConfig
if err := json.Unmarshal(projectConfigStr, &projectConfig); err != nil {
return err
}
if projectConfig.Name == "" {
return fmt.Errorf("No project config found for deployment")
}
for _, containerId := range containerIds {
err := s.containerManager.StopContainer(ctx, containerId)
s.Proxy.RemoveContainer(containerId)
func (d *Deployment) Stop(ctx context.Context) error {
for _, container := range d.Containers {
err := container.Stop(ctx)
if err != nil {
log.Printf("Failed to start container: %v\n", err)
return err
}
}
tx, err := s.db.Begin()
if err != nil {
log.Printf("Failed to begin transaction: %v\n", err)
return err
}
if err := tx.Commit(); err != nil {
log.Printf("Failed to commit transaction: %v\n", err)
return err
}
return nil
}
func (s *FluxServer) GetStatus(ctx context.Context, containerID string) (string, error) {
containerJSON, err := s.containerManager.dockerClient.ContainerInspect(ctx, containerID)
func (c *Container) GetStatus(ctx context.Context) (string, error) {
containerJSON, err := dockerClient.ContainerInspect(ctx, string(c.ContainerID[:]))
if err != nil {
return "", err
}
@@ -215,31 +338,20 @@ func (s *FluxServer) GetStatus(ctx context.Context, containerID string) (string,
return containerJSON.State.Status, nil
}
func (s *FluxServer) GetDeploymentStatus(ctx context.Context, deploymentID int64) (string, error) {
var deployment models.Deployments
s.db.QueryRow("SELECT id, url FROM deployments WHERE id = ?", deploymentID).Scan(&deployment.ID, &deployment.URL)
var containerIds []string
rows, err := s.db.Query("SELECT container_id FROM containers WHERE deployment_id = ?", deploymentID)
if err != nil {
log.Printf("Failed to query containers: %v\n", err)
return "", err
}
defer rows.Close()
for rows.Next() {
var newContainerId string
if err := rows.Scan(&newContainerId); err != nil {
log.Printf("Failed to scan container id: %v\n", err)
return "", err
}
containerIds = append(containerIds, newContainerId)
}
func (d *Deployment) Status(ctx context.Context) (string, error) {
var status string
for _, containerId := range containerIds {
containerStatus, err := s.GetStatus(ctx, containerId)
if d == nil {
fmt.Printf("Deployment is nil\n")
return "stopped", nil
}
if d.Containers == nil {
fmt.Printf("Containers are nil\n")
return "stopped", nil
}
for _, container := range d.Containers {
containerStatus, err := container.GetStatus(ctx)
if err != nil {
log.Printf("Failed to get container status: %v\n", err)
return "", err

View File

@@ -2,7 +2,6 @@ package server
import (
"context"
"database/sql"
"fmt"
"log"
"net/http"
@@ -12,209 +11,119 @@ import (
"sync"
"sync/atomic"
"time"
"github.com/juls0730/fluxd/models"
)
type ContainerProxy struct {
routes *RouteCache
db *sql.DB
cm *ContainerManager
activeConns int64
var ReverseProxy *Proxy
type Proxy struct {
deployments sync.Map
}
type RouteCache struct {
m sync.Map
func (p *Proxy) AddDeployment(deployment *Deployment) {
log.Printf("Adding deployment %s\n", deployment.URL)
p.deployments.Store(deployment.URL, deployment)
}
type containerRoute struct {
containerID string
port int
url string
proxy *httputil.ReverseProxy
isActive bool
}
func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
host := r.Host
func (rc *RouteCache) GetRoute(appUrl string) *containerRoute {
container, exists := rc.m.Load(appUrl)
if !exists {
return nil
}
return container.(*containerRoute)
}
func (rc *RouteCache) SetRoute(appUrl string, container *containerRoute) {
rc.m.Store(appUrl, container)
}
func (rc *RouteCache) DeleteRoute(appUrl string) {
rc.m.Delete(appUrl)
}
func (cp *ContainerProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Extract app name from host
appUrl := r.Host
container := cp.routes.GetRoute(appUrl)
if container == nil {
http.Error(w, "Container not found", http.StatusNotFound)
deployment, ok := p.deployments.Load(host)
if !ok {
http.Error(w, "Not found", http.StatusNotFound)
return
}
container.proxy.ServeHTTP(w, r)
}
atomic.AddInt64(&deployment.(*Deployment).Proxy.activeRequests, 1)
func (cp *ContainerProxy) AddContainer(projectConfig models.ProjectConfig, containerID string) error {
containerJSON, err := cp.cm.dockerClient.ContainerInspect(context.Background(), containerID)
if err != nil {
log.Printf("Failed to inspect container: %v\n", err)
return err
}
containerUrl, err := url.Parse(fmt.Sprintf("http://%s:%d", containerJSON.NetworkSettings.IPAddress, projectConfig.Port))
if err != nil {
log.Printf("Failed to parse URL: %v\n", err)
return err
}
proxy := cp.createProxy(containerUrl)
newRoute := &containerRoute{
url: projectConfig.Url,
proxy: proxy,
port: projectConfig.Port,
isActive: true,
}
cp.routes.SetRoute(projectConfig.Url, newRoute)
return nil
}
func (cp *ContainerProxy) createProxy(url *url.URL) *httputil.ReverseProxy {
proxy := httputil.NewSingleHostReverseProxy(url)
originalDirector := proxy.Director
proxy.Director = func(req *http.Request) {
atomic.AddInt64(&cp.activeConns, 1)
// Validate URL before directing
if url == nil {
log.Printf("URL is nil")
return
}
originalDirector(req)
}
proxy.ModifyResponse = func(resp *http.Response) error {
atomic.AddInt64(&cp.activeConns, -1)
return nil
}
// Handle errors
proxy.ErrorHandler = func(w http.ResponseWriter, r *http.Request, err error) {
atomic.AddInt64(&cp.activeConns, -1)
http.Error(w, "Service unavailable", http.StatusServiceUnavailable)
// Ensure request body is closed
if r.Body != nil {
r.Body.Close()
}
}
return proxy
}
func (cp *ContainerProxy) RemoveContainer(containerID string) error {
var deploymentID int64
if err := cp.db.QueryRow("SELECT deployment_id FROM containers WHERE id = ?", containerID).Scan(&deploymentID); err != nil {
return err
}
var url string
if err := cp.db.QueryRow("SELECT url FROM deployments WHERE id = ?", deploymentID).Scan(&url); err != nil {
return err
}
container := cp.routes.GetRoute(url)
container := deployment.(*Deployment).Proxy.currentHead
if container == nil {
return fmt.Errorf("container not found")
http.Error(w, "No active container found", http.StatusNotFound)
return
}
container.isActive = false
containerJSON, err := dockerClient.ContainerInspect(context.Background(), string(container.ContainerID[:]))
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
containerUrl, err := url.Parse(fmt.Sprintf("http://%s:%d", containerJSON.NetworkSettings.IPAddress, container.Deployment.Port))
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
proxy := &httputil.ReverseProxy{
Director: func(req *http.Request) {
req.URL = containerUrl
req.Host = containerUrl.Host
},
ModifyResponse: func(resp *http.Response) error {
atomic.AddInt64(&deployment.(*Deployment).Proxy.activeRequests, -1)
return nil
},
}
proxy.ServeHTTP(w, r)
}
type DeploymentProxy struct {
deployment *Deployment
currentHead *Container
gracePeriod time.Duration
activeRequests int64
}
func (dp *DeploymentProxy) GracefulShutdown(oldContainers []*Container) {
ctx, cancel := context.WithTimeout(context.Background(), dp.gracePeriod)
defer cancel()
// Create a channel to signal when wait group is done
for {
select {
case <-ctx.Done():
cp.routes.DeleteRoute(url)
return nil
break
default:
if atomic.LoadInt64(&cp.activeConns) == 0 {
cp.routes.DeleteRoute(url)
return nil
if atomic.LoadInt64(&dp.activeRequests) == 0 {
break
}
}
}
}
func (cp *ContainerProxy) ScanRoutes() {
rows, err := cp.db.Query("SELECT url, id FROM deployments")
if err != nil {
log.Printf("Failed to query deployments: %v\n", err)
return
}
defer rows.Close()
var containers []models.Containers
for rows.Next() {
var url string
var deploymentID int64
if err := rows.Scan(&url, &deploymentID); err != nil {
log.Printf("Failed to scan deployment: %v\n", err)
return
time.Sleep(time.Second)
}
rows, err := cp.db.Query("SELECT * FROM containers WHERE deployment_id = ?", deploymentID)
if atomic.LoadInt64(&dp.activeRequests) == 0 || ctx.Err() != nil {
break
}
}
for _, container := range oldContainers {
err := RemoveDockerContainer(context.Background(), string(container.ContainerID[:]))
if err != nil {
log.Printf("Failed to query containers: %v\n", err)
return
}
defer rows.Close()
for rows.Next() {
var container models.Containers
if err := rows.Scan(&container.ID, &container.ContainerID, &container.Head, &container.DeploymentID, &container.CreatedAt); err != nil {
log.Printf("Failed to scan container: %v\n", err)
return
}
fmt.Printf("Found container: %s\n", container.ContainerID)
containers = append(containers, container)
log.Printf("Failed to remove container: %v\n", err)
}
}
}
func (cp *ContainerProxy) Start() {
cp.ScanRoutes()
func InitProxy(apps *AppManager) {
ReverseProxy = &Proxy{}
apps.Range(func(key, value interface{}) bool {
app := value.(*App)
ReverseProxy.AddDeployment(&app.Deployment)
return true
})
}
func InitReverseProxy() {
InitProxy(Apps)
port := os.Getenv("FLUXD_PROXY_PORT")
if port == "" {
port = "7465"
}
server := &http.Server{
Addr: fmt.Sprintf(":%s", port),
Handler: cp,
}
go func() {
log.Printf("Proxy server starting on http://127.0.0.1:%s\n", port)
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
if err := http.ListenAndServe(fmt.Sprintf(":%s", port), ReverseProxy); err != nil && err != http.ErrServerClosed {
log.Fatalf("Proxy server error: %v", err)
}
}()

View File

@@ -1,11 +1,13 @@
CREATE TABLE IF NOT EXISTS deployments (
id INTEGER PRIMARY KEY AUTOINCREMENT,
url TEXT NOT NULL,
port INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS apps (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
image TEXT NOT NULL,
project_path TEXT NOT NULL,
project_config TEXT NOT NULL,
deployment_id INTEGER,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY(deployment_id) REFERENCES deployments(id)
);
@@ -14,12 +16,5 @@ CREATE TABLE IF NOT EXISTS containers (
container_id TEXT NOT NULL,
head BOOLEAN NOT NULL,
deployment_id INTEGER NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY(deployment_id) REFERENCES deployments(id)
);
CREATE TABLE IF NOT EXISTS deployments (
id INTEGER PRIMARY KEY AUTOINCREMENT,
url TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

View File

@@ -4,48 +4,38 @@ import (
"archive/tar"
"compress/gzip"
"database/sql"
"embed"
"encoding/json"
"io"
"log"
"os"
"path/filepath"
"github.com/juls0730/fluxd/models"
_ "embed"
"github.com/juls0730/fluxd/pkg"
_ "github.com/mattn/go-sqlite3"
)
//go:embed schema.sql
var schema embed.FS
var DefaultConfig = FluxServerConfig{
Builder: "paketobuildpacks/builder-jammy-tiny",
}
var (
//go:embed schema.sql
schemaBytes []byte
DefaultConfig = FluxServerConfig{
Builder: "paketobuildpacks/builder-jammy-tiny",
}
DB *sql.DB
)
type FluxServerConfig struct {
Builder string `json:"builder"`
}
type FluxServer struct {
containerManager *ContainerManager
config FluxServerConfig
db *sql.DB
Proxy *ContainerProxy
rootDir string
config FluxServerConfig
db *sql.DB
rootDir string
}
// var rootDir string
// func init() {
// rootDir = os.Getenv("FLUXD_ROOT_DIR")
// if rootDir == "" {
// rootDir = "/var/fluxd"
// }
// }
func NewServer() *FluxServer {
containerManager := NewContainerManager()
var serverConfig FluxServerConfig
rootDir := os.Getenv("FLUXD_ROOT_DIR")
@@ -84,36 +74,26 @@ func NewServer() *FluxServer {
log.Fatalf("Failed to create apps directory: %v\n", err)
}
db, err := sql.Open("sqlite3", filepath.Join(rootDir, "fluxd.db"))
DB, err = sql.Open("sqlite3", filepath.Join(rootDir, "fluxd.db"))
if err != nil {
log.Fatalf("Failed to open database: %v\n", err)
}
// create database schema
schemaBytes, err := schema.ReadFile("schema.sql")
if err != nil {
log.Fatalf("Failed to read schema file: %v\n", err)
}
_, err = db.Exec(string(schemaBytes))
_, err = DB.Exec(string(schemaBytes))
if err != nil {
log.Fatalf("Failed to create database schema: %v\n", err)
}
Apps.Init()
return &FluxServer{
containerManager: containerManager,
config: serverConfig,
db: db,
Proxy: &ContainerProxy{
routes: &RouteCache{},
db: db,
cm: containerManager,
},
config: serverConfig,
db: DB,
rootDir: rootDir,
}
}
func (s *FluxServer) UploadAppCode(code io.Reader, projectConfig models.ProjectConfig) (string, error) {
func (s *FluxServer) UploadAppCode(code io.Reader, projectConfig pkg.ProjectConfig) (string, error) {
projectPath := filepath.Join(s.rootDir, "apps", projectConfig.Name)
if err := os.MkdirAll(projectPath, 0755); err != nil {
log.Printf("Failed to create project directory: %v\n", err)