almost there
This commit is contained in:
@@ -11,13 +11,15 @@ import (
|
|||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"os/signal"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/briandowns/spinner"
|
"github.com/briandowns/spinner"
|
||||||
"github.com/juls0730/fluxd/models"
|
"github.com/juls0730/fluxd/pkg"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed config.json
|
//go:embed config.json
|
||||||
@@ -98,7 +100,7 @@ func getProjectName() string {
|
|||||||
}
|
}
|
||||||
defer fluxConfigFile.Close()
|
defer fluxConfigFile.Close()
|
||||||
|
|
||||||
var config models.ProjectConfig
|
var config pkg.ProjectConfig
|
||||||
if err := json.NewDecoder(fluxConfigFile).Decode(&config); err != nil {
|
if err := json.NewDecoder(fluxConfigFile).Decode(&config); err != nil {
|
||||||
fmt.Printf("Failed to decode flux.json: %v\n", err)
|
fmt.Printf("Failed to decode flux.json: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@@ -113,11 +115,23 @@ func getProjectName() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
loadingSpinner := spinner.New(spinner.CharSets[14], 100*time.Millisecond)
|
||||||
if len(os.Args) < 2 {
|
if len(os.Args) < 2 {
|
||||||
fmt.Println("Usage: flux <command>")
|
fmt.Println("Usage: flux <command>")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
signalChannel := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(signalChannel, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
go func() {
|
||||||
|
<-signalChannel
|
||||||
|
if loadingSpinner.Active() {
|
||||||
|
loadingSpinner.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Exit(0)
|
||||||
|
}()
|
||||||
|
|
||||||
command := os.Args[1]
|
command := os.Args[1]
|
||||||
|
|
||||||
if _, err := os.Stat(filepath.Join(configPath, "config.json")); err != nil {
|
if _, err := os.Stat(filepath.Join(configPath, "config.json")); err != nil {
|
||||||
@@ -151,7 +165,6 @@ func main() {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
loadingSpinner := spinner.New(spinner.CharSets[14], 100*time.Millisecond)
|
|
||||||
loadingSpinner.Suffix = " Deploying"
|
loadingSpinner.Suffix = " Deploying"
|
||||||
loadingSpinner.Start()
|
loadingSpinner.Start()
|
||||||
|
|
||||||
@@ -293,10 +306,63 @@ func main() {
|
|||||||
|
|
||||||
fmt.Printf("Successfully started %s\n", projectName)
|
fmt.Printf("Successfully started %s\n", projectName)
|
||||||
case "delete":
|
case "delete":
|
||||||
|
if len(os.Args) == 3 {
|
||||||
|
if os.Args[2] == "all" {
|
||||||
|
var response string
|
||||||
|
fmt.Print("Are you sure you want to delete all projects? this will delete all volumes and containers associated and cannot be undone. \n[y/N] ")
|
||||||
|
fmt.Scanln(&response)
|
||||||
|
|
||||||
|
if strings.ToLower(response) != "y" {
|
||||||
|
fmt.Println("Aborting...")
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
response = ""
|
||||||
|
|
||||||
|
fmt.Printf("Are you really sure you want to delete all projects? \n[y/N] ")
|
||||||
|
fmt.Scanln(&response)
|
||||||
|
|
||||||
|
if strings.ToLower(response) != "y" {
|
||||||
|
fmt.Println("Aborting...")
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest("DELETE", config.DeamonURL+"/deployments", nil)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Failed to delete deployments: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
resp, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Failed to delete deployments: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
responseBody, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("error reading response body: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(responseBody) > 0 && responseBody[len(responseBody)-1] == '\n' {
|
||||||
|
responseBody = responseBody[:len(responseBody)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Delete failed: %s\n", responseBody)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Successfully deleted all projects\n")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
projectName := getProjectName()
|
projectName := getProjectName()
|
||||||
|
|
||||||
// ask for confirmation
|
// ask for confirmation
|
||||||
fmt.Printf("Are you sure you want to delete %s? this will delete all volumes and containers associated with the deployment, and cannot be undone. \n[y/N]", projectName)
|
fmt.Printf("Are you sure you want to delete %s? this will delete all volumes and containers associated with the deployment, and cannot be undone. \n[y/N] ", projectName)
|
||||||
var response string
|
var response string
|
||||||
fmt.Scanln(&response)
|
fmt.Scanln(&response)
|
||||||
|
|
||||||
@@ -340,7 +406,22 @@ func main() {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
var apps []models.App
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
responseBody, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("error reading response body: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(responseBody) > 0 && responseBody[len(responseBody)-1] == '\n' {
|
||||||
|
responseBody = responseBody[:len(responseBody)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("List failed: %s\n", responseBody)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
var apps []pkg.App
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&apps); err != nil {
|
if err := json.NewDecoder(resp.Body).Decode(&apps); err != nil {
|
||||||
fmt.Printf("Failed to decode apps: %v\n", err)
|
fmt.Printf("Failed to decode apps: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@@ -355,7 +436,7 @@ func main() {
|
|||||||
fmt.Printf("%s (%s)\n", app.Name, app.DeploymentStatus)
|
fmt.Printf("%s (%s)\n", app.Name, app.DeploymentStatus)
|
||||||
}
|
}
|
||||||
case "init":
|
case "init":
|
||||||
var projectConfig models.ProjectConfig
|
var projectConfig pkg.ProjectConfig
|
||||||
|
|
||||||
var response string
|
var response string
|
||||||
if len(os.Args) > 2 {
|
if len(os.Args) > 2 {
|
||||||
@@ -379,7 +460,8 @@ func main() {
|
|||||||
|
|
||||||
fmt.Println("What port does your project listen to?")
|
fmt.Println("What port does your project listen to?")
|
||||||
fmt.Scanln(&response)
|
fmt.Scanln(&response)
|
||||||
projectConfig.Port, err = strconv.Atoi(response)
|
port, err := strconv.ParseUint(response, 10, 16)
|
||||||
|
projectConfig.Port = uint16(port)
|
||||||
if err != nil || projectConfig.Port < 1 || projectConfig.Port > 65535 {
|
if err != nil || projectConfig.Port < 1 || projectConfig.Port > 65535 {
|
||||||
fmt.Println("That doesnt look like a valid port", err)
|
fmt.Println("That doesnt look like a valid port", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
|||||||
@@ -9,11 +9,13 @@ import (
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
fluxServer := server.NewServer()
|
fluxServer := server.NewServer()
|
||||||
|
server.InitReverseProxy()
|
||||||
|
|
||||||
go fluxServer.Proxy.Start()
|
// go fluxServer.Proxy.Start()
|
||||||
|
|
||||||
http.HandleFunc("POST /deploy", fluxServer.DeployHandler)
|
http.HandleFunc("POST /deploy", fluxServer.DeployHandler)
|
||||||
http.HandleFunc("DELETE /deploy/{name}", fluxServer.DeleteDeployHandler)
|
http.HandleFunc("DELETE /deployments", fluxServer.DeleteAllDeploymentsHandler)
|
||||||
|
http.HandleFunc("DELETE /deployments/{name}", fluxServer.DeleteDeployHandler)
|
||||||
http.HandleFunc("POST /start/{name}", fluxServer.StartDeployHandler)
|
http.HandleFunc("POST /start/{name}", fluxServer.StartDeployHandler)
|
||||||
http.HandleFunc("POST /stop/{name}", fluxServer.StopDeployHandler)
|
http.HandleFunc("POST /stop/{name}", fluxServer.StopDeployHandler)
|
||||||
http.HandleFunc("GET /apps", fluxServer.ListAppsHandler)
|
http.HandleFunc("GET /apps", fluxServer.ListAppsHandler)
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
package models
|
|
||||||
|
|
||||||
type ProjectConfig struct {
|
|
||||||
Name string `json:"name,omitempty"`
|
|
||||||
Url string `json:"url,omitempty"`
|
|
||||||
Port int `json:"port,omitempty"`
|
|
||||||
EnvFile string `json:"env_file,omitempty"`
|
|
||||||
Environment []string `json:"environment,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type App struct {
|
|
||||||
ID int64 `json:"id,omitempty"`
|
|
||||||
Name string `json:"name,omitempty"`
|
|
||||||
Image string `json:"image,omitempty"`
|
|
||||||
ProjectPath string `json:"project_path,omitempty"`
|
|
||||||
ProjectConfig ProjectConfig `json:"project_config,omitempty"`
|
|
||||||
DeploymentID int64 `json:"deployment_id,omitempty"`
|
|
||||||
CreatedAt string `json:"created_at,omitempty"`
|
|
||||||
DeploymentStatus string `json:"deployment_status,omitempty"`
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
package models
|
|
||||||
|
|
||||||
type Containers struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
Head bool `json:"head"` // if the container is the head of the deployment
|
|
||||||
ContainerID string `json:"container_id"`
|
|
||||||
DeploymentID int64 `json:"deployment_id"`
|
|
||||||
CreatedAt string `json:"created_at"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Deployments struct {
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
URL string `json:"url"`
|
|
||||||
CreatedAt string `json:"created_at"`
|
|
||||||
}
|
|
||||||
9
pkg/config.go
Normal file
9
pkg/config.go
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package pkg
|
||||||
|
|
||||||
|
type ProjectConfig struct {
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Url string `json:"url,omitempty"`
|
||||||
|
Port uint16 `json:"port,omitempty"`
|
||||||
|
EnvFile string `json:"env_file,omitempty"`
|
||||||
|
Environment []string `json:"environment,omitempty"`
|
||||||
|
}
|
||||||
8
pkg/responses.go
Normal file
8
pkg/responses.go
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
package pkg
|
||||||
|
|
||||||
|
type App struct {
|
||||||
|
ID int64 `json:"id,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
DeploymentID int64 `json:"deployment_id,omitempty"`
|
||||||
|
DeploymentStatus string `json:"deployment_status,omitempty"`
|
||||||
|
}
|
||||||
@@ -15,25 +15,30 @@ import (
|
|||||||
"github.com/docker/docker/api/types/volume"
|
"github.com/docker/docker/api/types/volume"
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
"github.com/joho/godotenv"
|
"github.com/joho/godotenv"
|
||||||
"github.com/juls0730/fluxd/models"
|
"github.com/juls0730/fluxd/pkg"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ContainerManager struct {
|
var dockerClient *client.Client
|
||||||
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 {
|
func init() {
|
||||||
dockerClient, err := client.NewClientWithOpts(client.FromEnv)
|
log.Printf("Initializing Docker client...\n")
|
||||||
|
|
||||||
|
var err error
|
||||||
|
dockerClient, err = client.NewClientWithOpts(client.FromEnv)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to create Docker client: %v", err)
|
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)
|
log.Printf("Deploying container with image %s\n", imageName)
|
||||||
|
|
||||||
containerName := fmt.Sprintf("%s-%s", projectConfig.Name, time.Now().Format("20060102-150405"))
|
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",
|
Driver: "local",
|
||||||
DriverOpts: map[string]string{},
|
DriverOpts: map[string]string{},
|
||||||
Name: fmt.Sprintf("%s-volume", projectConfig.Name),
|
Name: fmt.Sprintf("flux_%s-volume", projectConfig.Name),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("Failed to create volume: %v", err)
|
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("Volume %s created at %s\n", vol.Name, vol.Mountpoint)
|
||||||
|
|
||||||
log.Printf("Creating container %s...\n", containerName)
|
log.Printf("Creating container %s...\n", containerName)
|
||||||
resp, err := cm.dockerClient.ContainerCreate(ctx, &container.Config{
|
resp, err := dockerClient.ContainerCreate(ctx, &container.Config{
|
||||||
Image: imageName,
|
Image: imageName,
|
||||||
Env: projectConfig.Environment,
|
Env: projectConfig.Environment,
|
||||||
// ExposedPorts: nat.PortSet{
|
|
||||||
// nat.Port(fmt.Sprintf("%d/tcp", projectConfig.Port)): {},
|
|
||||||
// },
|
|
||||||
Volumes: map[string]struct{}{
|
Volumes: map[string]struct{}{
|
||||||
vol.Name: {},
|
vol.Name: {},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
&container.HostConfig{
|
&container.HostConfig{
|
||||||
// PortBindings: nat.PortMap{
|
NetworkMode: "bridge",
|
||||||
// nat.Port(fmt.Sprintf("%d/tcp", projectConfig.Port)): []nat.PortBinding{
|
|
||||||
// {
|
|
||||||
// HostIP: "0.0.0.0",
|
|
||||||
// HostPort: strconv.Itoa(projectConfig.Port),
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
Mounts: []mount.Mount{
|
Mounts: []mount.Mount{
|
||||||
{
|
{
|
||||||
Type: mount.TypeVolume,
|
Type: mount.TypeVolume,
|
||||||
@@ -107,28 +102,37 @@ func (cm *ContainerManager) CreateContainer(ctx context.Context, imageName, proj
|
|||||||
return resp.ID, nil
|
return resp.ID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cm *ContainerManager) StartContainer(ctx context.Context, containerID string) error {
|
func (c *Container) Start(ctx context.Context) error {
|
||||||
return cm.dockerClient.ContainerStart(ctx, containerID, container.StartOptions{})
|
return dockerClient.ContainerStart(ctx, string(c.ContainerID[:]), container.StartOptions{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cm *ContainerManager) StopContainer(ctx context.Context, containerID string) error {
|
func (c *Container) Stop(ctx context.Context) error {
|
||||||
return cm.dockerClient.ContainerStop(ctx, containerID, container.StopOptions{})
|
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
|
// 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 {
|
func RemoveDockerContainer(ctx context.Context, containerID string) error {
|
||||||
if err := cm.dockerClient.ContainerStop(ctx, containerID, container.StopOptions{}); err != nil {
|
if err := dockerClient.ContainerStop(ctx, containerID, container.StopOptions{}); err != nil {
|
||||||
return fmt.Errorf("Failed to stop existing container: %v", err)
|
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 fmt.Errorf("Failed to remove existing container: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
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)
|
ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
|
||||||
defer cancel()
|
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")
|
return fmt.Errorf("container failed to become ready in time")
|
||||||
|
|
||||||
default:
|
default:
|
||||||
containerJSON, err := cm.dockerClient.ContainerInspect(ctx, containerID)
|
containerJSON, err := dockerClient.ContainerInspect(ctx, containerID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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
|
timeout := 30
|
||||||
err := cm.dockerClient.ContainerStop(ctx, containerID, container.StopOptions{
|
err := dockerClient.ContainerStop(ctx, containerID, container.StopOptions{
|
||||||
Timeout: &timeout,
|
Timeout: &timeout,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -170,15 +174,15 @@ func (cm *ContainerManager) GracefullyRemoveContainer(ctx context.Context, conta
|
|||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return cm.dockerClient.ContainerRemove(ctx, containerID, container.RemoveOptions{})
|
return dockerClient.ContainerRemove(ctx, containerID, container.RemoveOptions{})
|
||||||
default:
|
default:
|
||||||
containerJSON, err := cm.dockerClient.ContainerInspect(ctx, containerID)
|
containerJSON, err := dockerClient.ContainerInspect(ctx, containerID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !containerJSON.State.Running {
|
if !containerJSON.State.Running {
|
||||||
return cm.dockerClient.ContainerRemove(ctx, containerID, container.RemoveOptions{})
|
return dockerClient.ContainerRemove(ctx, containerID, container.RemoveOptions{})
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(time.Second)
|
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 {
|
func RemoveVolume(ctx context.Context, volumeID string) error {
|
||||||
if err := cm.dockerClient.VolumeRemove(ctx, volumeID, true); err != nil {
|
if err := dockerClient.VolumeRemove(ctx, volumeID, true); err != nil {
|
||||||
return fmt.Errorf("Failed to remove existing volume: %v", err)
|
return fmt.Errorf("Failed to remove existing volume: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cm *ContainerManager) findExistingContainers(ctx context.Context, containerPrefix string) ([]string, error) {
|
func findExistingDockerContainers(ctx context.Context, containerPrefix string) (map[string]bool, error) {
|
||||||
containers, err := cm.dockerClient.ContainerList(ctx, container.ListOptions{
|
containers, err := dockerClient.ContainerList(ctx, container.ListOptions{
|
||||||
All: true,
|
All: true,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var existingContainers []string
|
var existingContainers map[string]bool = make(map[string]bool)
|
||||||
for _, container := range containers {
|
for _, container := range containers {
|
||||||
if strings.HasPrefix(container.Names[0], fmt.Sprintf("/%s-", containerPrefix)) {
|
if strings.HasPrefix(container.Names[0], fmt.Sprintf("/%s-", containerPrefix)) {
|
||||||
existingContainers = append(existingContainers, container.ID)
|
existingContainers[container.ID] = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
299
server/deploy.go
299
server/deploy.go
@@ -1,14 +1,21 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/juls0730/fluxd/models"
|
"github.com/juls0730/fluxd/pkg"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
appInsertStmt *sql.Stmt
|
||||||
)
|
)
|
||||||
|
|
||||||
type DeployRequest struct {
|
type DeployRequest struct {
|
||||||
@@ -17,7 +24,7 @@ type DeployRequest struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type DeployResponse struct {
|
type DeployResponse struct {
|
||||||
App models.App `json:"app"`
|
App App `json:"app"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *FluxServer) DeployHandler(w http.ResponseWriter, r *http.Request) {
|
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()
|
defer deployRequest.Code.Close()
|
||||||
|
|
||||||
var projectConfig models.ProjectConfig
|
var projectConfig pkg.ProjectConfig
|
||||||
if err := json.NewDecoder(deployRequest.Config).Decode(&projectConfig); err != nil {
|
if err := json.NewDecoder(deployRequest.Config).Decode(&projectConfig); err != nil {
|
||||||
log.Printf("Failed to decode config: %v\n", err)
|
log.Printf("Failed to decode config: %v\n", err)
|
||||||
http.Error(w, "Invalid flux.json", http.StatusBadRequest)
|
http.Error(w, "Invalid flux.json", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if projectConfig.Name == "" {
|
if projectConfig.Name == "" || projectConfig.Url == "" || projectConfig.Port == 0 {
|
||||||
http.Error(w, "No project name specified", http.StatusBadRequest)
|
http.Error(w, "Invalid flux.json, a name, url, and port must be 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)
|
|
||||||
return
|
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)
|
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 := exec.Command("pack", "build", imageName, "--builder", s.config.Builder)
|
||||||
buildCmd.Dir = projectPath
|
buildCmd.Dir = projectPath
|
||||||
err = buildCmd.Run()
|
err = buildCmd.Run()
|
||||||
@@ -96,59 +93,61 @@ func (s *FluxServer) DeployHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var app models.App
|
app := Apps.GetApp(projectConfig.Name)
|
||||||
s.db.QueryRow("SELECT id, name, deployment_id FROM apps WHERE name = ?", projectConfig.Name).Scan(&app.ID, &app.Name, &app.DeploymentID)
|
|
||||||
|
|
||||||
if app.ID == 0 {
|
if app == nil {
|
||||||
configBytes, err := json.Marshal(projectConfig)
|
app = &App{
|
||||||
if err != nil {
|
Name: projectConfig.Name,
|
||||||
log.Printf("Failed to marshal project config: %v\n", err)
|
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
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 {
|
if err != nil {
|
||||||
log.Printf("Failed to create container: %v\n", err)
|
log.Printf("Failed to create container: %v\n", err)
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
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 {
|
if err != nil {
|
||||||
log.Printf("Failed to create deployment: %v\n", err)
|
log.Printf("Failed to create deployment: %v\n", err)
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
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
|
// 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 {
|
if err != nil {
|
||||||
log.Printf("Failed to insert app: %v\n", err)
|
log.Printf("Failed to insert app: %v\n", err)
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
appID, err := appResult.LastInsertId()
|
err = deployment.Start(r.Context())
|
||||||
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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed to start deployment: %v\n", err)
|
log.Printf("Failed to start deployment: %v\n", err)
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Apps.AddApp(app.Name, app)
|
||||||
} else {
|
} else {
|
||||||
|
log.Printf("Upgrading deployment %s...\n", app.Name)
|
||||||
|
|
||||||
// if deploy is not started, start it
|
// if deploy is not started, start it
|
||||||
deploymentStatus, err := s.GetDeploymentStatus(r.Context(), app.DeploymentID)
|
deploymentStatus, err := app.Deployment.Status(r.Context())
|
||||||
if deploymentStatus != "started" || err != nil {
|
if deploymentStatus != "running" || err != nil {
|
||||||
err = s.StartDeployment(r.Context(), app.DeploymentID)
|
err = app.Deployment.Start(r.Context())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed to start deployment: %v\n", err)
|
log.Printf("Failed to start deployment: %v\n", err)
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
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 {
|
if err != nil {
|
||||||
log.Printf("Failed to upgrade deployment: %v\n", err)
|
log.Printf("Failed to upgrade deployment: %v\n", err)
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
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)
|
log.Printf("App %s deployed successfully!\n", app.Name)
|
||||||
|
|
||||||
json.NewEncoder(w).Encode(DeployResponse{
|
json.NewEncoder(w).Encode(DeployResponse{
|
||||||
App: app,
|
App: *app,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *FluxServer) StartDeployHandler(w http.ResponseWriter, r *http.Request) {
|
func (s *FluxServer) StartDeployHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
name := r.PathValue("name")
|
name := r.PathValue("name")
|
||||||
|
|
||||||
var app struct {
|
app := Apps.GetApp(name)
|
||||||
id int64
|
if app == nil {
|
||||||
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 {
|
|
||||||
http.Error(w, "App not found", http.StatusNotFound)
|
http.Error(w, "App not found", http.StatusNotFound)
|
||||||
return
|
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 {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
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) {
|
func (s *FluxServer) StopDeployHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
name := r.PathValue("name")
|
name := r.PathValue("name")
|
||||||
|
|
||||||
var app struct {
|
app := Apps.GetApp(name)
|
||||||
id int64
|
if app == nil {
|
||||||
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 {
|
|
||||||
http.Error(w, "App not found", http.StatusNotFound)
|
http.Error(w, "App not found", http.StatusNotFound)
|
||||||
return
|
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 {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
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) {
|
func (s *FluxServer) DeleteDeployHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
name := r.PathValue("name")
|
name := r.PathValue("name")
|
||||||
|
var err error
|
||||||
|
|
||||||
var app struct {
|
app := Apps.GetApp(name)
|
||||||
id int
|
if app == nil {
|
||||||
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 {
|
|
||||||
http.Error(w, "App not found", http.StatusNotFound)
|
http.Error(w, "App not found", http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var containerId []string
|
log.Printf("Deleting deployment %s...\n", name)
|
||||||
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()
|
|
||||||
|
|
||||||
for rows.Next() {
|
for _, container := range app.Deployment.Containers {
|
||||||
var newContainerId string
|
err = RemoveDockerContainer(r.Context(), string(container.ContainerID[:]))
|
||||||
if err := rows.Scan(&newContainerId); err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed to scan container id: %v\n", err)
|
log.Printf("Failed to remove container: %v\n", err)
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
containerId = append(containerId, newContainerId)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Deleting deployment %s...\n", name)
|
err = RemoveVolume(r.Context(), fmt.Sprintf("flux_%s-volume", name))
|
||||||
|
if err != nil {
|
||||||
for _, container := range containerId {
|
log.Printf("Failed to remove volume: %v\n", err)
|
||||||
s.containerManager.RemoveContainer(r.Context(), container)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
s.containerManager.RemoveVolume(r.Context(), fmt.Sprintf("%s-volume", name))
|
|
||||||
|
|
||||||
tx, err := s.db.Begin()
|
tx, err := s.db.Begin()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed to begin transaction: %v\n", err)
|
log.Printf("Failed to begin transaction: %v\n", err)
|
||||||
@@ -269,7 +263,7 @@ func (s *FluxServer) DeleteDeployHandler(w http.ResponseWriter, r *http.Request)
|
|||||||
return
|
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 {
|
if err != nil {
|
||||||
tx.Rollback()
|
tx.Rollback()
|
||||||
log.Printf("Failed to delete deployment: %v\n", err)
|
log.Printf("Failed to delete deployment: %v\n", err)
|
||||||
@@ -277,7 +271,7 @@ func (s *FluxServer) DeleteDeployHandler(w http.ResponseWriter, r *http.Request)
|
|||||||
return
|
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 {
|
if err != nil {
|
||||||
tx.Rollback()
|
tx.Rollback()
|
||||||
log.Printf("Failed to delete containers: %v\n", err)
|
log.Printf("Failed to delete containers: %v\n", err)
|
||||||
@@ -285,7 +279,7 @@ func (s *FluxServer) DeleteDeployHandler(w http.ResponseWriter, r *http.Request)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = tx.Exec("DELETE FROM apps WHERE id = ?", app.id)
|
_, err = tx.Exec("DELETE FROM apps WHERE id = ?", app.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tx.Rollback()
|
tx.Rollback()
|
||||||
log.Printf("Failed to delete app: %v\n", err)
|
log.Printf("Failed to delete app: %v\n", err)
|
||||||
@@ -299,48 +293,109 @@ func (s *FluxServer) DeleteDeployHandler(w http.ResponseWriter, r *http.Request)
|
|||||||
return
|
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)
|
w.WriteHeader(http.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *FluxServer) ListAppsHandler(w http.ResponseWriter, r *http.Request) {
|
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 each app, get the deployment status
|
||||||
for i, app := range apps {
|
var apps []*pkg.App
|
||||||
deploymentStatus, err := s.GetDeploymentStatus(r.Context(), app.DeploymentID)
|
for _, app := range Apps.GetAllApps() {
|
||||||
|
var extApp pkg.App
|
||||||
|
deploymentStatus, err := app.Deployment.Status(r.Context())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed to get deployment status: %v\n", err)
|
log.Printf("Failed to get deployment status: %v\n", err)
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
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")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
|||||||
@@ -2,98 +2,291 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/juls0730/fluxd/models"
|
"github.com/juls0730/fluxd/pkg"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Creates a deployment and containers in the database
|
var (
|
||||||
func (s *FluxServer) CreateDeployment(ctx context.Context, projectConfig models.ProjectConfig, containerID string) (int64, error) {
|
Apps *AppManager = new(AppManager)
|
||||||
deploymentResult, err := s.db.Exec("INSERT INTO deployments (url) VALUES (?)", projectConfig.Url)
|
deploymentInsertStmt *sql.Stmt
|
||||||
if err != nil {
|
containerInsertStmt *sql.Stmt
|
||||||
log.Printf("Failed to insert deployment: %v\n", err)
|
)
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
deploymentID, err := deploymentResult.LastInsertId()
|
type AppManager struct {
|
||||||
if err != nil {
|
sync.Map
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *FluxServer) UpgradeDeployment(ctx context.Context, deploymentID int64, projectConfig models.ProjectConfig, imageName string, projectPath string) error {
|
type App struct {
|
||||||
configBytes, err := json.Marshal(projectConfig)
|
ID int64 `json:"id,omitempty"`
|
||||||
if err != nil {
|
Deployment Deployment `json:"-"`
|
||||||
log.Printf("Failed to marshal project config: %v\n", err)
|
Name string `json:"name,omitempty"`
|
||||||
return err
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("Failed to find existing containers: %v", err)
|
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
|
// 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 {
|
if err != nil {
|
||||||
log.Printf("Failed to create container: %v\n", err)
|
log.Printf("Failed to create container: %v\n", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// calls AddContainer in proxy
|
var container Container
|
||||||
err = s.containerManager.StartContainer(ctx, containerID)
|
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 {
|
if err != nil {
|
||||||
log.Printf("Failed to start container: %v\n", err)
|
log.Printf("Failed to start container: %v\n", err)
|
||||||
return 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)
|
log.Printf("Failed to wait for container: %v\n", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := s.db.Exec("INSERT INTO containers (container_id, deployment_id, head) VALUES (?, ?, ?)", containerID, deploymentID, true); err != nil {
|
tx, err := s.db.Begin()
|
||||||
log.Printf("Failed to insert container: %v\n", err)
|
if err != nil {
|
||||||
|
log.Printf("Failed to begin transaction: %v\n", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// update app in the database
|
if _, err := tx.Exec("UPDATE deployments SET url = ?, port = ? WHERE id = ?", projectConfig.Url, projectConfig.Port, deployment.ID); err != nil {
|
||||||
if _, err := s.db.Exec("UPDATE apps SET project_config = ?, deployment_id = ? WHERE name = ?", configBytes, deploymentID, projectConfig.Name); 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)
|
log.Printf("Failed to update app: %v\n", err)
|
||||||
|
tx.Rollback()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: swap containers if they are running and have the same image so that we can have a constant uptime
|
if err := tx.Commit(); err != nil {
|
||||||
tx, err := s.db.Begin()
|
log.Printf("Failed to commit transaction: %v\n", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
tx, err = s.db.Begin()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed to begin transaction: %v\n", err)
|
log.Printf("Failed to begin transaction: %v\n", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, existingContainer := range existingContainers {
|
// Create a new proxy that points to the new head, and replace the old one, but ensure that the old one is gracefully shutdown
|
||||||
log.Printf("Stopping existing container: %s\n", existingContainer[0:12])
|
oldProxy := deployment.Proxy
|
||||||
|
deployment.Proxy = &DeploymentProxy{
|
||||||
tx.Exec("DELETE FROM containers WHERE container_id = ?", existingContainer)
|
deployment: deployment,
|
||||||
err = s.containerManager.GracefullyRemoveContainer(ctx, existingContainer)
|
currentHead: &container,
|
||||||
if err != nil {
|
gracePeriod: time.Second * 30,
|
||||||
tx.Rollback()
|
activeRequests: 0,
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
if err := tx.Commit(); err != nil {
|
||||||
log.Printf("Failed to commit transaction: %v\n", err)
|
log.Printf("Failed to commit transaction: %v\n", err)
|
||||||
return err
|
return err
|
||||||
@@ -102,112 +295,42 @@ func (s *FluxServer) UpgradeDeployment(ctx context.Context, deploymentID int64,
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *FluxServer) StartDeployment(ctx context.Context, deploymentID int64) error {
|
func arrayContains(arr []string, str string) bool {
|
||||||
var containerIds []string
|
for _, a := range arr {
|
||||||
rows, err := s.db.Query("SELECT container_id FROM containers WHERE deployment_id = ?", deploymentID)
|
if a == str {
|
||||||
if err != nil {
|
return true
|
||||||
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
|
return false
|
||||||
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 {
|
func (d *Deployment) Start(ctx context.Context) error {
|
||||||
err := s.containerManager.StartContainer(ctx, containerId)
|
for _, container := range d.Containers {
|
||||||
s.Proxy.AddContainer(projectConfig, containerId)
|
err := container.Start(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed to start container: %v\n", err)
|
log.Printf("Failed to start container: %v\n", err)
|
||||||
return 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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *FluxServer) StopDeployment(ctx context.Context, deploymentID int64) error {
|
func (d *Deployment) Stop(ctx context.Context) error {
|
||||||
var containerIds []string
|
for _, container := range d.Containers {
|
||||||
rows, err := s.db.Query("SELECT container_id FROM containers WHERE deployment_id = ?", deploymentID)
|
err := container.Stop(ctx)
|
||||||
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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed to start container: %v\n", err)
|
log.Printf("Failed to start container: %v\n", err)
|
||||||
return 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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *FluxServer) GetStatus(ctx context.Context, containerID string) (string, error) {
|
func (c *Container) GetStatus(ctx context.Context) (string, error) {
|
||||||
containerJSON, err := s.containerManager.dockerClient.ContainerInspect(ctx, containerID)
|
containerJSON, err := dockerClient.ContainerInspect(ctx, string(c.ContainerID[:]))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -215,31 +338,20 @@ func (s *FluxServer) GetStatus(ctx context.Context, containerID string) (string,
|
|||||||
return containerJSON.State.Status, nil
|
return containerJSON.State.Status, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *FluxServer) GetDeploymentStatus(ctx context.Context, deploymentID int64) (string, error) {
|
func (d *Deployment) Status(ctx context.Context) (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)
|
|
||||||
}
|
|
||||||
|
|
||||||
var status string
|
var status string
|
||||||
for _, containerId := range containerIds {
|
if d == nil {
|
||||||
containerStatus, err := s.GetStatus(ctx, containerId)
|
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 {
|
if err != nil {
|
||||||
log.Printf("Failed to get container status: %v\n", err)
|
log.Printf("Failed to get container status: %v\n", err)
|
||||||
return "", err
|
return "", err
|
||||||
|
|||||||
243
server/proxy.go
243
server/proxy.go
@@ -2,7 +2,6 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -12,209 +11,119 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/juls0730/fluxd/models"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ContainerProxy struct {
|
var ReverseProxy *Proxy
|
||||||
routes *RouteCache
|
|
||||||
db *sql.DB
|
type Proxy struct {
|
||||||
cm *ContainerManager
|
deployments sync.Map
|
||||||
activeConns int64
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type RouteCache struct {
|
func (p *Proxy) AddDeployment(deployment *Deployment) {
|
||||||
m sync.Map
|
log.Printf("Adding deployment %s\n", deployment.URL)
|
||||||
|
p.deployments.Store(deployment.URL, deployment)
|
||||||
}
|
}
|
||||||
|
|
||||||
type containerRoute struct {
|
func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
containerID string
|
host := r.Host
|
||||||
port int
|
|
||||||
url string
|
|
||||||
proxy *httputil.ReverseProxy
|
|
||||||
isActive bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rc *RouteCache) GetRoute(appUrl string) *containerRoute {
|
deployment, ok := p.deployments.Load(host)
|
||||||
|
if !ok {
|
||||||
container, exists := rc.m.Load(appUrl)
|
http.Error(w, "Not found", http.StatusNotFound)
|
||||||
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)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
container.proxy.ServeHTTP(w, r)
|
atomic.AddInt64(&deployment.(*Deployment).Proxy.activeRequests, 1)
|
||||||
}
|
|
||||||
|
|
||||||
func (cp *ContainerProxy) AddContainer(projectConfig models.ProjectConfig, containerID string) error {
|
container := deployment.(*Deployment).Proxy.currentHead
|
||||||
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)
|
|
||||||
if container == nil {
|
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()
|
defer cancel()
|
||||||
|
|
||||||
|
// Create a channel to signal when wait group is done
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
cp.routes.DeleteRoute(url)
|
break
|
||||||
return nil
|
|
||||||
default:
|
default:
|
||||||
if atomic.LoadInt64(&cp.activeConns) == 0 {
|
if atomic.LoadInt64(&dp.activeRequests) == 0 {
|
||||||
cp.routes.DeleteRoute(url)
|
break
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cp *ContainerProxy) ScanRoutes() {
|
time.Sleep(time.Second)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
if err != nil {
|
||||||
log.Printf("Failed to query containers: %v\n", err)
|
log.Printf("Failed to remove container: %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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cp *ContainerProxy) Start() {
|
func InitProxy(apps *AppManager) {
|
||||||
cp.ScanRoutes()
|
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")
|
port := os.Getenv("FLUXD_PROXY_PORT")
|
||||||
if port == "" {
|
if port == "" {
|
||||||
port = "7465"
|
port = "7465"
|
||||||
}
|
}
|
||||||
|
|
||||||
server := &http.Server{
|
|
||||||
Addr: fmt.Sprintf(":%s", port),
|
|
||||||
Handler: cp,
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
log.Printf("Proxy server starting on http://127.0.0.1:%s\n", port)
|
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)
|
log.Fatalf("Proxy server error: %v", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|||||||
@@ -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 (
|
CREATE TABLE IF NOT EXISTS apps (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
image TEXT NOT NULL,
|
|
||||||
project_path TEXT NOT NULL,
|
|
||||||
project_config TEXT NOT NULL,
|
|
||||||
deployment_id INTEGER,
|
deployment_id INTEGER,
|
||||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
FOREIGN KEY(deployment_id) REFERENCES deployments(id)
|
FOREIGN KEY(deployment_id) REFERENCES deployments(id)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -14,12 +16,5 @@ CREATE TABLE IF NOT EXISTS containers (
|
|||||||
container_id TEXT NOT NULL,
|
container_id TEXT NOT NULL,
|
||||||
head BOOLEAN NOT NULL,
|
head BOOLEAN NOT NULL,
|
||||||
deployment_id INTEGER NOT NULL,
|
deployment_id INTEGER NOT NULL,
|
||||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
FOREIGN KEY(deployment_id) REFERENCES deployments(id)
|
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
|
|
||||||
);
|
);
|
||||||
@@ -4,48 +4,38 @@ import (
|
|||||||
"archive/tar"
|
"archive/tar"
|
||||||
"compress/gzip"
|
"compress/gzip"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"embed"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/juls0730/fluxd/models"
|
_ "embed"
|
||||||
|
|
||||||
|
"github.com/juls0730/fluxd/pkg"
|
||||||
_ "github.com/mattn/go-sqlite3"
|
_ "github.com/mattn/go-sqlite3"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed schema.sql
|
var (
|
||||||
var schema embed.FS
|
//go:embed schema.sql
|
||||||
|
schemaBytes []byte
|
||||||
var DefaultConfig = FluxServerConfig{
|
DefaultConfig = FluxServerConfig{
|
||||||
Builder: "paketobuildpacks/builder-jammy-tiny",
|
Builder: "paketobuildpacks/builder-jammy-tiny",
|
||||||
}
|
}
|
||||||
|
DB *sql.DB
|
||||||
|
)
|
||||||
|
|
||||||
type FluxServerConfig struct {
|
type FluxServerConfig struct {
|
||||||
Builder string `json:"builder"`
|
Builder string `json:"builder"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type FluxServer struct {
|
type FluxServer struct {
|
||||||
containerManager *ContainerManager
|
config FluxServerConfig
|
||||||
config FluxServerConfig
|
db *sql.DB
|
||||||
db *sql.DB
|
rootDir string
|
||||||
Proxy *ContainerProxy
|
|
||||||
rootDir string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// var rootDir string
|
|
||||||
|
|
||||||
// func init() {
|
|
||||||
// rootDir = os.Getenv("FLUXD_ROOT_DIR")
|
|
||||||
// if rootDir == "" {
|
|
||||||
// rootDir = "/var/fluxd"
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
func NewServer() *FluxServer {
|
func NewServer() *FluxServer {
|
||||||
containerManager := NewContainerManager()
|
|
||||||
|
|
||||||
var serverConfig FluxServerConfig
|
var serverConfig FluxServerConfig
|
||||||
|
|
||||||
rootDir := os.Getenv("FLUXD_ROOT_DIR")
|
rootDir := os.Getenv("FLUXD_ROOT_DIR")
|
||||||
@@ -84,36 +74,26 @@ func NewServer() *FluxServer {
|
|||||||
log.Fatalf("Failed to create apps directory: %v\n", err)
|
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 {
|
if err != nil {
|
||||||
log.Fatalf("Failed to open database: %v\n", err)
|
log.Fatalf("Failed to open database: %v\n", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// create database schema
|
_, err = DB.Exec(string(schemaBytes))
|
||||||
schemaBytes, err := schema.ReadFile("schema.sql")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Failed to read schema file: %v\n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = db.Exec(string(schemaBytes))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to create database schema: %v\n", err)
|
log.Fatalf("Failed to create database schema: %v\n", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Apps.Init()
|
||||||
|
|
||||||
return &FluxServer{
|
return &FluxServer{
|
||||||
containerManager: containerManager,
|
config: serverConfig,
|
||||||
config: serverConfig,
|
db: DB,
|
||||||
db: db,
|
|
||||||
Proxy: &ContainerProxy{
|
|
||||||
routes: &RouteCache{},
|
|
||||||
db: db,
|
|
||||||
cm: containerManager,
|
|
||||||
},
|
|
||||||
rootDir: rootDir,
|
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)
|
projectPath := filepath.Join(s.rootDir, "apps", projectConfig.Name)
|
||||||
if err := os.MkdirAll(projectPath, 0755); err != nil {
|
if err := os.MkdirAll(projectPath, 0755); err != nil {
|
||||||
log.Printf("Failed to create project directory: %v\n", err)
|
log.Printf("Failed to create project directory: %v\n", err)
|
||||||
|
|||||||
Reference in New Issue
Block a user