Skip to content

Commit

Permalink
feat: add cli mode
Browse files Browse the repository at this point in the history
refactor code into ui package
  • Loading branch information
dhth committed Mar 31, 2024
1 parent eac895f commit 62010a9
Show file tree
Hide file tree
Showing 17 changed files with 243 additions and 122 deletions.
67 changes: 34 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,60 +36,61 @@ go install github.com/dhth/outtasync@latest
- `outtasync` doesn't change or override git's pager, so the diff will
follow your `.gitconfig` settings (if present).

⚡️ Usage
🛠️ Configuration
---

1. Create a configuration file that looks like the following.

```yaml
globalRefreshCommand: aws sso login --sso-session sessionname
profiles:
- name: qa
stacks:
- name: bingo-service-qa
local: ~/projects/bingo-service/cloudformation/infrastructure.yml
region: eu-central-1
refreshCommand: aws sso login --profile qa1
- name: papaya-service-qa
local: ~/projects/papaya-service/cloudformation/service.yml
region: eu-central-1
- name: racoon-service-qa
local: ~/projects/racoon-service/cloudformation/service.yml
region: eu-central-1
- name: prod
stacks:
- name: brb-dll-prod
local: ~/projects/brd-dll-service/cloudformation/service.yml
region: eu-central-1
refreshCommand: aws sso login --profile rgb-prod
- name: galactus-service-prod
local: ~/projects/galactus-service/cloudformation/service.yml
region: eu-central-1
```
Create a configuration file that looks like the following. By default,
`outtasync` will look for this file at `~/.config/outtasync.yml`.

```yaml
globalRefreshCommand: aws sso login --sso-session sessionname
profiles:
- name: qa
stacks:
- name: bingo-service-qa
local: ~/projects/bingo-service/cloudformation/infrastructure.yml
region: eu-central-1
refreshCommand: aws sso login --profile qa1
- name: papaya-service-qa
local: ~/projects/papaya-service/cloudformation/service.yml
region: eu-central-1
- name: racoon-service-qa
local: ~/projects/racoon-service/cloudformation/service.yml
region: eu-central-1
- name: prod
stacks:
- name: brb-dll-prod
local: ~/projects/brd-dll-service/cloudformation/service.yml
region: eu-central-1
refreshCommand: aws sso login --profile rgb-prod
- name: galactus-service-prod
local: ~/projects/galactus-service/cloudformation/service.yml
region: eu-central-1
```

`refreshCommand` overrides `globalRefreshCommand` whereever set.

*Note: The `globalRefreshCommand` and `refreshCommand` settings are only needed
if you want to invoke the command that refreshes your AWS credentials via the
TUI directly.*

2. Place this file at `~/.config/outtasync.yml` *(optional)*

3. Run the TUI as follows:
⚡️ Usage
---

```bash
outtasync
outtasync -config-file /path/to/config.yml
outtasync -profiles qa,prod
```

4. Press `?` to view keyboard shortcuts to use the TUI.
By default, `outtasync` runs in TUI mode. You can also run it in CLI mode (where
it outputs the results to stdout) using `-mode=cli` flag.

TODO
---

- [ ] Add a command to generate a sample config file
- [ ] Add CLI mode
- [x] Add CLI mode

Acknowledgements
---
Expand Down
10 changes: 5 additions & 5 deletions cmd/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"os/user"
"strings"

"github.com/dhth/outtasync/model"
"github.com/dhth/outtasync/ui"
"gopkg.in/yaml.v3"
)

Expand Down Expand Up @@ -34,7 +34,7 @@ func expandTilde(path string) string {
return path
}

func ReadConfig(configFilePath string, profilesToFetch []string) ([]model.Stack, error) {
func ReadConfig(configFilePath string, profilesToFetch []string) ([]ui.Stack, error) {
localFile, err := os.ReadFile(expandTilde(configFilePath))
if err != nil {
os.Exit(1)
Expand All @@ -50,7 +50,7 @@ func ReadConfig(configFilePath string, profilesToFetch []string) ([]model.Stack,
}

globalRefreshCmd := t.GlobalRefreshCommand
var rows []model.Stack
var rows []ui.Stack
for _, profile := range t.Profiles {
if len(profilesToFetch) > 0 && !profilesMap[profile.Name] {
continue
Expand All @@ -62,15 +62,15 @@ func ReadConfig(configFilePath string, profilesToFetch []string) ([]model.Stack,
} else {
refreshCmd = globalRefreshCmd
}
rows = append(rows, model.Stack{
rows = append(rows, ui.Stack{
Name: stack.Name,
AwsProfile: profile.Name,
AwsRegion: stack.Region,
Template: "",
Local: expandTilde(stack.Local),
Tag: stack.Tag,
RefreshCommand: refreshCmd,
FetchStatus: model.StatusUnfetched,
FetchStatus: ui.StatusUnfetched,
OuttaSync: false,
Err: nil,
})
Expand Down
35 changes: 34 additions & 1 deletion cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"flag"

"github.com/aws/aws-sdk-go-v2/service/cloudformation"
"github.com/dhth/outtasync/ui"
)

Expand All @@ -16,6 +17,10 @@ func die(msg string, args ...any) {
os.Exit(1)
}

var (
mode = flag.String("mode", "tui", "the mode to use; possible values: tui/cli")
)

func Execute() {
currentUser, err := user.Current()
var defaultConfigFilePath string
Expand All @@ -32,6 +37,10 @@ func Execute() {

flag.Parse()

if *mode == "" {
die("mode cannot be empty")
}

if *configFilePath == "" {
die("config-file cannot be empty")
}
Expand All @@ -53,6 +62,30 @@ func Execute() {
if len(stacks) == 0 {
die(cfgErrSuggestion(fmt.Sprintf("No stacks found for the requested parameters")))
}
ui.RenderUI(stacks)

awsCfgs := make(map[string]ui.AwsConfig)
cfClients := make(map[string]ui.AwsCFClient)

seen := make(map[string]bool)
for _, stack := range stacks {
configKey := ui.GetAWSConfigKey(stack)
if !seen[configKey] {
cfg, err := ui.GetAWSConfig(stack.AwsProfile, stack.AwsRegion)
awsCfgs[configKey] = ui.AwsConfig{Config: cfg, Err: err}
seen[configKey] = true
if err != nil {
cfClients[configKey] = ui.AwsCFClient{Err: err}
} else {
cfClients[configKey] = ui.AwsCFClient{Client: cloudformation.NewFromConfig(cfg)}
}
}
}

switch *mode {
case "tui":
ui.RenderUI(stacks, awsCfgs)
case "cli":
ui.ShowResults(stacks, awsCfgs)
}

}
54 changes: 0 additions & 54 deletions model/aws.go

This file was deleted.

54 changes: 54 additions & 0 deletions ui/aws.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package ui

import (
"context"
"os"

"github.com/aws/aws-sdk-go-v2/aws"

"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/cloudformation"
)

func GetAWSConfigKey(stack Stack) string {
return stack.AwsProfile + ":" + stack.AwsRegion
}

func GetAWSConfig(profile string, region string) (aws.Config, error) {
if profile == "default" {
cfg, err := config.LoadDefaultConfig(context.TODO(),
config.WithRegion(region))
return cfg, err
} else {
cfg, err := config.LoadDefaultConfig(context.TODO(),
config.WithRegion(region),
config.WithSharedConfigProfile(profile))
return cfg, err
}
}

func CheckStackSyncStatus(awsConfig AwsConfig, stack Stack) StackSyncResult {
if awsConfig.Err != nil {
return StackSyncResult{stack, "", false, awsConfig.Err}
}

svc := cloudformation.NewFromConfig(awsConfig.Config)

templateInput := cloudformation.GetTemplateInput{
StackName: &stack.Name,
}
templOut, err := svc.GetTemplate(context.TODO(), &templateInput)
if err != nil {
return StackSyncResult{stack, "", false, err}
}

templBody := *templOut.TemplateBody

localFile, err := os.ReadFile(stack.Local)
if err != nil {
return StackSyncResult{stack, "", false, err}
}
localFileContent := string(localFile)
outtaSync := localFileContent != templBody
return StackSyncResult{stack, templBody, outtaSync, err}
}
13 changes: 12 additions & 1 deletion model/cmds.go → ui/cmds.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package model
package ui

import (
"fmt"
Expand Down Expand Up @@ -51,3 +51,14 @@ func showFile(filePath string) tea.Cmd {
return tea.Msg(ShowFileFinished{})
})
}

func getCFTemplateBody(awsConfig AwsConfig, index int, stack Stack) tea.Cmd {
return func() tea.Msg {
stackSyncStatus := CheckStackSyncStatus(awsConfig, stack)

if awsConfig.Err != nil {
return TemplateFetchedMsg{index, stack, "", false, awsConfig.Err}
}
return TemplateFetchedMsg{index, stack, stackSyncStatus.TemplateBody, stackSyncStatus.Outtasync, stackSyncStatus.Err}
}
}
6 changes: 3 additions & 3 deletions model/delegate.go → ui/delegate.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package model
package ui

import (
"github.com/charmbracelet/bubbles/key"
Expand Down Expand Up @@ -38,8 +38,8 @@ func newAppItemDelegate(keys *delegateKeyMap) list.DefaultDelegate {

d.Styles.SelectedTitle = d.Styles.
SelectedTitle.
Foreground(lipgloss.Color("#fe8019")).
BorderLeftForeground(lipgloss.Color("#fe8019"))
Foreground(lipgloss.Color(StackListColor)).
BorderLeftForeground(lipgloss.Color(StackListColor))
d.Styles.SelectedDesc = d.Styles.
SelectedTitle.
Copy()
Expand Down
16 changes: 6 additions & 10 deletions model/initial.go → ui/initial.go
Original file line number Diff line number Diff line change
@@ -1,22 +1,15 @@
package model
package ui

import (
"github.com/charmbracelet/bubbles/list"
"github.com/charmbracelet/lipgloss"
)

func InitialModel(stacks []Stack) model {
func InitialModel(stacks []Stack, awsCfgs map[string]AwsConfig) model {
stackItems := make([]list.Item, 0, len(stacks))
awsCfgs := make(map[string]awsConfig)

seen := make(map[string]bool)
for _, stack := range stacks {
stackItems = append(stackItems, stack)
configKey := getAWSConfigKey(stack)
if !seen[configKey] {
cfg, err := getAWSConfig(stack.AwsProfile, stack.AwsRegion)
awsCfgs[configKey] = awsConfig{cfg, err}
seen[configKey] = true
}
}

var appDelegateKeys = newAppDelegateKeyMap()
Expand All @@ -29,6 +22,9 @@ func InitialModel(stacks []Stack) model {
m.stacksList.Title = "Stacks"
m.stacksList.SetStatusBarItemName("stack", "stacks")
m.stacksList.DisableQuitKeybindings()
m.stacksList.Styles.Title.Background(lipgloss.Color(StackListColor))
m.stacksList.Styles.Title.Foreground(lipgloss.Color(DefaultBackgroundColor))
m.stacksList.Styles.Title.Bold(true)

return m
}
Loading

0 comments on commit 62010a9

Please sign in to comment.