Skip to content

Commit

Permalink
Improve error handling and logging
Browse files Browse the repository at this point in the history
  • Loading branch information
David Cruz committed Sep 23, 2024
1 parent 893069a commit c7f9ea3
Show file tree
Hide file tree
Showing 10 changed files with 467 additions and 384 deletions.
12 changes: 7 additions & 5 deletions cmd/config/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
"github.com/spf13/viper"
)

// globalProtectCmd represents the globalProtect command
// configCmd represents the config command
var configCmd = &cobra.Command{
Use: "config",
Short: "A set of commands for working with the config",
Expand All @@ -22,7 +22,7 @@ var configCmd = &cobra.Command{
},
}

// listCmd represents the get command
// listCmd represents the list command
var listCmd = &cobra.Command{
Use: "list",
Short: "Print config file path",
Expand All @@ -32,15 +32,17 @@ var listCmd = &cobra.Command{
},
}

// showCmd represents the get command
// showCmd represents the show command
var showCmd = &cobra.Command{
Use: "show",
Short: "Print config file",
Long: ``,
Run: func(cmd *cobra.Command, args []string) {
log.Println()

file, err := os.Open(viper.ConfigFileUsed())
if err != nil {
panic(err)
log.Fatalf("%v\n\n", err)
}
defer file.Close()

Expand All @@ -52,7 +54,7 @@ var showCmd = &cobra.Command{
},
}

// editCmd represents the globalProtect command
// editCmd represents the edit command
var editCmd = &cobra.Command{
Use: "edit",
Short: "Open config file in default editor",
Expand Down
50 changes: 27 additions & 23 deletions cmd/firewall/get_config_set.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package firewall

import (
"bufio"
"bytes"
"fmt"
"log"
"os"
Expand Down Expand Up @@ -39,7 +40,9 @@ Examples:
> panos-cli firewall get config set --filter 'mgt-config' fw01.example.com`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Fprintln(os.Stderr)
log.Println()

// Ensure at least one host is specified
hosts = cmd.Flags().Args()
if len(hosts) == 0 {
if isInputFromPipe() {
Expand All @@ -50,8 +53,7 @@ Examples:
}
} else {
cmd.Help()
fmt.Printf("\nno hosts specified\n")
os.Exit(1)
log.Fatalf("\nno hosts specified\n\n")
}
}

Expand All @@ -65,8 +67,7 @@ Examples:
}
} else {
cmd.Help()
fmt.Printf("\nunable to retrieve password from standard input\n")
os.Exit(1)
log.Fatalf("\nunable to retrieve password from standard input\n\n")
}
}

Expand All @@ -81,48 +82,48 @@ Examples:
if !keyBasedAuth && viper.GetString("password") == "" && password == "" {
tty, err := os.Open("/dev/tty")
if err != nil {
log.Fatal(err, "error allocating terminal")
log.Fatalf("error allocating terminal\n\n")
}
fd := int(tty.Fd())
fmt.Fprintf(os.Stderr, "Password (%s): ", user)
bytepw, err := term.ReadPassword(int(fd))
if err != nil {
panic(err)
log.Fatalf("%v\n\n", err)
}
tty.Close()
password = string(bytepw)
fmt.Fprintf(os.Stderr, "\n\n")
log.Printf("\n\n")
} else if password == "" {
password = viper.GetString("password")
}

if keyBasedAuth {
file, err := os.ReadFile(filepath.Join(os.Getenv("HOME"), ".ssh", "id_rsa"))
if err != nil {
log.Fatal(err)
log.Fatalf("%v\n\n", err)
}

signer, err = ssh.ParsePrivateKey(file)
if err != nil {
if err.Error() == "ssh: this private key is passphrase protected" {
tty, err := os.Open("/dev/tty")
if err != nil {
log.Fatal(err, "error allocating terminal")
log.Fatalf("error allocating terminal: %v", err)
}
fd := int(tty.Fd())
fmt.Fprintf(os.Stderr, "SSH Private Key Passphrase: ")
passphrase, err := term.ReadPassword(fd)
if err != nil {
log.Fatal(err)
log.Fatalf("%v\n\n", err)
}
tty.Close()
fmt.Fprintf(os.Stderr, "\n\n")
log.Printf("\n\n")
signer, err = ssh.ParsePrivateKeyWithPassphrase(file, passphrase)
if err != nil {
log.Fatal(err)
log.Fatalf("%v\n\n", err)
}
} else {
log.Fatal(err)
log.Fatalf("%v\n\n", err)
}
}
}
Expand All @@ -136,10 +137,11 @@ Examples:

start := time.Now()

var errorBuffer bytes.Buffer
ch := make(chan sessionDetails, 100)
doneCh := make(chan struct{})

go printConfigSet(ch, doneCh)
go printConfigSet(ch, &errorBuffer, doneCh)

for _, host := range hosts {
wg.Add(1)
Expand All @@ -149,9 +151,12 @@ Examples:
close(ch)
<-doneCh

// Print errors
log.Println(errorBuffer.String())

// Print summary
elapsed := time.Since(start)
fmt.Fprintf(os.Stderr, " Completed in %.3f seconds\n", elapsed.Seconds())
log.Printf(" Completed in %.3f seconds\n", elapsed.Seconds())
},
}

Expand All @@ -169,18 +174,17 @@ func init() {
getConfigSetCmd.Flags().StringVarP(&configFilter, "filter", "f", configFilter, "filter configuration output")
}

// printConfigSet prints the results
func printConfigSet(ch <-chan sessionDetails, doneCh chan<- struct{}) {
for {
if session, chanIsOpen := <-ch; chanIsOpen {
func printConfigSet(ch <-chan sessionDetails, error *bytes.Buffer, done chan<- struct{}) {
for session := range ch {
if session.error != "" {
error.WriteString(session.error)
} else {
green.Printf("\n*** %s ***\n\n", session.host)
fmt.Printf("%s\n\n", trimConfigSetOutput(session.results[cmds[2]]))
blue.Printf("################################################################################\n\n")
} else {
doneCh <- struct{}{}
return
}
}
done <- struct{}{} // Notify when done
}

// trimOutput removes the echoed command and the prompt from the output
Expand Down
72 changes: 39 additions & 33 deletions cmd/firewall/get_config_xml.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package firewall

import (
"bufio"
"bytes"
"crypto/tls"
"encoding/base64"
"encoding/xml"
Expand Down Expand Up @@ -30,6 +31,7 @@ type innerXML struct {
type configuration struct {
Host string
Config innerXML `xml:"result"`
Error string
}

// getConfigXmlCmd represents the 'config xml' command
Expand All @@ -55,12 +57,14 @@ Examples:
> panos-cli firewall get config xml --type 'effective-running' --xpath 'mgt-config' fw01.example.com`,
Run: func(cmd *cobra.Command, args []string) {
log.Println()

// Ensure at least one host is specified
hosts = cmd.Flags().Args()
if len(hosts) == 0 {
if isInputFromPipe() {
if viper.GetString("apikey") == "" {
log.Fatal("api key based auth is required when reading hosts from stdin, execute `panos-cli config edit` to add an api key")
log.Fatalf("api key based auth is required when reading hosts from stdin, execute `panos-cli config edit` to add an api key\n\n")
}

// Read hosts from stdin
Expand All @@ -70,27 +74,23 @@ Examples:
}
} else {
cmd.Help()
fmt.Printf("\nno hosts specified\n")
os.Exit(1)
log.Fatalf("\nno hosts specified\n\n")
}
}

if !slices.Contains([]string{"candidate", "effective-running", "merged", "pushed-shared-policy", "pushed-template", "running", "synced", "synced-diff"}, configType) {
cmd.Help()
fmt.Printf("\ninvalid configuration type\n")
os.Exit(1)
log.Fatalf("\ninvalid configuration type\n\n")
}

if !slices.Contains([]string{"effective-running", "running"}, configType) && xpath != "." {
cmd.Help()
fmt.Printf("\nxpath should only be used with configuration types 'effective-running' and 'running'\n")
os.Exit(1)
log.Fatalf("\nxpath should only be used with configuration types 'effective-running' and 'running'\n\n")
}

// If the user flag is not set or the user is not set, prompt for user
fmt.Fprintln(os.Stderr)
if viper.GetString("user") == "" && user == "" {
fmt.Fprint(os.Stderr, "PAN User: ")
fmt.Fprintf(os.Stderr, "PAN User: ")
fmt.Scanln(&user)
} else if user == "" {
user = viper.GetString("user")
Expand All @@ -101,25 +101,26 @@ Examples:
if userFlagSet || (viper.GetString("apikey") == "" && password == "") {
tty, err := os.Open("/dev/tty")
if err != nil {
log.Fatal(err, "error allocating terminal")
log.Fatalf("error allocating terminal: %v\n\n", err)
}
fd := int(tty.Fd())
fmt.Fprintf(os.Stderr, "Password (%s): ", user)
bytepw, err := term.ReadPassword(int(fd))
if err != nil {
panic(err)
log.Fatalf("%v\n\n", err)
}
tty.Close()
password = string(bytepw)
fmt.Fprintf(os.Stderr, "\n\n")
log.Printf("\n\n")
}

start := time.Now()

var errorBuffer bytes.Buffer
ch := make(chan configuration, 10)
doneCh := make(chan struct{})

go printConfigXml(ch, doneCh)
go printConfigXml(ch, &errorBuffer, doneCh)

wg.Add(len(hosts))
for _, fw := range hosts {
Expand All @@ -128,11 +129,13 @@ Examples:
wg.Wait()
close(ch)
<-doneCh
fmt.Fprintln(os.Stderr)

// Print errors
log.Println(errorBuffer.String())

// Print summary
elapsed := time.Since(start)
fmt.Fprintf(os.Stderr, " Completed in %.3f seconds\n", elapsed.Seconds())
log.Printf(" Completed in %.3f seconds\n", elapsed.Seconds())
},
}

Expand All @@ -148,6 +151,11 @@ func init() {
func getConfigXml(ch chan<- configuration, fw string, userFlagSet bool, xpath string) {
defer wg.Done()

config := configuration{Host: fw}
defer func() {
ch <- config
}()

var cmd string
if slices.Contains([]string{"effective-running", "running"}, configType) {
cmd = fmt.Sprintf("<show><config><%s><xpath>%s</xpath></%s></config></show>", configType, xpath, configType)
Expand All @@ -163,8 +171,8 @@ func getConfigXml(ch chan<- configuration, fw string, userFlagSet bool, xpath st
url := fmt.Sprintf("https://%s/api/", fw)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
red.Fprintf(os.Stderr, "fail\n\n")
panic(err)
config.Error = fmt.Sprintf("%s: %v\n\n", fw, err)
return
}

// Get configuration
Expand All @@ -183,40 +191,38 @@ func getConfigXml(ch chan<- configuration, fw string, userFlagSet bool, xpath st

resp, err := client.Do(req)
if err != nil {
red.Fprintf(os.Stderr, "fail\n\n")
panic(err)
config.Error = fmt.Sprintf("%s: %v\n\n", fw, err)
return
}
if resp.StatusCode != 200 {
red.Fprintf(os.Stderr, "fail\n\n")
log.Fatalf("%s (%s)", resp.Status, fw)
config.Error = fmt.Sprintf("%s: response status code: %s\n\n", fw, resp.Status)
return
}

respBody, err := io.ReadAll(resp.Body)
if err != nil {
red.Fprintf(os.Stderr, "fail\n\n")
panic(err)
config.Error = fmt.Sprintf("%s: %v\n\n", fw, err)
return
}

resp.Body.Close()

config := configuration{Host: fw}
err = xml.Unmarshal(respBody, &config)
if err != nil {
panic(err)
config.Error = fmt.Sprintf("%s: %v\n\n", fw, err)
return
}

ch <- config
}

func printConfigXml(ch <-chan configuration, doneCh chan<- struct{}) {
for {
if session, chanIsOpen := <-ch; chanIsOpen {
func printConfigXml(ch <-chan configuration, error *bytes.Buffer, done chan<- struct{}) {
for session := range ch {
if session.Error != "" {
error.WriteString(session.Error)
} else {
green.Printf("\n*** %s ***\n\n", session.Host)
fmt.Printf("%s\n\n", session.Config.InnerXML)
blue.Printf("################################################################################\n\n")
} else {
doneCh <- struct{}{}
return
}
}
done <- struct{}{} // Notify when done
}
Loading

0 comments on commit c7f9ea3

Please sign in to comment.