fix pipe closed issue, and a lot of other stuff
This commit is contained in:
113
cmd/flux/handlers/delete.go
Normal file
113
cmd/flux/handlers/delete.go
Normal file
@@ -0,0 +1,113 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/briandowns/spinner"
|
||||
"github.com/juls0730/flux/cmd/flux/models"
|
||||
"github.com/juls0730/flux/pkg"
|
||||
)
|
||||
|
||||
func DeleteCommand(seekingHelp bool, config models.Config, info pkg.Info, loadingSpinner *spinner.Spinner, spinnerWriter *models.CustomSpinnerWriter, args []string) error {
|
||||
if seekingHelp {
|
||||
fmt.Println(`Usage:
|
||||
flux delete [project-name | all]
|
||||
|
||||
Options:
|
||||
project-name: The name of the project to delete
|
||||
all: Delete all projects
|
||||
|
||||
Flux will delete the deployment of the app in the current directory or the specified project.`)
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(args) == 1 {
|
||||
if args[0] == "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...")
|
||||
return nil
|
||||
}
|
||||
|
||||
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...")
|
||||
return nil
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("DELETE", 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)
|
||||
}
|
||||
|
||||
fmt.Printf("Successfully deleted all projects\n")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
projectName, err := GetProjectName("delete", args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 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)
|
||||
var response string
|
||||
fmt.Scanln(&response)
|
||||
|
||||
if strings.ToLower(response) != "y" {
|
||||
fmt.Println("Aborting...")
|
||||
return nil
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("DELETE", config.DeamonURL+"/deployments/"+projectName, nil)
|
||||
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)
|
||||
}
|
||||
|
||||
fmt.Printf("Successfully deleted %s\n", projectName)
|
||||
|
||||
return nil
|
||||
}
|
||||
254
cmd/flux/handlers/deploy.go
Normal file
254
cmd/flux/handlers/deploy.go
Normal file
@@ -0,0 +1,254 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bufio"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/briandowns/spinner"
|
||||
"github.com/juls0730/flux/cmd/flux/models"
|
||||
"github.com/juls0730/flux/pkg"
|
||||
)
|
||||
|
||||
func matchesIgnorePattern(path string, info os.FileInfo, patterns []string) bool {
|
||||
normalizedPath := filepath.ToSlash(path)
|
||||
normalizedPath = strings.TrimPrefix(normalizedPath, "./")
|
||||
|
||||
for _, pattern := range patterns {
|
||||
pattern = strings.TrimSpace(pattern)
|
||||
if pattern == "" || strings.HasPrefix(pattern, "#") {
|
||||
continue
|
||||
}
|
||||
|
||||
regexPattern := convertGitignorePatternToRegex(pattern)
|
||||
|
||||
matched, err := regexp.MatchString(regexPattern, normalizedPath)
|
||||
if err == nil && matched {
|
||||
if strings.HasSuffix(pattern, "/") && info.IsDir() {
|
||||
return true
|
||||
}
|
||||
if !info.IsDir() {
|
||||
dir := filepath.Dir(normalizedPath)
|
||||
for dir != "." && dir != "/" {
|
||||
dirPattern := convertGitignorePatternToRegex(pattern)
|
||||
if matched, _ := regexp.MatchString(dirPattern, filepath.ToSlash(dir)); matched {
|
||||
return true
|
||||
}
|
||||
dir = filepath.Dir(dir)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func convertGitignorePatternToRegex(pattern string) string {
|
||||
pattern = strings.TrimSuffix(pattern, "/")
|
||||
pattern = regexp.QuoteMeta(pattern)
|
||||
pattern = strings.ReplaceAll(pattern, "\\*\\*", ".*")
|
||||
pattern = strings.ReplaceAll(pattern, "\\*", "[^/]*")
|
||||
pattern = strings.ReplaceAll(pattern, "\\?", ".")
|
||||
pattern = "(^|.*/)" + pattern + "(/.*)?$"
|
||||
|
||||
return pattern
|
||||
}
|
||||
|
||||
func compressDirectory(compression pkg.Compression) ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
var err error
|
||||
|
||||
var ignoredFiles []string
|
||||
fluxIgnore, err := os.Open(".fluxignore")
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if fluxIgnore != nil {
|
||||
defer fluxIgnore.Close()
|
||||
|
||||
scanner := bufio.NewScanner(fluxIgnore)
|
||||
for scanner.Scan() {
|
||||
ignoredFiles = append(ignoredFiles, scanner.Text())
|
||||
}
|
||||
}
|
||||
|
||||
var gzWriter *gzip.Writer
|
||||
if compression.Enabled {
|
||||
gzWriter, err = gzip.NewWriterLevel(&buf, compression.Level)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var tarWriter *tar.Writer
|
||||
if gzWriter != nil {
|
||||
tarWriter = tar.NewWriter(gzWriter)
|
||||
} else {
|
||||
tarWriter = tar.NewWriter(&buf)
|
||||
}
|
||||
|
||||
err = filepath.Walk(".", func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if path == "flux.json" || info.IsDir() || matchesIgnorePattern(path, info, ignoredFiles) {
|
||||
return nil
|
||||
}
|
||||
|
||||
header, err := tar.FileInfoHeader(info, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
header.Name = path
|
||||
|
||||
if err = tarWriter.WriteHeader(header); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !info.IsDir() {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
if _, err = io.Copy(tarWriter, file); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = tarWriter.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if gzWriter != nil {
|
||||
if err = gzWriter.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func DeployCommand(seekingHelp bool, config models.Config, info pkg.Info, loadingSpinner *spinner.Spinner, spinnerWriter *models.CustomSpinnerWriter, args []string) error {
|
||||
if seekingHelp {
|
||||
fmt.Println(`Usage:
|
||||
flux deploy
|
||||
|
||||
Flux will deploy the app in the current directory, and start routing traffic to it.`)
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, err := os.Stat("flux.json"); err != nil {
|
||||
return fmt.Errorf("no flux.json found, please run flux init first")
|
||||
}
|
||||
|
||||
loadingSpinner.Suffix = " Deploying"
|
||||
loadingSpinner.Start()
|
||||
|
||||
buf, err := compressDirectory(info.Compression)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to compress directory: %v", err)
|
||||
}
|
||||
|
||||
body := &bytes.Buffer{}
|
||||
writer := multipart.NewWriter(body)
|
||||
configPart, err := writer.CreateFormFile("config", "flux.json")
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create config part: %v", err)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
codePart, err := writer.CreateFormFile("code", "code.tar.gz")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create code part: %v", err)
|
||||
}
|
||||
|
||||
if _, err := codePart.Write(buf); err != nil {
|
||||
return fmt.Errorf("failed to write code part: %v", err)
|
||||
}
|
||||
|
||||
if err := writer.Close(); err != nil {
|
||||
return fmt.Errorf("failed to close writer: %v", err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", config.DeamonURL+"/deploy", body)
|
||||
req.Header.Set("Content-Type", writer.FormDataContentType())
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create request: %v", err)
|
||||
}
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to send request: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
customWriter := models.NewCustomStdout(spinnerWriter)
|
||||
|
||||
scanner := bufio.NewScanner(resp.Body)
|
||||
var event string
|
||||
var data pkg.DeploymentEvent
|
||||
var line string
|
||||
for scanner.Scan() {
|
||||
line = scanner.Text()
|
||||
if strings.HasPrefix(line, "data: ") {
|
||||
if err := json.Unmarshal([]byte(line[6:]), &data); err != nil {
|
||||
return fmt.Errorf("failed to parse deployment event: %v", err)
|
||||
}
|
||||
|
||||
switch event {
|
||||
case "complete":
|
||||
loadingSpinner.Stop()
|
||||
fmt.Printf("App %s deployed successfully!\n", data.Message.(map[string]interface{})["name"])
|
||||
return nil
|
||||
case "cmd_output":
|
||||
customWriter.Printf("... %s\n", data.Message)
|
||||
case "error":
|
||||
loadingSpinner.Stop()
|
||||
return fmt.Errorf("deployment failed: %s", data.Message)
|
||||
default:
|
||||
customWriter.Printf("%s\n", data.Message)
|
||||
}
|
||||
event = ""
|
||||
} else if strings.HasPrefix(line, "event: ") {
|
||||
event = strings.TrimPrefix(line, "event: ")
|
||||
}
|
||||
}
|
||||
|
||||
// the stream closed, but we didnt get a "complete" event
|
||||
line = strings.TrimSuffix(line, "\n")
|
||||
return fmt.Errorf("deploy failed: %s", line)
|
||||
}
|
||||
73
cmd/flux/handlers/init.go
Normal file
73
cmd/flux/handlers/init.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/briandowns/spinner"
|
||||
"github.com/juls0730/flux/cmd/flux/models"
|
||||
"github.com/juls0730/flux/pkg"
|
||||
)
|
||||
|
||||
func InitCommand(seekingHelp bool, config models.Config, info pkg.Info, loadingSpinner *spinner.Spinner, spinnerWriter *models.CustomSpinnerWriter, args []string) error {
|
||||
if seekingHelp {
|
||||
fmt.Println(`Usage:
|
||||
flux init [project-name]
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
var projectConfig pkg.ProjectConfig
|
||||
|
||||
var response string
|
||||
if len(args) > 1 {
|
||||
response = args[0]
|
||||
} else {
|
||||
fmt.Println("What is the name of your project?")
|
||||
fmt.Scanln(&response)
|
||||
}
|
||||
|
||||
projectConfig.Name = response
|
||||
|
||||
fmt.Println("What URL should your project listen to?")
|
||||
fmt.Scanln(&response)
|
||||
if strings.HasPrefix(response, "http") {
|
||||
response = strings.TrimPrefix(response, "http://")
|
||||
response = strings.TrimPrefix(response, "https://")
|
||||
}
|
||||
|
||||
response = strings.Split(response, "/")[0]
|
||||
|
||||
projectConfig.Url = response
|
||||
|
||||
fmt.Println("What port does your project listen to?")
|
||||
fmt.Scanln(&response)
|
||||
port, err := strconv.ParseUint(response, 10, 16)
|
||||
portErr := fmt.Errorf("that doesnt look like a valid port, try a number between 1024 and 65535")
|
||||
if port > 65535 {
|
||||
return portErr
|
||||
}
|
||||
|
||||
projectConfig.Port = uint16(port)
|
||||
if err != nil || projectConfig.Port < 1024 {
|
||||
return portErr
|
||||
}
|
||||
|
||||
configBytes, err := json.MarshalIndent(projectConfig, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse project config: %v", err)
|
||||
}
|
||||
|
||||
os.WriteFile("flux.json", configBytes, 0644)
|
||||
|
||||
fmt.Printf("Successfully initialized project %s\n", projectConfig.Name)
|
||||
|
||||
return nil
|
||||
}
|
||||
55
cmd/flux/handlers/list.go
Normal file
55
cmd/flux/handlers/list.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/briandowns/spinner"
|
||||
"github.com/juls0730/flux/cmd/flux/models"
|
||||
"github.com/juls0730/flux/pkg"
|
||||
)
|
||||
|
||||
func ListCommand(seekingHelp bool, config models.Config, info pkg.Info, loadingSpinner *spinner.Spinner, spinnerWriter *models.CustomSpinnerWriter, args []string) error {
|
||||
if seekingHelp {
|
||||
fmt.Println(`Usage:
|
||||
flux list
|
||||
|
||||
Flux will list all the apps in the daemon.`)
|
||||
return nil
|
||||
}
|
||||
|
||||
resp, err := http.Get(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
|
||||
}
|
||||
36
cmd/flux/handlers/project.go
Normal file
36
cmd/flux/handlers/project.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/juls0730/flux/pkg"
|
||||
)
|
||||
|
||||
func GetProjectName(command string, args []string) (string, error) {
|
||||
var projectName string
|
||||
|
||||
if len(args) == 0 {
|
||||
if _, err := os.Stat("flux.json"); err != nil {
|
||||
return "", fmt.Errorf("usage: flux %[1]s <app name>, or 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]
|
||||
}
|
||||
|
||||
return projectName, nil
|
||||
}
|
||||
48
cmd/flux/handlers/start.go
Normal file
48
cmd/flux/handlers/start.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/briandowns/spinner"
|
||||
"github.com/juls0730/flux/cmd/flux/models"
|
||||
"github.com/juls0730/flux/pkg"
|
||||
)
|
||||
|
||||
func StartCommand(seekingHelp bool, config models.Config, info pkg.Info, loadingSpinner *spinner.Spinner, spinnerWriter *models.CustomSpinnerWriter, args []string) error {
|
||||
if seekingHelp {
|
||||
fmt.Println(`Usage:
|
||||
flux start
|
||||
|
||||
Flux will start the deployment of the app in the current directory.`)
|
||||
return nil
|
||||
}
|
||||
|
||||
projectName, err := GetProjectName("start", args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req, err := http.Post(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
|
||||
}
|
||||
47
cmd/flux/handlers/stop.go
Normal file
47
cmd/flux/handlers/stop.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/briandowns/spinner"
|
||||
"github.com/juls0730/flux/cmd/flux/models"
|
||||
"github.com/juls0730/flux/pkg"
|
||||
)
|
||||
|
||||
func StopCommand(seekingHelp bool, config models.Config, info pkg.Info, loadingSpinner *spinner.Spinner, spinnerWriter *models.CustomSpinnerWriter, args []string) error {
|
||||
if seekingHelp {
|
||||
fmt.Println(`Usage:
|
||||
flux stop
|
||||
|
||||
Flux will stop the deployment of the app in the current directory.`)
|
||||
return nil
|
||||
}
|
||||
|
||||
projectName, err := GetProjectName("stop", args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req, err := http.Post(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
|
||||
}
|
||||
649
cmd/flux/main.go
649
cmd/flux/main.go
@@ -1,27 +1,20 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bufio"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/agnivade/levenshtein"
|
||||
"github.com/briandowns/spinner"
|
||||
"github.com/juls0730/flux/cmd/flux/handlers"
|
||||
"github.com/juls0730/flux/cmd/flux/models"
|
||||
"github.com/juls0730/flux/pkg"
|
||||
)
|
||||
|
||||
@@ -30,613 +23,6 @@ var config []byte
|
||||
|
||||
var configPath = filepath.Join(os.Getenv("HOME"), "/.config/flux")
|
||||
|
||||
type Config struct {
|
||||
DeamonURL string `json:"deamon_url"`
|
||||
}
|
||||
|
||||
func matchesIgnorePattern(path string, info os.FileInfo, patterns []string) bool {
|
||||
normalizedPath := filepath.ToSlash(path)
|
||||
normalizedPath = strings.TrimPrefix(normalizedPath, "./")
|
||||
|
||||
for _, pattern := range patterns {
|
||||
pattern = strings.TrimSpace(pattern)
|
||||
if pattern == "" || strings.HasPrefix(pattern, "#") {
|
||||
continue
|
||||
}
|
||||
|
||||
regexPattern := convertGitignorePatternToRegex(pattern)
|
||||
|
||||
matched, err := regexp.MatchString(regexPattern, normalizedPath)
|
||||
if err == nil && matched {
|
||||
if strings.HasSuffix(pattern, "/") && info.IsDir() {
|
||||
return true
|
||||
}
|
||||
if !info.IsDir() {
|
||||
dir := filepath.Dir(normalizedPath)
|
||||
for dir != "." && dir != "/" {
|
||||
dirPattern := convertGitignorePatternToRegex(pattern)
|
||||
if matched, _ := regexp.MatchString(dirPattern, filepath.ToSlash(dir)); matched {
|
||||
return true
|
||||
}
|
||||
dir = filepath.Dir(dir)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func convertGitignorePatternToRegex(pattern string) string {
|
||||
pattern = strings.TrimSuffix(pattern, "/")
|
||||
pattern = regexp.QuoteMeta(pattern)
|
||||
pattern = strings.ReplaceAll(pattern, "\\*\\*", ".*")
|
||||
pattern = strings.ReplaceAll(pattern, "\\*", "[^/]*")
|
||||
pattern = strings.ReplaceAll(pattern, "\\?", ".")
|
||||
pattern = "(^|.*/)" + pattern + "(/.*)?$"
|
||||
|
||||
return pattern
|
||||
}
|
||||
|
||||
func compressDirectory(compression pkg.Compression) ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
var err error
|
||||
|
||||
var ignoredFiles []string
|
||||
fluxIgnore, err := os.Open(".fluxignore")
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if fluxIgnore != nil {
|
||||
defer fluxIgnore.Close()
|
||||
|
||||
scanner := bufio.NewScanner(fluxIgnore)
|
||||
for scanner.Scan() {
|
||||
ignoredFiles = append(ignoredFiles, scanner.Text())
|
||||
}
|
||||
}
|
||||
|
||||
var gzWriter *gzip.Writer
|
||||
if compression.Enabled {
|
||||
gzWriter, err = gzip.NewWriterLevel(&buf, compression.Level)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var tarWriter *tar.Writer
|
||||
if gzWriter != nil {
|
||||
tarWriter = tar.NewWriter(gzWriter)
|
||||
} else {
|
||||
tarWriter = tar.NewWriter(&buf)
|
||||
}
|
||||
|
||||
err = filepath.Walk(".", func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if path == "flux.json" || info.IsDir() || matchesIgnorePattern(path, info, ignoredFiles) {
|
||||
return nil
|
||||
}
|
||||
|
||||
header, err := tar.FileInfoHeader(info, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
header.Name = path
|
||||
|
||||
if err = tarWriter.WriteHeader(header); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !info.IsDir() {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
if _, err = io.Copy(tarWriter, file); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = tarWriter.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if gzWriter != nil {
|
||||
if err = gzWriter.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func arrayContains(arr []string, str string) bool {
|
||||
for _, a := range arr {
|
||||
if a == str {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func getProjectName(command string, args []string) (string, error) {
|
||||
var projectName string
|
||||
|
||||
if len(args) == 0 {
|
||||
if _, err := os.Stat("flux.json"); err != nil {
|
||||
return "", fmt.Errorf("usage: flux %[1]s <app name>, or 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]
|
||||
}
|
||||
|
||||
return projectName, nil
|
||||
}
|
||||
|
||||
type CustomSpinnerWriter struct {
|
||||
currentSpinnerMsg string
|
||||
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 (w *CustomStdout) Write(p []byte) (n int, err error) {
|
||||
w.lock.Lock()
|
||||
defer w.lock.Unlock()
|
||||
|
||||
n, err = os.Stdout.Write([]byte(fmt.Sprintf("\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))
|
||||
}
|
||||
|
||||
func DeployCommand(seekingHelp bool, config Config, info pkg.Info, loadingSpinner *spinner.Spinner, spinnerWriter *CustomSpinnerWriter, args []string) error {
|
||||
if seekingHelp {
|
||||
fmt.Println(`Usage:
|
||||
flux deploy
|
||||
|
||||
Flux will deploy the app in the current directory, and start routing traffic to it.`)
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, err := os.Stat("flux.json"); err != nil {
|
||||
return fmt.Errorf("no flux.json found, please run flux init first")
|
||||
}
|
||||
|
||||
loadingSpinner.Suffix = " Deploying"
|
||||
loadingSpinner.Start()
|
||||
|
||||
buf, err := compressDirectory(info.Compression)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to compress directory: %v", err)
|
||||
}
|
||||
|
||||
body := &bytes.Buffer{}
|
||||
writer := multipart.NewWriter(body)
|
||||
configPart, err := writer.CreateFormFile("config", "flux.json")
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create config part: %v", err)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
codePart, err := writer.CreateFormFile("code", "code.tar.gz")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create code part: %v", err)
|
||||
}
|
||||
|
||||
if _, err := codePart.Write(buf); err != nil {
|
||||
return fmt.Errorf("failed to write code part: %v", err)
|
||||
}
|
||||
|
||||
if err := writer.Close(); err != nil {
|
||||
return fmt.Errorf("failed to close writer: %v", err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", config.DeamonURL+"/deploy", body)
|
||||
req.Header.Set("Content-Type", writer.FormDataContentType())
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create request: %v", err)
|
||||
}
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to send request: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
customWriter := &CustomStdout{
|
||||
spinner: spinnerWriter,
|
||||
lock: sync.Mutex{},
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(resp.Body)
|
||||
var event string
|
||||
var data pkg.DeploymentEvent
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if strings.HasPrefix(line, "data: ") {
|
||||
if err := json.Unmarshal([]byte(line[6:]), &data); err != nil {
|
||||
return fmt.Errorf("failed to parse deployment event: %v", err)
|
||||
}
|
||||
|
||||
switch event {
|
||||
case "complete":
|
||||
loadingSpinner.Stop()
|
||||
var deploymentResponse struct {
|
||||
App pkg.App `json:"app"`
|
||||
}
|
||||
if err := json.Unmarshal([]byte(data.Message), &deploymentResponse); err != nil {
|
||||
return fmt.Errorf("failed to parse deployment response: %v", err)
|
||||
}
|
||||
fmt.Printf("App %s deployed successfully!\n", deploymentResponse.App.Name)
|
||||
return nil
|
||||
case "cmd_output":
|
||||
customWriter.Printf("... %s\n", data.Message)
|
||||
case "error":
|
||||
loadingSpinner.Stop()
|
||||
return fmt.Errorf("deployment failed: %s", data.Message)
|
||||
default:
|
||||
customWriter.Printf("%s\n", data.Message)
|
||||
}
|
||||
event = ""
|
||||
} else if strings.HasPrefix(line, "event: ") {
|
||||
event = strings.TrimPrefix(line, "event: ")
|
||||
}
|
||||
}
|
||||
|
||||
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("deploy failed: %s", responseBody)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func StopCommand(seekingHelp bool, config Config, info pkg.Info, loadingSpinner *spinner.Spinner, spinnerWriter *CustomSpinnerWriter, args []string) error {
|
||||
if seekingHelp {
|
||||
fmt.Println(`Usage:
|
||||
flux stop
|
||||
|
||||
Flux will stop the deployment of the app in the current directory.`)
|
||||
return nil
|
||||
}
|
||||
|
||||
projectName, err := getProjectName("stop", args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req, err := http.Post(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
|
||||
}
|
||||
|
||||
func StartCommand(seekingHelp bool, config Config, info pkg.Info, loadingSpinner *spinner.Spinner, spinnerWriter *CustomSpinnerWriter, args []string) error {
|
||||
if seekingHelp {
|
||||
fmt.Println(`Usage:
|
||||
flux start
|
||||
|
||||
Flux will start the deployment of the app in the current directory.`)
|
||||
return nil
|
||||
}
|
||||
|
||||
projectName, err := getProjectName("start", args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req, err := http.Post(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
|
||||
}
|
||||
|
||||
func DeleteCommand(seekingHelp bool, config Config, info pkg.Info, loadingSpinner *spinner.Spinner, spinnerWriter *CustomSpinnerWriter, args []string) error {
|
||||
if seekingHelp {
|
||||
fmt.Println(`Usage:
|
||||
flux delete [project-name | all]
|
||||
|
||||
Options:
|
||||
project-name: The name of the project to delete
|
||||
all: Delete all projects
|
||||
|
||||
Flux will delete the deployment of the app in the current directory or the specified project.`)
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(args) == 1 {
|
||||
if args[0] == "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...")
|
||||
return nil
|
||||
}
|
||||
|
||||
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...")
|
||||
return nil
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("DELETE", 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)
|
||||
}
|
||||
|
||||
fmt.Printf("Successfully deleted all projects\n")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
projectName, err := getProjectName("delete", args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 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)
|
||||
var response string
|
||||
fmt.Scanln(&response)
|
||||
|
||||
if strings.ToLower(response) != "y" {
|
||||
fmt.Println("Aborting...")
|
||||
return nil
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("DELETE", config.DeamonURL+"/deployments/"+projectName, nil)
|
||||
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)
|
||||
}
|
||||
|
||||
fmt.Printf("Successfully deleted %s\n", projectName)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ListCommand(seekingHelp bool, config Config, info pkg.Info, loadingSpinner *spinner.Spinner, spinnerWriter *CustomSpinnerWriter, args []string) error {
|
||||
if seekingHelp {
|
||||
fmt.Println(`Usage:
|
||||
flux list
|
||||
|
||||
Flux will list all the apps in the daemon.`)
|
||||
return nil
|
||||
}
|
||||
|
||||
resp, err := http.Get(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
|
||||
}
|
||||
|
||||
func InitCommand(seekingHelp bool, config Config, info pkg.Info, loadingSpinner *spinner.Spinner, spinnerWriter *CustomSpinnerWriter, args []string) error {
|
||||
if seekingHelp {
|
||||
fmt.Println(`Usage:
|
||||
flux init [project-name]
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
var projectConfig pkg.ProjectConfig
|
||||
|
||||
var response string
|
||||
if len(args) > 1 {
|
||||
response = args[0]
|
||||
} else {
|
||||
fmt.Println("What is the name of your project?")
|
||||
fmt.Scanln(&response)
|
||||
}
|
||||
|
||||
projectConfig.Name = response
|
||||
|
||||
fmt.Println("What URL should your project listen to?")
|
||||
fmt.Scanln(&response)
|
||||
if strings.HasPrefix(response, "http") {
|
||||
response = strings.TrimPrefix(response, "http://")
|
||||
response = strings.TrimPrefix(response, "https://")
|
||||
}
|
||||
|
||||
response = strings.Split(response, "/")[0]
|
||||
|
||||
projectConfig.Url = response
|
||||
|
||||
fmt.Println("What port does your project listen to?")
|
||||
fmt.Scanln(&response)
|
||||
port, err := strconv.ParseUint(response, 10, 16)
|
||||
projectConfig.Port = uint16(port)
|
||||
if err != nil || projectConfig.Port < 1 || projectConfig.Port > 65535 {
|
||||
return fmt.Errorf("that doesnt look like a valid port, try a number between 1 and 65535")
|
||||
}
|
||||
|
||||
configBytes, err := json.MarshalIndent(projectConfig, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse project config: %v", err)
|
||||
}
|
||||
|
||||
os.WriteFile("flux.json", configBytes, 0644)
|
||||
|
||||
fmt.Printf("Successfully initialized project %s\n", projectConfig.Name)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var helpStr = `Usage:
|
||||
flux <command>
|
||||
|
||||
@@ -656,16 +42,16 @@ Use "flux <command> --help" for more information about a command.`
|
||||
var maxDistance = 3
|
||||
|
||||
type CommandHandler struct {
|
||||
commands map[string]func(bool, Config, pkg.Info, *spinner.Spinner, *CustomSpinnerWriter, []string) error
|
||||
commands map[string]func(bool, models.Config, pkg.Info, *spinner.Spinner, *models.CustomSpinnerWriter, []string) error
|
||||
}
|
||||
|
||||
func (h *CommandHandler) RegisterCmd(name string, handler func(bool, Config, pkg.Info, *spinner.Spinner, *CustomSpinnerWriter, []string) error) {
|
||||
func (h *CommandHandler) RegisterCmd(name string, handler func(bool, models.Config, pkg.Info, *spinner.Spinner, *models.CustomSpinnerWriter, []string) error) {
|
||||
h.commands[name] = handler
|
||||
}
|
||||
|
||||
func runCommand(command string, args []string, config Config, info pkg.Info, cmdHandler CommandHandler, try int) error {
|
||||
func runCommand(command string, args []string, config models.Config, info pkg.Info, cmdHandler CommandHandler, try int) error {
|
||||
if try == 2 {
|
||||
return fmt.Errorf("Unknown command: %s", command)
|
||||
return fmt.Errorf("unknown command: %s", command)
|
||||
}
|
||||
|
||||
seekingHelp := false
|
||||
@@ -674,12 +60,9 @@ func runCommand(command string, args []string, config Config, info pkg.Info, cmd
|
||||
args = args[:len(args)-1]
|
||||
}
|
||||
|
||||
spinnerWriter := CustomSpinnerWriter{
|
||||
currentSpinnerMsg: "",
|
||||
lock: sync.Mutex{},
|
||||
}
|
||||
spinnerWriter := models.NewCustomSpinnerWriter()
|
||||
|
||||
loadingSpinner := spinner.New(spinner.CharSets[14], 100*time.Millisecond, spinner.WithWriter(&spinnerWriter))
|
||||
loadingSpinner := spinner.New(spinner.CharSets[14], 100*time.Millisecond, spinner.WithWriter(spinnerWriter))
|
||||
defer func() {
|
||||
if loadingSpinner.Active() {
|
||||
loadingSpinner.Stop()
|
||||
@@ -699,7 +82,7 @@ func runCommand(command string, args []string, config Config, info pkg.Info, cmd
|
||||
|
||||
handler, ok := cmdHandler.commands[command]
|
||||
if ok {
|
||||
return handler(seekingHelp, config, info, loadingSpinner, &spinnerWriter, args)
|
||||
return handler(seekingHelp, config, info, loadingSpinner, spinnerWriter, args)
|
||||
}
|
||||
|
||||
// diff the command against the list of commands and if we find a command that is more than 80% similar, ask if that's what the user meant
|
||||
@@ -758,7 +141,7 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
var config Config
|
||||
var config models.Config
|
||||
configBytes, err := os.ReadFile(filepath.Join(configPath, "config.json"))
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to read config file: %v\n", err)
|
||||
@@ -797,14 +180,14 @@ func main() {
|
||||
}
|
||||
|
||||
cmdHandler := CommandHandler{
|
||||
commands: make(map[string]func(bool, Config, pkg.Info, *spinner.Spinner, *CustomSpinnerWriter, []string) error),
|
||||
commands: make(map[string]func(bool, models.Config, pkg.Info, *spinner.Spinner, *models.CustomSpinnerWriter, []string) error),
|
||||
}
|
||||
|
||||
cmdHandler.RegisterCmd("deploy", DeployCommand)
|
||||
cmdHandler.RegisterCmd("stop", StopCommand)
|
||||
cmdHandler.RegisterCmd("start", StartCommand)
|
||||
cmdHandler.RegisterCmd("delete", DeleteCommand)
|
||||
cmdHandler.RegisterCmd("init", InitCommand)
|
||||
cmdHandler.RegisterCmd("deploy", handlers.DeployCommand)
|
||||
cmdHandler.RegisterCmd("stop", handlers.StopCommand)
|
||||
cmdHandler.RegisterCmd("start", handlers.StartCommand)
|
||||
cmdHandler.RegisterCmd("delete", handlers.DeleteCommand)
|
||||
cmdHandler.RegisterCmd("init", handlers.InitCommand)
|
||||
|
||||
err = runCommand(command, args, config, info, cmdHandler, 0)
|
||||
if err != nil {
|
||||
|
||||
5
cmd/flux/models/config.go
Normal file
5
cmd/flux/models/config.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package models
|
||||
|
||||
type Config struct {
|
||||
DeamonURL string `json:"deamon_url"`
|
||||
}
|
||||
69
cmd/flux/models/writers.go
Normal file
69
cmd/flux/models/writers.go
Normal file
@@ -0,0 +1,69 @@
|
||||
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{},
|
||||
}
|
||||
}
|
||||
|
||||
func (w *CustomStdout) Write(p []byte) (n int, err error) {
|
||||
w.lock.Lock()
|
||||
defer w.lock.Unlock()
|
||||
|
||||
n, err = os.Stdout.Write([]byte(fmt.Sprintf("\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,15 +1,16 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
_ "net/http/pprof"
|
||||
|
||||
"github.com/juls0730/flux/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)
|
||||
@@ -19,6 +20,9 @@ func main() {
|
||||
http.HandleFunc("GET /apps", fluxServer.ListAppsHandler)
|
||||
http.HandleFunc("GET /heartbeat", fluxServer.DaemonInfoHandler)
|
||||
|
||||
log.Printf("Fluxd started on http://127.0.0.1:5647\n")
|
||||
log.Fatal(http.ListenAndServe(":5647", nil))
|
||||
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