Massive architectural rework
This commit massively overhauls the project's structure to simplify development. Most parts are now correctly compartmentalized and dependencies are passed in a sane way rather than global variables galore xd.
This commit is contained in:
14
cmd/cli/commands/command.go
Normal file
14
cmd/cli/commands/command.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/juls0730/flux/pkg"
|
||||
"github.com/juls0730/flux/pkg/API"
|
||||
)
|
||||
|
||||
type CommandCtx struct {
|
||||
Config pkg.CLIConfig
|
||||
Info API.Info
|
||||
Interactive bool
|
||||
}
|
||||
|
||||
type CommandFunc func(CommandCtx, []string) error
|
||||
@@ -4,15 +4,13 @@ import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/juls0730/flux/cmd/flux/models"
|
||||
util "github.com/juls0730/flux/internal/util/cli"
|
||||
)
|
||||
|
||||
var usage = `Usage:
|
||||
var deleteUsage = `Usage:
|
||||
flux delete [project-name | all]
|
||||
|
||||
Options:
|
||||
@@ -22,11 +20,14 @@ Options:
|
||||
Flags:
|
||||
%s
|
||||
|
||||
Flux will delete the deployment of the app in the current directory or the specified project.
|
||||
`
|
||||
Flux will delete the deployment of the app in the current directory or the specified project.`
|
||||
|
||||
func deleteAll(ctx models.CommandCtx, noConfirm *bool) error {
|
||||
func deleteAll(ctx CommandCtx, noConfirm *bool) error {
|
||||
if !*noConfirm {
|
||||
if !ctx.Interactive {
|
||||
return fmt.Errorf("delete command cannot be run non-interactively without --no-confirm")
|
||||
}
|
||||
|
||||
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. [y/N] ")
|
||||
fmt.Scanln(&response)
|
||||
@@ -48,32 +49,13 @@ func deleteAll(ctx models.CommandCtx, noConfirm *bool) error {
|
||||
}
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("DELETE", ctx.Config.DeamonURL+"/deployments", nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete deployments: %v", err)
|
||||
}
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete deployments: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
responseBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading response body: %v", err)
|
||||
}
|
||||
|
||||
responseBody = []byte(strings.TrimSuffix(string(responseBody), "\n"))
|
||||
|
||||
return fmt.Errorf("delete failed: %s", responseBody)
|
||||
}
|
||||
util.DeleteRequest(ctx.Config.DaemonURL + "/deployments")
|
||||
|
||||
fmt.Printf("Successfully deleted all projects\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
func DeleteCommand(ctx models.CommandCtx, args []string) error {
|
||||
func DeleteCommand(ctx CommandCtx, args []string) error {
|
||||
fs := flag.NewFlagSet("delete", flag.ExitOnError)
|
||||
fs.Usage = func() {
|
||||
var buf bytes.Buffer
|
||||
@@ -81,7 +63,7 @@ func DeleteCommand(ctx models.CommandCtx, args []string) error {
|
||||
fs.SetOutput(&buf)
|
||||
fs.PrintDefaults()
|
||||
|
||||
fmt.Printf(usage, strings.TrimRight(buf.String(), "\n"))
|
||||
fmt.Println(deleteUsage, strings.TrimRight(buf.String(), "\n"))
|
||||
}
|
||||
|
||||
noConfirm := fs.Bool("no-confirm", false, "Skip confirmation prompt")
|
||||
@@ -98,14 +80,18 @@ func DeleteCommand(ctx models.CommandCtx, args []string) error {
|
||||
return deleteAll(ctx, noConfirm)
|
||||
}
|
||||
|
||||
projectName, err := GetProjectId("delete", args, ctx.Config)
|
||||
project, err := util.GetProject("delete", args, ctx.Config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("\tfailed to get project name: %v.\n\tSee flux delete --help for more information", err)
|
||||
}
|
||||
|
||||
// ask for confirmation
|
||||
// ask for confirmation if not --no-confirm
|
||||
if !*noConfirm {
|
||||
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)
|
||||
if !ctx.Interactive {
|
||||
return fmt.Errorf("delete command cannot be run non-interactively without --no-confirm")
|
||||
}
|
||||
|
||||
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] ", project.Name)
|
||||
var response string
|
||||
fmt.Scanln(&response)
|
||||
|
||||
@@ -115,25 +101,9 @@ func DeleteCommand(ctx models.CommandCtx, args []string) error {
|
||||
}
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("DELETE", ctx.Config.DeamonURL+"/deployments/"+projectName, nil)
|
||||
err = util.DeleteRequest(ctx.Config.DaemonURL + "/app/" + project.Id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete app: %v", err)
|
||||
}
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete app: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
responseBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading response body: %v", err)
|
||||
}
|
||||
|
||||
responseBody = []byte(strings.TrimSuffix(string(responseBody), "\n"))
|
||||
|
||||
return fmt.Errorf("delete failed: %s", responseBody)
|
||||
return fmt.Errorf("failed to delete project: %v", err)
|
||||
}
|
||||
|
||||
if len(args) == 0 {
|
||||
@@ -141,7 +111,7 @@ func DeleteCommand(ctx models.CommandCtx, args []string) error {
|
||||
os.Remove(".fluxid")
|
||||
}
|
||||
|
||||
fmt.Printf("Successfully deleted %s\n", projectName)
|
||||
fmt.Printf("Successfully deleted %s\n", project.Name)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -19,8 +19,10 @@ import (
|
||||
|
||||
"github.com/briandowns/spinner"
|
||||
"github.com/google/uuid"
|
||||
"github.com/juls0730/flux/cmd/flux/models"
|
||||
"github.com/joho/godotenv"
|
||||
util "github.com/juls0730/flux/internal/util/cli"
|
||||
"github.com/juls0730/flux/pkg"
|
||||
"github.com/juls0730/flux/pkg/API"
|
||||
)
|
||||
|
||||
func matchesIgnorePattern(path string, info os.FileInfo, patterns []string) bool {
|
||||
@@ -67,7 +69,7 @@ func convertGitignorePatternToRegex(pattern string) string {
|
||||
return pattern
|
||||
}
|
||||
|
||||
func compressDirectory(compression pkg.Compression) ([]byte, error) {
|
||||
func compressDirectory(compressionLevel int) ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
var err error
|
||||
|
||||
@@ -89,8 +91,8 @@ func compressDirectory(compression pkg.Compression) ([]byte, error) {
|
||||
}
|
||||
|
||||
var gzWriter *gzip.Writer
|
||||
if compression.Enabled {
|
||||
gzWriter, err = gzip.NewWriterLevel(&buf, compression.Level)
|
||||
if compressionLevel > 0 {
|
||||
gzWriter, err = gzip.NewWriterLevel(&buf, compressionLevel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -154,12 +156,31 @@ func compressDirectory(compression pkg.Compression) ([]byte, error) {
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func DeployCommand(ctx models.CommandCtx, args []string) error {
|
||||
func preprocessEnvFile(envFile string, target *[]string) error {
|
||||
envBytes, err := os.Open(envFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open env file: %v", err)
|
||||
}
|
||||
defer envBytes.Close()
|
||||
|
||||
envVars, err := godotenv.Parse(envBytes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse env file: %v", err)
|
||||
}
|
||||
|
||||
for key, value := range envVars {
|
||||
*target = append(*target, fmt.Sprintf("%s=%s", key, value))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func DeployCommand(ctx CommandCtx, args []string) error {
|
||||
if _, err := os.Stat("flux.json"); err != nil {
|
||||
return fmt.Errorf("no flux.json found, please run flux init first")
|
||||
}
|
||||
|
||||
spinnerWriter := models.NewCustomSpinnerWriter()
|
||||
spinnerWriter := util.NewCustomSpinnerWriter()
|
||||
|
||||
loadingSpinner := spinner.New(spinner.CharSets[14], 100*time.Millisecond, spinner.WithWriter(spinnerWriter))
|
||||
defer func() {
|
||||
@@ -182,7 +203,7 @@ func DeployCommand(ctx models.CommandCtx, args []string) error {
|
||||
loadingSpinner.Suffix = " Deploying"
|
||||
loadingSpinner.Start()
|
||||
|
||||
buf, err := compressDirectory(ctx.Info.Compression)
|
||||
buf, err := compressDirectory(ctx.Info.CompressionLevel)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to compress directory: %v", err)
|
||||
}
|
||||
@@ -220,17 +241,62 @@ func DeployCommand(ctx models.CommandCtx, args []string) error {
|
||||
return fmt.Errorf("failed to create config part: %v", err)
|
||||
}
|
||||
|
||||
type FluxContainers struct {
|
||||
pkg.Container
|
||||
EnvFile string `json:"env_file,omitempty"`
|
||||
}
|
||||
|
||||
type FluxConfig struct {
|
||||
pkg.ProjectConfig
|
||||
EnvFile string `json:"env_file,omitempty"`
|
||||
Containers []FluxContainers `json:"containers,omitempty"`
|
||||
}
|
||||
|
||||
fluxConfigFile, err := os.Open("flux.json")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open flux.json: %v", err)
|
||||
}
|
||||
defer fluxConfigFile.Close()
|
||||
|
||||
if _, err := io.Copy(configPart, fluxConfigFile); err != nil {
|
||||
return fmt.Errorf("failed to write config part: %v", err)
|
||||
// Read the entire JSON file into a byte slice
|
||||
byteValue, err := io.ReadAll(fluxConfigFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read flux.json: %v", err)
|
||||
}
|
||||
|
||||
codePart, err := writer.CreateFormFile("code", "code.tar.gz")
|
||||
var fluxConfig FluxConfig
|
||||
err = json.Unmarshal(byteValue, &fluxConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unmarshal flux.json: %v", err)
|
||||
}
|
||||
|
||||
if fluxConfig.EnvFile != "" {
|
||||
if err := preprocessEnvFile(fluxConfig.EnvFile, &fluxConfig.Environment); err != nil {
|
||||
return fmt.Errorf("failed to preprocess env file: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, container := range fluxConfig.Containers {
|
||||
if container.EnvFile != "" {
|
||||
if err := preprocessEnvFile(container.EnvFile, &container.Environment); err != nil {
|
||||
return fmt.Errorf("failed to preprocess env file: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// write the pre-processed flux.json to the config part
|
||||
if err := json.NewEncoder(configPart).Encode(fluxConfig); err != nil {
|
||||
return fmt.Errorf("failed to encode flux.json: %v", err)
|
||||
}
|
||||
|
||||
var codeFileName string
|
||||
if ctx.Info.CompressionLevel > 0 {
|
||||
codeFileName = "code.tar.gz"
|
||||
} else {
|
||||
codeFileName = "code.tar"
|
||||
}
|
||||
|
||||
codePart, err := writer.CreateFormFile("code", codeFileName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create code part: %v", err)
|
||||
}
|
||||
@@ -243,7 +309,7 @@ func DeployCommand(ctx models.CommandCtx, args []string) error {
|
||||
return fmt.Errorf("failed to close writer: %v", err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", ctx.Config.DeamonURL+"/deploy", body)
|
||||
req, err := http.NewRequest("POST", ctx.Config.DaemonURL+"/deploy", body)
|
||||
req.Header.Set("Content-Type", writer.FormDataContentType())
|
||||
|
||||
if err != nil {
|
||||
@@ -256,11 +322,11 @@ func DeployCommand(ctx models.CommandCtx, args []string) error {
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
customWriter := models.NewCustomStdout(spinnerWriter)
|
||||
customWriter := util.NewCustomStdout(spinnerWriter)
|
||||
|
||||
scanner := bufio.NewScanner(resp.Body)
|
||||
var event string
|
||||
var data pkg.DeploymentEvent
|
||||
var data API.DeploymentEvent
|
||||
var line string
|
||||
for scanner.Scan() {
|
||||
line = scanner.Text()
|
||||
@@ -1,27 +1,47 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/juls0730/flux/cmd/flux/models"
|
||||
"github.com/juls0730/flux/pkg"
|
||||
)
|
||||
|
||||
func InitCommand(ctx models.CommandCtx, args []string) error {
|
||||
// if seekingHelp {
|
||||
// fmt.Println(`Usage:
|
||||
// flux init [project-name]
|
||||
var initUsage = `Usage:
|
||||
flux init [project-name]
|
||||
|
||||
// Options:
|
||||
// project-name: The name of the project to initialize
|
||||
Options:
|
||||
project-name: The name of the project to initialize
|
||||
|
||||
// Flux will initialize a new project in the current directory or the specified project.`)
|
||||
// return nil
|
||||
// }
|
||||
Flux will initialize a new project in the current directory or the specified project.`
|
||||
|
||||
func InitCommand(ctx CommandCtx, args []string) error {
|
||||
if !ctx.Interactive {
|
||||
return fmt.Errorf("init command can only be run in interactive mode")
|
||||
}
|
||||
|
||||
fs := flag.NewFlagSet("init", flag.ExitOnError)
|
||||
fs.Usage = func() {
|
||||
var buf bytes.Buffer
|
||||
// Redirect flagset to print to buffer instead of stdout
|
||||
fs.SetOutput(&buf)
|
||||
fs.PrintDefaults()
|
||||
|
||||
fmt.Println(initUsage)
|
||||
}
|
||||
|
||||
err := fs.Parse(args)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
args = fs.Args()
|
||||
|
||||
var projectConfig pkg.ProjectConfig
|
||||
|
||||
26
cmd/cli/commands/list.go
Normal file
26
cmd/cli/commands/list.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
util "github.com/juls0730/flux/internal/util/cli"
|
||||
"github.com/juls0730/flux/pkg/API"
|
||||
)
|
||||
|
||||
func ListCommand(ctx CommandCtx, args []string) error {
|
||||
apps, err := util.GetRequest[[]API.App](ctx.Config.DaemonURL + "/apps")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get apps: %v", err)
|
||||
}
|
||||
|
||||
if len(*apps) == 0 {
|
||||
fmt.Println("No apps found")
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, app := range *apps {
|
||||
fmt.Printf("%s (%s)\n", app.Name, app.DeploymentStatus)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
22
cmd/cli/commands/start.go
Normal file
22
cmd/cli/commands/start.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
util "github.com/juls0730/flux/internal/util/cli"
|
||||
)
|
||||
|
||||
func StartCommand(ctx CommandCtx, args []string) error {
|
||||
projectName, err := util.GetProject("start", args, ctx.Config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Put request to start the project, since the start endpoint is idempotent.
|
||||
// If the project is already running, this will return a 304 Not Modified
|
||||
util.PutRequest(ctx.Config.DaemonURL+"/app/"+projectName.Id+"/start", nil)
|
||||
|
||||
fmt.Printf("Successfully started %s\n", projectName)
|
||||
|
||||
return nil
|
||||
}
|
||||
19
cmd/cli/commands/stop.go
Normal file
19
cmd/cli/commands/stop.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
util "github.com/juls0730/flux/internal/util/cli"
|
||||
)
|
||||
|
||||
func StopCommand(ctx CommandCtx, args []string) error {
|
||||
projectName, err := util.GetProject("stop", args, ctx.Config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
util.PutRequest(ctx.Config.DaemonURL+"/app/"+projectName.Id+"/stop", nil)
|
||||
|
||||
fmt.Printf("Successfully stopped %s\n", projectName)
|
||||
return nil
|
||||
}
|
||||
@@ -5,17 +5,22 @@ import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/agnivade/levenshtein"
|
||||
"github.com/juls0730/flux/cmd/flux/commands"
|
||||
"github.com/juls0730/flux/cmd/flux/models"
|
||||
"github.com/juls0730/flux/cmd/cli/commands"
|
||||
util "github.com/juls0730/flux/internal/util/cli"
|
||||
"github.com/juls0730/flux/pkg"
|
||||
"github.com/juls0730/flux/pkg/API"
|
||||
"github.com/mattn/go-isatty"
|
||||
)
|
||||
|
||||
func isInteractive() bool {
|
||||
return isatty.IsTerminal(os.Stdout.Fd()) || isatty.IsCygwinTerminal(os.Stdout.Fd())
|
||||
}
|
||||
|
||||
//go:embed config.json
|
||||
var config []byte
|
||||
|
||||
@@ -37,11 +42,9 @@ Use "flux <command> --help" for more information about a command.
|
||||
|
||||
var maxDistance = 3
|
||||
|
||||
type CommandFunc func(models.CommandCtx, []string) error
|
||||
|
||||
type Command struct {
|
||||
Help string
|
||||
HandlerFunc CommandFunc
|
||||
HandlerFunc commands.CommandFunc
|
||||
}
|
||||
|
||||
type CommandHandler struct {
|
||||
@@ -56,7 +59,7 @@ func NewCommandHandler() CommandHandler {
|
||||
}
|
||||
}
|
||||
|
||||
func (h *CommandHandler) RegisterCmd(name string, handler CommandFunc, help string) {
|
||||
func (h *CommandHandler) RegisterCmd(name string, handler commands.CommandFunc, help string) {
|
||||
coomand := Command{
|
||||
Help: help,
|
||||
HandlerFunc: handler,
|
||||
@@ -100,15 +103,16 @@ func (h *CommandHandler) GetHelp() {
|
||||
fmt.Printf(helpStr, strings.TrimRight(commandsStr, "\n"))
|
||||
}
|
||||
|
||||
func (h *CommandHandler) GetHelpCmd(models.CommandCtx, []string) error {
|
||||
func (h *CommandHandler) GetHelpCmd(commands.CommandCtx, []string) error {
|
||||
h.GetHelp()
|
||||
return nil
|
||||
}
|
||||
|
||||
func runCommand(command string, args []string, config models.Config, info pkg.Info, cmdHandler CommandHandler) error {
|
||||
commandCtx := models.CommandCtx{
|
||||
Config: config,
|
||||
Info: info,
|
||||
func runCommand(command string, args []string, config pkg.CLIConfig, info API.Info, cmdHandler CommandHandler) error {
|
||||
commandCtx := commands.CommandCtx{
|
||||
Config: config,
|
||||
Info: info,
|
||||
Interactive: isInteractive(),
|
||||
}
|
||||
|
||||
commandStruct, ok := cmdHandler.commands[command]
|
||||
@@ -152,6 +156,10 @@ func runCommand(command string, args []string, config models.Config, info pkg.In
|
||||
}
|
||||
|
||||
func main() {
|
||||
if !isInteractive() {
|
||||
fmt.Printf("Flux is being run non-interactively\n")
|
||||
}
|
||||
|
||||
cmdHandler := NewCommandHandler()
|
||||
|
||||
cmdHandler.RegisterCmd("init", commands.InitCommand, "Initialize a new project")
|
||||
@@ -189,7 +197,7 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
var config models.Config
|
||||
var config pkg.CLIConfig
|
||||
configBytes, err := os.ReadFile(filepath.Join(configPath, "config.json"))
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to read config file: %v\n", err)
|
||||
@@ -201,26 +209,14 @@ func main() {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
resp, err := http.Get(config.DeamonURL + "/heartbeat")
|
||||
if err != nil {
|
||||
fmt.Println("Failed to connect to daemon")
|
||||
os.Exit(1)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
fmt.Println("Failed to connect to daemon")
|
||||
if config.DaemonURL == "" {
|
||||
fmt.Printf("Daemon URL is empty\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
var info pkg.Info
|
||||
err = json.NewDecoder(resp.Body).Decode(&info)
|
||||
info, err := util.GetRequest[API.Info](config.DaemonURL + "/heartbeat")
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to decode info: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
fmt.Println("Failed to connect to daemon")
|
||||
fmt.Printf("Failed to connect to daemon\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
@@ -229,7 +225,7 @@ func main() {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
err = runCommand(os.Args[1], fs.Args()[1:], config, info, cmdHandler)
|
||||
err = runCommand(os.Args[1], fs.Args()[1:], config, *info, cmdHandler)
|
||||
if err != nil {
|
||||
fmt.Printf("Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
35
cmd/daemon/main.go
Normal file
35
cmd/daemon/main.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
_ "net/http/pprof"
|
||||
"os"
|
||||
|
||||
"github.com/juls0730/flux/internal/handlers"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fluxServer := handlers.NewServer()
|
||||
defer fluxServer.Stop()
|
||||
|
||||
http.HandleFunc("POST /deploy", fluxServer.DeployNewApp)
|
||||
|
||||
http.HandleFunc("GET /apps", fluxServer.GetAllApps)
|
||||
http.HandleFunc("GET /app/by-name/{name}", fluxServer.GetAppByName)
|
||||
http.HandleFunc("GET /app/by-id/{id}", fluxServer.GetAppById)
|
||||
|
||||
http.HandleFunc("PUT /app/{id}/start", fluxServer.StartApp)
|
||||
http.HandleFunc("PUT /app/{id}/stop", fluxServer.StopApp)
|
||||
|
||||
http.HandleFunc("DELETE /apps", fluxServer.DeleteAllDeploymentsHandler)
|
||||
http.HandleFunc("DELETE /app/{id}", fluxServer.DeleteDeployHandler)
|
||||
|
||||
http.HandleFunc("GET /heartbeat", fluxServer.DaemonInfoHandler)
|
||||
|
||||
err := fluxServer.ListenAndServe()
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to start server: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/juls0730/flux/cmd/flux/models"
|
||||
"github.com/juls0730/flux/pkg"
|
||||
)
|
||||
|
||||
func ListCommand(ctx models.CommandCtx, args []string) error {
|
||||
resp, err := http.Get(ctx.Config.DeamonURL + "/apps")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get apps: %v", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
responseBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading response body: %v", err)
|
||||
}
|
||||
|
||||
responseBody = []byte(strings.TrimSuffix(string(responseBody), "\n"))
|
||||
|
||||
return fmt.Errorf("list failed: %s", responseBody)
|
||||
}
|
||||
|
||||
var apps []pkg.App
|
||||
if err := json.NewDecoder(resp.Body).Decode(&apps); err != nil {
|
||||
return fmt.Errorf("failed to decode apps: %v", err)
|
||||
}
|
||||
|
||||
if len(apps) == 0 {
|
||||
fmt.Println("No apps found")
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, app := range apps {
|
||||
fmt.Printf("%s (%s)\n", app.Name, app.DeploymentStatus)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/juls0730/flux/cmd/flux/models"
|
||||
"github.com/juls0730/flux/pkg"
|
||||
)
|
||||
|
||||
func GetProjectId(command string, args []string, config models.Config) (string, error) {
|
||||
var projectName string
|
||||
|
||||
if _, err := os.Stat(".fluxid"); err == nil {
|
||||
id, err := os.ReadFile(".fluxid")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to read .fluxid: %v", err)
|
||||
}
|
||||
|
||||
return string(id), nil
|
||||
}
|
||||
|
||||
if len(args) == 0 {
|
||||
if _, err := os.Stat("flux.json"); err != nil {
|
||||
return "", fmt.Errorf("the current directory is not a flux project, please run flux %[1]s in the project directory", command)
|
||||
}
|
||||
|
||||
fluxConfigFile, err := os.Open("flux.json")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to open flux.json: %v", err)
|
||||
}
|
||||
defer fluxConfigFile.Close()
|
||||
|
||||
var config pkg.ProjectConfig
|
||||
if err := json.NewDecoder(fluxConfigFile).Decode(&config); err != nil {
|
||||
return "", fmt.Errorf("failed to decode flux.json: %v", err)
|
||||
}
|
||||
|
||||
projectName = config.Name
|
||||
} else {
|
||||
projectName = args[0]
|
||||
}
|
||||
|
||||
// make an http get request to the daemon to get the project name
|
||||
resp, err := http.Get(config.DeamonURL + "/apps/by-name/" + projectName)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get project name: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
responseBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error reading response body: %v", err)
|
||||
}
|
||||
|
||||
responseBody = []byte(strings.TrimSuffix(string(responseBody), "\n"))
|
||||
|
||||
return "", fmt.Errorf("get project name failed: %s", responseBody)
|
||||
}
|
||||
|
||||
var app pkg.App
|
||||
if err := json.NewDecoder(resp.Body).Decode(&app); err != nil {
|
||||
return "", fmt.Errorf("failed to decode app: %v", err)
|
||||
}
|
||||
|
||||
return app.Id.String(), nil
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/juls0730/flux/cmd/flux/models"
|
||||
)
|
||||
|
||||
func StartCommand(ctx models.CommandCtx, args []string) error {
|
||||
projectName, err := GetProjectId("start", args, ctx.Config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req, err := http.Post(ctx.Config.DeamonURL+"/start/"+projectName, "application/json", nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to start app: %v", err)
|
||||
}
|
||||
defer req.Body.Close()
|
||||
|
||||
if req.StatusCode != http.StatusOK {
|
||||
responseBody, err := io.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading response body: %v", err)
|
||||
}
|
||||
|
||||
responseBody = []byte(strings.TrimSuffix(string(responseBody), "\n"))
|
||||
|
||||
return fmt.Errorf("start failed: %s", responseBody)
|
||||
}
|
||||
|
||||
fmt.Printf("Successfully started %s\n", projectName)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/juls0730/flux/cmd/flux/models"
|
||||
)
|
||||
|
||||
func StopCommand(ctx models.CommandCtx, args []string) error {
|
||||
projectName, err := GetProjectId("stop", args, ctx.Config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req, err := http.Post(ctx.Config.DeamonURL+"/stop/"+projectName, "application/json", nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to stop app: %v", err)
|
||||
}
|
||||
defer req.Body.Close()
|
||||
|
||||
if req.StatusCode != http.StatusOK {
|
||||
responseBody, err := io.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading response body: %v", err)
|
||||
}
|
||||
|
||||
responseBody = []byte(strings.TrimSuffix(string(responseBody), "\n"))
|
||||
|
||||
return fmt.Errorf("stop failed: %s", responseBody)
|
||||
}
|
||||
|
||||
fmt.Printf("Successfully stopped %s\n", projectName)
|
||||
return nil
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package models
|
||||
|
||||
import "github.com/juls0730/flux/pkg"
|
||||
|
||||
type CommandCtx struct {
|
||||
Config Config
|
||||
Info pkg.Info
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
package models
|
||||
|
||||
type Config struct {
|
||||
DeamonURL string `json:"deamon_url"`
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type CustomSpinnerWriter struct {
|
||||
currentSpinnerMsg string
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
func NewCustomSpinnerWriter() *CustomSpinnerWriter {
|
||||
return &CustomSpinnerWriter{
|
||||
currentSpinnerMsg: "",
|
||||
lock: sync.Mutex{},
|
||||
}
|
||||
}
|
||||
|
||||
func (w *CustomSpinnerWriter) Write(p []byte) (n int, err error) {
|
||||
w.lock.Lock()
|
||||
defer w.lock.Unlock()
|
||||
|
||||
n, err = os.Stdout.Write(p)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
|
||||
w.currentSpinnerMsg = string(p)
|
||||
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
type CustomStdout struct {
|
||||
spinner *CustomSpinnerWriter
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
func NewCustomStdout(spinner *CustomSpinnerWriter) *CustomStdout {
|
||||
return &CustomStdout{
|
||||
spinner: spinner,
|
||||
lock: sync.Mutex{},
|
||||
}
|
||||
}
|
||||
|
||||
// We have this custom writer because we want to have a spinner at the bottom of the terminal, but we dont want to have
|
||||
// it interfere with the output of the command
|
||||
func (w *CustomStdout) Write(p []byte) (n int, err error) {
|
||||
w.lock.Lock()
|
||||
defer w.lock.Unlock()
|
||||
|
||||
// clear line and carriage return
|
||||
n, err = os.Stdout.Write(fmt.Appendf(nil, "\033[2K\r%s", p))
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
|
||||
nn, err := os.Stdout.Write([]byte(w.spinner.currentSpinnerMsg))
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
|
||||
n = nn + n
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (w *CustomStdout) Printf(format string, a ...interface{}) (n int, err error) {
|
||||
str := fmt.Sprintf(format, a...)
|
||||
return w.Write([]byte(str))
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
_ "net/http/pprof"
|
||||
|
||||
"github.com/juls0730/flux/internal/server"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fluxServer := server.NewServer()
|
||||
defer fluxServer.Stop()
|
||||
|
||||
http.HandleFunc("POST /deploy", fluxServer.DeployHandler)
|
||||
http.HandleFunc("DELETE /deployments", fluxServer.DeleteAllDeploymentsHandler)
|
||||
http.HandleFunc("DELETE /deployments/{id}", fluxServer.DeleteDeployHandler)
|
||||
http.HandleFunc("POST /start/{id}", fluxServer.StartDeployHandler)
|
||||
http.HandleFunc("POST /stop/{id}", fluxServer.StopDeployHandler)
|
||||
http.HandleFunc("GET /apps", fluxServer.ListAppsHandler)
|
||||
http.HandleFunc("GET /apps/by-name/{name}", fluxServer.GetAppByNameHandler)
|
||||
http.HandleFunc("GET /heartbeat", fluxServer.DaemonInfoHandler)
|
||||
|
||||
fluxServer.Logger.Info("Fluxd started on http://127.0.0.1:5647")
|
||||
err := http.ListenAndServe(":5647", nil)
|
||||
if err != nil {
|
||||
fluxServer.Logger.Fatalf("Failed to start server: %v", zap.Error(err))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user