good progress

This commit is contained in:
jms 2020-10-24 23:12:10 -05:00
parent ac3e3a846e
commit 048940ef7a
14 changed files with 535 additions and 54 deletions

View File

@ -46,7 +46,6 @@ to quickly create a Cobra application.`,
os.Exit(1) os.Exit(1)
} }
}, },
} }
@ -64,7 +63,6 @@ func init() {
// addCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") // addCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
} }
const focusedTextColor = "205" const focusedTextColor = "205"
var ( var (
@ -75,7 +73,6 @@ var (
blurredSubmitButton = "[ " + te.String("Submit").Foreground(color("240")).String() + " ]" blurredSubmitButton = "[ " + te.String("Submit").Foreground(color("240")).String() + " ]"
) )
// Will ask a user for a unique name until they exit or provide one. // Will ask a user for a unique name until they exit or provide one.
func cfgRename(c data.HostDetails) { func cfgRename(c data.HostDetails) {
p := tea.NewProgram(renameModel(c)) p := tea.NewProgram(renameModel(c))
@ -109,15 +106,14 @@ func toFile(fp string, c data.HostDetails) {
tea.Quit() tea.Quit()
} }
type model struct { type model struct {
index int index int
dbFileName input.Model dbFileName input.Model
hostName input.Model hostName input.Model
dbName input.Model dbName input.Model
userName input.Model userName input.Model
secret input.Model secret input.Model
submitButton string submitButton string
} }
func initialModel() model { func initialModel() model {
@ -143,13 +139,10 @@ func initialModel() model {
secret.Placeholder = "secret" secret.Placeholder = "secret"
secret.Prompt = blurredPrompt secret.Prompt = blurredPrompt
return model{0, dbFileName, hostName, dbName, userName, secret, blurredSubmitButton} return model{0, dbFileName, hostName, dbName, userName, secret, blurredSubmitButton}
} }
func (m model) Init() tea.Cmd { func (m model) Init() tea.Cmd {
return tea.Batch( return tea.Batch(
input.Blink(m.dbFileName), input.Blink(m.dbFileName),
@ -160,7 +153,6 @@ func (m model) Init() tea.Cmd {
) )
} }
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmd tea.Cmd var cmd tea.Cmd
@ -224,7 +216,6 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.userName = inputs[3] m.userName = inputs[3]
m.secret = inputs[4] m.secret = inputs[4]
if m.index == len(inputs) { if m.index == len(inputs) {
m.submitButton = focusedSubmitButton m.submitButton = focusedSubmitButton
} else { } else {
@ -240,7 +231,6 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, cmd return m, cmd
} }
func updateInputs(msg tea.Msg, m model) (model, tea.Cmd) { func updateInputs(msg tea.Msg, m model) (model, tea.Cmd) {
var ( var (
cmd tea.Cmd cmd tea.Cmd
@ -261,11 +251,9 @@ func updateInputs(msg tea.Msg, m model) (model, tea.Cmd) {
m.secret, cmd = input.Update(msg, m.secret) m.secret, cmd = input.Update(msg, m.secret)
return m, tea.Batch(cmds...) return m, tea.Batch(cmds...)
} }
func (m model) View() string { func (m model) View() string {
s := "\n" s := "\n"
@ -294,7 +282,7 @@ func configurator(t []input.Model) {
var cfgFileName string var cfgFileName string
var fp string var fp string
var homeDir string var homeDir string
c = data.HostDetails{t[4].Value(), t[1].Value(), t[2].Value(), t[3].Value()} c = data.HostDetails{t[4].Value(), t[1].Value(), t[2].Value(), t[3].Value()}
cfgFileName = t[0].Value() cfgFileName = t[0].Value()
homeDir, err := os.UserHomeDir() homeDir, err := os.UserHomeDir()
if err != nil { if err != nil {
@ -311,7 +299,6 @@ func configurator(t []input.Model) {
} }
toFile(fp, c) toFile(fp, c)
} }
func renameModel(c data.HostDetails) rename { func renameModel(c data.HostDetails) rename {
@ -363,7 +350,6 @@ func (m rename) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
} }
m.textInput, cmd = input.Update(msg, m.textInput) m.textInput, cmd = input.Update(msg, m.textInput)
return m, cmd return m, cmd
} }
@ -396,4 +382,4 @@ func Frame() tea.Cmd {
return tea.Tick(time.Second/60, func(time.Time) tea.Msg { return tea.Tick(time.Second/60, func(time.Time) tea.Msg {
return FrameMsg{} return FrameMsg{}
}) })
} }

View File

@ -17,9 +17,10 @@ package cmd
import ( import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"pgee/box"
"pgee/utils"
"pgm/data" "pgm/data"
"pgm/loader"
"pgm/logger"
"os"
) )
// billyCmd represents the billy command // billyCmd represents the billy command
@ -35,15 +36,18 @@ to quickly create a Cobra application.`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
var dbcfg string var dbcfg string
var title string var title string
var hosts []string var pgHosts []string
hosts = data.ReadHosts() pgHosts = data.ReadHosts()
title = "choose db to load, \"l\" or enter will load your choice" title = "choose db to load, \"l\" or enter will load your choice"
box.Box(pgee, title, dbcfg) err := loader.Loader(pgHosts, title, dbcfg)
if err != nil {
logger.Logger("[ERROR] loading error for testing, ignore.")
}
os.Exit(0)
}, },
} }
func init() { func init() {
rootCmd.AddCommand(loadCmd) rootCmd.AddCommand(loadCmd)
@ -57,4 +61,3 @@ func init() {
// is called directly, e.g.: // is called directly, e.g.:
// billyCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") // billyCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
} }

View File

@ -21,6 +21,7 @@ import (
"log" "log"
"os" "os"
"pgm/config" "pgm/config"
"pgm/logger"
homedir "github.com/mitchellh/go-homedir" homedir "github.com/mitchellh/go-homedir"
"github.com/spf13/viper" "github.com/spf13/viper"
@ -64,6 +65,7 @@ func init() {
// Cobra also supports local flags, which will only run // Cobra also supports local flags, which will only run
// when this action is called directly. // when this action is called directly.
rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
viper.BindPFlag("temp_db", rootCmd.PersistentFlags().Lookup("temp_db"))
} }
// initConfig reads in config file and ENV variables if set. // initConfig reads in config file and ENV variables if set.
@ -78,7 +80,6 @@ func InitConfig() {
fmt.Println(err) fmt.Println(err)
os.Exit(1) os.Exit(1)
} }
os.Setenv("PGMHOME", home)
// Search config in home directory with name ".pgm" (without extension). // Search config in home directory with name ".pgm" (without extension).
viper.AddConfigPath(home) viper.AddConfigPath(home)
viper.SetConfigName(".pgm") viper.SetConfigName(".pgm")
@ -100,7 +101,6 @@ func InitConfig() {
// If a config file is found, read it in. // If a config file is found, read it in.
if err := viper.ReadInConfig(); err == nil { if err := viper.ReadInConfig(); err == nil {
fmt.Println("Using config file:", viper.ConfigFileUsed()) logger.Logger("[LOG] Using config file:"+ viper.ConfigFileUsed())
} }
} }

View File

@ -22,19 +22,19 @@ func IsInit(h string, s string, l string, cfgFile string) bool {
} }
// if there is no $HOME/.pgm dir // if there is no $HOME/.pgm dir
if _, err := os.Stat(homeDirectory + "/.pgm"); os.IsNotExist(err) { if _, err := os.Stat(homeDirectory + "/.pgm"); os.IsNotExist(err) {
err = os.Mkdir(homeDirectory + "/.pgm/", 0755) err = os.Mkdir(homeDirectory+"/.pgm/", 0755)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
} }
// creates logs, src, and hosts directories. // creates logs, src, and hosts directories.
for _, f := range dirs { for _, f := range dirs {
if len(f) >0 { if len(f) > 0 {
err := os.MkdirAll(homeDirectory + "/" + f, 0755) err := os.MkdirAll(homeDirectory+"/"+f, 0755)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
isCfg = append(isCfg, true) isCfg = append(isCfg, true)
} }
} }
@ -44,23 +44,20 @@ func IsInit(h string, s string, l string, cfgFile string) bool {
} }
} }
return true return true
} }
// determines if key exists in config file // determines if key exists in config file
func cfgExists( s string, d []string, cfgFile string) []string { func cfgExists(s string, d []string, cfgFile string) []string {
var dir []string var dir []string
var cfgPath string var cfgPath string
dir = strings.Split(s, "/") dir = strings.Split(s, "/")
cfgPath = dir[len(dir)-1] cfgPath = dir[len(dir)-1]
configDirectoryKey := parseCfg(cfgPath) configDirectoryKey := parseCfg(cfgPath)
if err := viper.ReadInConfig(); err == nil { if err := viper.ReadInConfig(); err == nil {
viper.SetConfigFile(cfgFile) viper.SetConfigFile(cfgFile)
} }
if viper.GetString(configDirectoryKey)!=""{ if viper.GetString(configDirectoryKey) != "" {
a := viper.GetString(configDirectoryKey) a := viper.GetString(configDirectoryKey)
d = append(d, a) d = append(d, a)
} else { } else {
@ -70,12 +67,12 @@ func cfgExists( s string, d []string, cfgFile string) []string {
} }
// neatly return a string formatted to match the config file // neatly return a string formatted to match the config file
func parseCfg (s string) string { func parseCfg(s string) string {
var directory string var directory string
var isTest string var isTest string
// if we have test directories these are configured already in .pgm.yaml // if we have test directories these are configured already in .pgm.yaml
isTest = s[:3] isTest = s[:3]
if isTest == "test"{ if isTest == "test" {
return s return s
} }
directory = "%s_dir" directory = "%s_dir"
@ -105,4 +102,4 @@ func ReadConfig() (*viper.Viper, error) {
return v, err return v, err
} }
return v, nil return v, nil
} }

View File

@ -2,6 +2,7 @@ package data
import ( import (
"fmt" "fmt"
"github.com/spf13/viper"
"log" "log"
"os" "os"
"pgm/logger" "pgm/logger"
@ -23,7 +24,7 @@ func ReadHosts() []string {
log.Fatal(err) log.Fatal(err)
} }
file, err := os.Open(homeDir +"/.pgm/hosts") file, err := os.Open(homeDir + "/.pgm/hosts")
if err != nil { if err != nil {
logged := logger.Logger("failed opening directory: %s" + err.Error()) logged := logger.Logger("failed opening directory: %s" + err.Error())
if logged == true { if logged == true {
@ -40,3 +41,26 @@ func ReadHosts() []string {
return pgee return pgee
} }
// returns a value from the .pgm.yaml file
func ViperReturnKey(k string ) string {
var h string
var s string
h, err := os.UserHomeDir()
if err != nil {
logger.Logger("[ERROR] could not set home directory: " + err.Error())
os.Exit(0)
}
viper.SetConfigName("scr")
viper.SetConfigType("json")
viper.AddConfigPath(h + "/.pgm/scn/")
err = viper.ReadInConfig() // Find and read the config file
if err != nil { // Handle errors reading the config file
logger.Logger("[ERROR] failed to load viper config: " + err.Error())
fmt.Println("error occurred and was logged.")
os.Exit(1)
}
s = viper.GetString(k)
logger.Logger("[LOG] got value from .pgm.yaml: " + s )
return s
}

55
db/db.go Normal file
View File

@ -0,0 +1,55 @@
package db
import (
"context"
"database/sql"
"fmt"
_ "github.com/lib/pq"
"os"
"pgm/data"
"pgm/logger"
)
func DbConn(c data.HostDetails) *sql.DB {
var h string
var u string
var pw string
var dbn string
var psqlInfo string
h = c.Hostname
u = c.Username
pw = c.Secret
dbn = c.DatabaseName
psqlInfo = fmt.Sprintf("host=%s port=%d user=%s "+
"password=%s dbname=%s sslmode=disable",
h, 5432, u, pw, dbn)
db, err := sql.Open("postgres", psqlInfo)
testUserName := data.ViperReturnKey("test_user")
logger.Logger("[LOG] reserved username is " + testUserName)
if err != nil {
switch u {
case testUserName:
logger.Logger("[ERROR] go tests expects to connect with the docker compose database." +
"[ERROR] run \"docker-compose up\" and re-run tests")
os.Exit(1)
default:
logger.Logger("[ERROR] db error: " + err.Error())
}
}
ctx := context.Background()
err = db.PingContext(ctx)
if err != nil {
switch u {
case testUserName:
logger.Logger("[ERROR] db error, unable to ping: " + err.Error()+
"[ERROR] ensure docker database is running and re-run tests")
os.Exit(1)
default:
logger.Logger("[ERROR] db error: " + err.Error())
}
}
return db
}

18
docker-compose.yml Normal file
View File

@ -0,0 +1,18 @@
version: '3.7'
services:
pg-db:
image: postgres:11.5
container_name: pg-db
expose:
- "5432"
ports:
- "5432:5432"
environment:
- POSTGRES_DB=localdb
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
volumes:
- ${PWD}/sql/localDB.sql:/docker-entrypoint-initdb.d/localdb.sql

3
go.mod
View File

@ -6,9 +6,10 @@ require (
github.com/charmbracelet/bubbles v0.7.1 github.com/charmbracelet/bubbles v0.7.1
github.com/charmbracelet/bubbletea v0.12.1 github.com/charmbracelet/bubbletea v0.12.1
github.com/fsnotify/fsnotify v1.4.9 // indirect github.com/fsnotify/fsnotify v1.4.9 // indirect
github.com/lib/pq v1.8.0
github.com/magiconair/properties v1.8.4 // indirect github.com/magiconair/properties v1.8.4 // indirect
github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/go-homedir v1.1.0
github.com/mitchellh/mapstructure v1.3.3 // indirect github.com/mitchellh/mapstructure v1.3.3
github.com/muesli/termenv v0.7.4 github.com/muesli/termenv v0.7.4
github.com/pelletier/go-toml v1.8.1 // indirect github.com/pelletier/go-toml v1.8.1 // indirect
github.com/spf13/afero v1.4.1 // indirect github.com/spf13/afero v1.4.1 // indirect

1
go.sum
View File

@ -114,6 +114,7 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac= github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac=
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=

282
loader/loader.go Normal file
View File

@ -0,0 +1,282 @@
package loader
import (
"database/sql"
"encoding/json"
"fmt"
tea "github.com/charmbracelet/bubbletea"
"github.com/mitchellh/mapstructure"
"github.com/muesli/termenv"
"github.com/spf13/viper"
"io/ioutil"
"os"
"pgm/config"
"pgm/data"
"pgm/db"
"pgm/logger"
"pgm/ui"
"strconv"
"strings"
"time"
)
type host struct {
tic int
t string
choices []string
choice int
chosen bool
Quitting bool
Frames int
File string
isTest bool
}
var (
term = termenv.ColorProfile()
subtle = makeFgStyle("241")
dot = colorFg(" • ", "236")
)
func Loader(t []string, s string, b string) error {
var hostChoice host
var isT bool
isT = setTest(s)
logger.Logger("[LOG] is loader test: " + strconv.FormatBool(hostChoice.isTest))
hostChoice = host{60, s, t, 0, false, false, 0, b, isT}
if err := tea.NewProgram(hostChoice).Start(); err != nil {
if isT == true {
return nil
}
logger.Logger("[ERROR] tea error occured")
os.Exit(1)
}
return nil
}
// method to enable go tests if set to true
// loader function will test that it can load the ui to chose a database
// and quits the ui
func setTest(s string) bool {
logger.Logger("[LOG] is test: " + s)
if s == "test" {
return true
}
return false
}
func (m host) Init() tea.Cmd {
return tick()
}
func choicesUpdate(msg tea.Msg, m host) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "q", "esc":
m.Quitting = true
return m, tea.Quit
case "j", "down":
m.choice += 1
if m.choice > len(m.choices) {
m.choice = len(m.choices)
}
case "k", "up":
m.choice -= 1
if m.choice < 0 {
m.choice = 0
}
case "l", "enter":
m.File = m.choices[m.choice]
m.chosen = true
return m, Frame()
}
case tickMsg:
if m.isTest == true {
m.File = "test"
m.Quitting = true
return m, tea.Quit
}
if m.tic == 0 {
m.Quitting = true
return m, tea.Quit
}
m.tic -= 1
return m, tick()
}
return m, nil
}
// Update loop for the second view after a choice has been made
func (m host) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if msg, ok := msg.(tea.KeyMsg); ok {
k := msg.String()
if k == "q" || k == "esc" || k == "ctrl+c" {
m.Quitting = true
return m, tea.Quit
}
}
if !m.chosen {
return choicesUpdate(msg, m)
}
return m, tea.Quit
}
// Views return a string based on data in the host. That string which will be
// rendered to the terminal.
func (m host) View() string {
logger.Logger("[LOG] view initialized")
var s string
if m.Quitting {
logger.Logger("[LOG] leaving pgm")
return "leaving pgm..."
}
if !m.chosen {
s = choicesView(m)
} else {
tea.Quit()
LoadUi(m)
}
return s
}
// Passes our config to the screen file.
// screen is tasked with rendering more in depth views.
func LoadUi(m host) {
var g data.HostDetails
var h string
h, err := os.UserHomeDir()
if err != nil {
logger.Logger("[ERROR] uable to set home dir: " + err.Error())
os.Exit(0)
}
config.ReadConfig()
l := viper.GetString("hosts_dir")
filepath := h + "/" + l + "/" + m.File
loadhost(filepath, &g)
Screener(g)
}
// Creates the menu for choosing a file from your configs.
// Exits after 60 seconds.
// Gets Title from outside in the load method if this... should ever be needed elsewhere.
func choicesView(m host) string {
var menu string
c := m.choice
tpl := m.t + "\n\n"
tpl += "%s\n\n"
tpl += "menu exits in %s seconds\n\n"
tpl += subtle("j/k, up/down: select") + dot + subtle("enter: choose") + dot + subtle("q, esc: quit")
for i, s := range m.choices {
menu = menu + "::" + checkbox(s, c == i)
}
mn := strings.Replace(menu, "::", "\n", -1)
choices := fmt.Sprintf(
mn,
)
return fmt.Sprintf(tpl, choices, colorFg(strconv.Itoa(m.tic), "79"))
}
// the rest of these functions are to do with looks and aesthetics
// returns a neat checkbox
func checkbox(label string, checked bool) string {
if checked {
return colorFg("[x] "+label, "212")
}
return fmt.Sprintf("[ ] %s", label)
}
// fun colors
func colorFg(val, color string) string {
return termenv.String(val).Foreground(term.Color(color)).String()
}
// Return a function that will colorize the foreground of a given string.
func makeFgStyle(color string) func(string) string {
return termenv.Style{}.Foreground(term.Color(color)).Styled
}
// Frame event
func Frame() tea.Cmd {
return tea.Tick(time.Second/60, func(time.Time) tea.Msg {
return FrameMsg{}
})
}
type tickMsg struct{}
type FrameMsg struct{}
// exits if no choice is selected in a minute
func tick() tea.Cmd {
return tea.Tick(time.Second, func(time.Time) tea.Msg {
return tickMsg{}
})
}
// loads a yaml file into the correct format for consumption.
// errs the pipeline step if there is not a configuration in this repository.
func loadhost(c string, p *data.HostDetails) *data.HostDetails {
logger.Logger("[LOG] file path string is: " + c)
cfg, err := ioutil.ReadFile(c)
if err != nil {
logger.Logger("[ERROR] unable to find this filepath: " + err.Error())
os.Exit(1)
}
json.Unmarshal(cfg, &p)
return p
}
func Screener(g data.HostDetails) error {
var scn Scr
var dbConn *sql.DB
logger.Logger("[LOG] starting screen")
s := data.ViperReturnKey("UserSessions")
mapstructure.Decode(s, &scn)
dbConn = db.DbConn(g)
switch g.Username {
case "test":
dbConn.Close()
return nil
}
ui.GraphicUi(dbConn)
return nil
}
// Simple struct for a view in the UI.
type Scr struct {
Query string `json:"query"`
Columns int `json:"columns"`
UIType string `json:"uiType"`
Title string `json:"title"`
Quitting bool
}
func (m Scr) Init() tea.Cmd {
return tick()
}
func (m Scr) View() string {
return m.Query
}
func (m Scr) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if msg, ok := msg.(tea.KeyMsg); ok {
k := msg.String()
if k == "q" || k == "esc" || k == "ctrl+c" {
m.Quitting = true
return m, tea.Quit
}
}
return m, nil
}

View File

@ -7,7 +7,10 @@ import (
func Logger(s string) bool { func Logger(s string) bool {
var h string var h string
h = os.Getenv("PGMHOME") h, err := os.UserHomeDir()
if err != nil {
log.Fatal(err)
}
if s == "test" { if s == "test" {
os.Create("/var/tmp/PGMLOGTEST") os.Create("/var/tmp/PGMLOGTEST")
file, err := os.OpenFile("/var/tmp/PGMLOGTEST", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666) file, err := os.OpenFile("/var/tmp/PGMLOGTEST", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)

View File

@ -1,10 +1,13 @@
package main_test package main_test
import ( import (
"encoding/json"
"fmt" "fmt"
"github.com/spf13/viper" "github.com/spf13/viper"
"os" "os"
"pgm/config" "pgm/config"
"pgm/data"
"pgm/loader"
"pgm/logger" "pgm/logger"
"testing" "testing"
) )
@ -14,7 +17,7 @@ import (
func TestInit(t *testing.T) { func TestInit(t *testing.T) {
t.Parallel() t.Parallel()
var cfg string var cfg string
testCases := []initTests{ testCases := []initTest{
{a: "junk", b: "notreal", c: "failingonpurpose", want: true}, {a: "junk", b: "notreal", c: "failingonpurpose", want: true},
{a: "test_host_dir", b: "test_screen_dr", c: "test_log_dir", want: true}, {a: "test_host_dir", b: "test_screen_dr", c: "test_log_dir", want: true},
} }
@ -57,7 +60,70 @@ func TestLoadConfig(t *testing.T) {
} }
type initTests struct { // loader function will test that it can load the ui to chose a database
// and quits the ui.
// "test" is added into the array of files a user has configured and selected
func TestLoader(t *testing.T) {
t.Parallel()
var tc []string
var s string
var b string
var want error = nil
s = "test"
b = "test"
tc = data.ReadHosts()
tc = append(tc, "test")
got := loader.Loader(tc, s, b)
if want != got {
t.Errorf("want %t, got %t", want, got)
}
}
func TestScreener(t *testing.T){
t.Parallel()
var p data.HostDetails
var testDb string
var want error = nil
var h string
h, err := os.UserHomeDir()
if err != nil {
logger.Logger("[ERROR] could not set home directory: " + err.Error())
os.Exit(0)
}
viper.SetConfigName(".pgm") // config file name without extension
viper.SetConfigType("yaml")
viper.AddConfigPath(h)
viper.AutomaticEnv() // read value ENV variable
err = viper.ReadInConfig()
if err != nil {
logger.Logger("[ERROR] fatal error config file: " + err.Error())
os.Exit(1)
}
testDb = viper.GetString("test_db")
err = json.Unmarshal([]byte(testDb), &p)
if err != nil {
logger.Logger("[LOG] test screener failed: " + err.Error())
os.Exit(1)
}
got := loader.Screener(p)
if want != got {
t.Errorf("want %t, got %t", want, got)
}
}
type viperHostTest struct {
a string
want bool
}
type initTest struct {
a, b, c string a, b, c string
want bool want bool
} }

21
sql/localDB.sql Normal file
View File

@ -0,0 +1,21 @@
create schema test;
create table test.employees (
Id SERIAL NOT NULL PRIMARY KEY,
Name VARCHAR(50),
Location VARCHAR(50)
);
create table test.company (
cid SERIAL NOT NULL PRIMARY KEY,
Name VARCHAR(50)
);
create table test.employer (
eid SERIAL NOT NULL PRIMARY KEY,
Id INT,
cid INT
);
ALTER TABLE test.employer add constraint emp_fk foreign key(Id) REFERENCES test.employees(id) ON UPDATE CASCADE ON DELETE CASCADE;
insert into test.employees (name,location) VALUES ('bob','kalamazoo');
create role test with login password 'test';
insert into test.employees (name,location) VALUES ('cheeks','magoo');
update test.employees set location = 'buttsville' where name = 'cheeks';
grant all privileges on schema test to test;

24
ui/ui.go Normal file
View File

@ -0,0 +1,24 @@
package ui
import (
tea "github.com/charmbracelet/bubbletea"
"database/sql"
"pgm/logger"
"fmt"
"os"
)
func GraphicUi(db *sql.DB, ) {
row, err := db.Query("Select name from test.employees")
if err != nil {
logger.Logger("[ERROR] database error occured: " + err.Error())
os.Exit(0)
}
defer row.Close()
for row.Next() {
fmt.Println(row)
}
tea.Quit()
}