diff --git a/Makefile b/Makefile index 8f2f70b..7e8c800 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,5 @@ +VERSION := $(shell cat app-version) + dep: go mod download @@ -14,9 +16,11 @@ vet: go vet tag: + echo "Tagging version $(VERSION)" git tag -a v$(VERSION) -m "Release v$(VERSION)" git push origin v$(VERSION) + plan: cd tests && terraform plan -var="cluster_count=10" -var="service_count=10" -var="task_count=10" diff --git a/api/task.go b/api/task.go index 463d7c5..2aa2ee9 100644 --- a/api/task.go +++ b/api/task.go @@ -54,9 +54,13 @@ func (store *Store) ListTasks(clusterName, serviceName *string) ([]types.Task, e } // aws ecs register-task-definition --family ${{family}} --... -func (store *Store) RegisterTaskDefinition(family string) { - listTasksOutput, err := store.ecs.RegisterTaskDefinition(context.Background(), &ecs.RegisterTaskDefinitionInput{ - Family: &family, - }) - logger.Println(listTasksOutput, err) +// return registered task definition revision +func (store *Store) RegisterTaskDefinition(input *ecs.RegisterTaskDefinitionInput) (string, int32, error) { + registeredTdOutput, err := store.ecs.RegisterTaskDefinition(context.Background(), input) + if err != nil { + return "", 0, err + } + family := *registeredTdOutput.TaskDefinition.Family + revision := registeredTdOutput.TaskDefinition.Revision + return family, revision, nil } diff --git a/app-version b/app-version new file mode 100644 index 0000000..e6d5cb8 --- /dev/null +++ b/app-version @@ -0,0 +1 @@ +1.0.2 \ No newline at end of file diff --git a/sample/main.go b/sample/main.go deleted file mode 100644 index 0f342cb..0000000 --- a/sample/main.go +++ /dev/null @@ -1,83 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "os" - "os/exec" -) - -const filename = "data.json" - -type Data struct { - // Your JSON data structure here - ExampleField string `json:"example_field"` -} - -func main() { - // Load the JSON data from the file or create a new instance if the file doesn't exist. - var data Data - if _, err := os.Stat(filename); err == nil { - file, err := ioutil.ReadFile(filename) - if err != nil { - fmt.Println("Error reading file:", err) - os.Exit(1) - } - if err := json.Unmarshal(file, &data); err != nil { - fmt.Println("Error unmarshaling JSON:", err) - os.Exit(1) - } - } else { - data = Data{ - ExampleField: "Initial value", - } - } - - // Save the JSON data to a temporary file. - tmpfile, err := ioutil.TempFile("", "data.json") - if err != nil { - fmt.Println("Error creating temporary file:", err) - os.Exit(1) - } - defer os.Remove(tmpfile.Name()) - defer tmpfile.Close() - - jsonData, err := json.MarshalIndent(data, "", " ") - if err != nil { - fmt.Println("Error marshaling JSON:", err) - os.Exit(1) - } - - if _, err := tmpfile.Write(jsonData); err != nil { - fmt.Println("Error writing to temporary file:", err) - os.Exit(1) - } - - // Open the vi editor to allow the user to modify the JSON data. - editor := os.Getenv("EDITOR") - cmd := exec.Command(editor, tmpfile.Name()) - cmd.Stdin = os.Stdin - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - - if err := cmd.Run(); err != nil { - fmt.Println("Error opening editor:", err) - os.Exit(1) - } - - // Reload the JSON data after editing. - file, err := ioutil.ReadFile(tmpfile.Name()) - if err != nil { - fmt.Println("Error reading temporary file:", err) - os.Exit(1) - } - if err := json.Unmarshal(file, &data); err != nil { - fmt.Println("Error unmarshaling JSON:", err) - os.Exit(1) - } - - // You can now use the 'data' variable with the updated JSON data. - fmt.Println("Modified JSON Data:") - fmt.Println(data) -} diff --git a/ui/app.go b/ui/app.go index 0e0272c..77faeba 100644 --- a/ui/app.go +++ b/ui/app.go @@ -2,6 +2,7 @@ package ui import ( "flag" + "fmt" "github.com/aws/aws-sdk-go-v2/service/ecs/types" "github.com/gdamore/tcell/v2" @@ -19,6 +20,7 @@ const ( var ( logger = util.Logger readonly = false + version = false ) // Entity contains ECS resources to show @@ -47,6 +49,7 @@ type App struct { func init() { flag.BoolVar(&readonly, "readonly", false, "Enable readonly mode") + flag.BoolVar(&version, "version", false, "Print e1s version") } func newApp() *App { @@ -67,6 +70,10 @@ func newApp() *App { // Entry point of the app func Show() error { flag.Parse() + if version { + fmt.Println("v" + util.AppVersion) + return nil + } app := newApp() app.initStyles() diff --git a/ui/modal.go b/ui/modal.go index bdb7cce..419b513 100644 --- a/ui/modal.go +++ b/ui/modal.go @@ -11,6 +11,9 @@ import ( "github.com/rivo/tview" ) +// any form need at least one field +const placeholder = " (form placeholder) " + // Show update service modal and handle submit event func (v *View) showEditServiceModal() { if v.kind != ServicePage { @@ -35,7 +38,17 @@ func (v *View) showAutoScaling() { v.app.Pages.AddPage(title, v.modal(content, 100, 25), true, true) } -const placeholder = " (form placeholder) " +// Show task definition register confirm modal +func (v *View) showTaskDefinitionConfirm(fn func()) { + if v.kind != TaskPage { + return + } + content, title := v.taskDefinitionRegisterContent(fn) + if content == nil { + return + } + v.app.Pages.AddPage(title, v.modal(content, 100, 10), true, true) +} // Show service metrics modal(Memory/CPU) func (v *View) showMetrics() { @@ -49,6 +62,33 @@ func (v *View) showMetrics() { v.app.Pages.AddPage(title, v.modal(content, 100, 15), true, true) } +// Get task definition register content +func (v *View) taskDefinitionRegisterContent(fn func()) (*tview.Form, string) { + if v.kind != TaskPage { + return nil, "" + } + + readonly := "[-:-:-](readonly) " + title := " Register edited [purple::b]task definition?" + readonly + f := v.styledForm(title) + + // handle form close + f.AddButton("Cancel", func() { + v.closeModal() + }) + + // readonly mode has no submit button + if v.app.readonly { + return f, title + } + + // handle form submit + f.AddButton("Register", func() { + fn() + }) + return f, title +} + // Get service auto scaling form func (v *View) serviceAutoScalingContent() (*tview.Form, string) { if v.kind != ServicePage { diff --git a/ui/table.go b/ui/table.go index 82ca264..0298ae9 100644 --- a/ui/table.go +++ b/ui/table.go @@ -1,11 +1,14 @@ package ui import ( + "bytes" "encoding/json" + "fmt" "os" "os/exec" "strings" + "github.com/aws/aws-sdk-go-v2/service/ecs" "github.com/gdamore/tcell/v2" "github.com/keidarcy/e1s/util" "github.com/rivo/tview" @@ -206,8 +209,14 @@ func (v *View) editTaskDefinition() { defer os.Remove(tmpfile.Name()) defer tmpfile.Close() - jsonData, _ := json.MarshalIndent(td, "", " ") - if _, err := tmpfile.Write(jsonData); err != nil { + originalTD, err := json.MarshalIndent(td, "", " ") + if err != nil { + logger.Println("Error reading temporary file:", err) + v.errorModal(errMsg) + return + } + + if _, err := tmpfile.Write(originalTD); err != nil { logger.Println("Error writing to temporary file:", err) v.errorModal(errMsg) return @@ -230,21 +239,43 @@ func (v *View) editTaskDefinition() { return } - file, err := os.ReadFile(tmpfile.Name()) + editedTD, err := os.ReadFile(tmpfile.Name()) if err != nil { logger.Println("Error reading temporary file:", err) v.errorModal(errMsg) return } - logger.Println(string(file)) - if err := json.Unmarshal(file, &taskDefinition); err != nil { + // remove edited empty line + if editedTD[len(editedTD)-1] == '\n' { + originalTD = append(originalTD, '\n') + } + + // if no change do nothing + if bytes.Equal(originalTD, editedTD) { + v.flashModal(" no change", 2) + return + } + + var updatedTd ecs.RegisterTaskDefinitionInput + if err := json.Unmarshal(editedTD, &updatedTd); err != nil { logger.Println("Error unmarshaling JSON:", err) v.errorModal(errMsg) return } - logger.Println("Modified JSON Data:") - logger.Println(taskDefinition) + register := func() { + family, revision, err := v.app.Store.RegisterTaskDefinition(&updatedTd) + + if err != nil { + logger.Println("Error opening editor:", err) + v.errorModal(errMsg) + return + } + v.successModal(fmt.Sprintf("SUCCESS 🚀\nTaskDefinition Family: %s\nRevision: %d\n", family, revision)) + } + + v.showTaskDefinitionConfirm(register) + }) } diff --git a/ui/view.go b/ui/view.go index 0ff4afd..5177116 100644 --- a/ui/view.go +++ b/ui/view.go @@ -30,10 +30,10 @@ const ( showMetrics = "Show metrics" editService = "Edit Service" - editTaskDefinition = "Edit Task Definition(WIP)" + editTaskDefinition = "Edit Task Definition" openInBrowser = "Open in browser" sshContainer = "SSH container" - toggleFullScreen = "Toggle full screen" + toggleFullScreen = "JSON Toggle full screen" ) const ( diff --git a/util/util.go b/util/util.go index c2297b1..7e14a03 100644 --- a/util/util.go +++ b/util/util.go @@ -22,24 +22,35 @@ const ( clusterURLFmt = clusterFmt + regionFmt serviceURLFmt = clusterFmt + serviceFmt + regionFmt taskURLFmt = clusterFmt + serviceFmt + taskFmt + regionFmt - AppName = "e1s" - AppVersion = "1.0.2" + + versionFilename = "app-version" + AppName = "e1s" ) var ( - Logger *log.Logger + Logger *log.Logger + AppVersion string ) func init() { + // init basic logger logPath := fmt.Sprintf("/tmp/%s-debug.log", AppName) logFile, err := os.OpenFile(logPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) if err != nil { fmt.Printf("failed to open log file path: %s, err: %v\n", logPath, err) os.Exit(1) } - // defer logFile.Close() + // defer logFile.Close() Logger = log.New(logFile, "", log.LstdFlags) + + // read app-version + content, err := os.ReadFile(versionFilename) + if err != nil { + AppVersion = "N/A" + } + + AppVersion = string(content) } func ArnToName(arn *string) string {