URI in config file (see comment in program) should have format “user:password@tcp(xx.xx.xx.xx:3306)/databasename”

package main

// requires config file with format
//[master]
//URI=....
//
//[slave_hostname_1]
//URI=....
//[slave_hostname_2]
//URI=...
//

import (
	"database/sql"
	"flag"
	"fmt"
	"log"
	"os"
	"time"

	"github.com/go-ini/ini"
	_ "github.com/go-sql-driver/mysql"
)

var (
	printDebugMsg = true
)

type data struct {
	variableName  string
	variableValue string
}

func main() {

	paramDebug := flag.Bool("debug", false, "print debug messages")
	paramHelp := flag.Bool("help", false, "print help")
	paramShowAll := flag.Bool("showall", false, "show all variables not only differences")
	paramDelimiter := flag.String("delimiter", "", "URI of master database")
	paramConfigFile := flag.String("configfile", "", "name of config file with setting - if used parameters master and slave are ignored")
	paramSlaveName := flag.String("slave", "", "process only specific slave from config file - give name of section")
	flag.Parse()

	printDebugMsg = *paramDebug
	printHelpMsg := *paramHelp
	showAll := *paramShowAll
	configFile := *paramConfigFile
	listDelimiter := *paramDelimiter
	slaveName := *paramSlaveName
	debugMsg(fmt.Sprintf("printDebugMsg: %v", printDebugMsg))
	debugMsg(fmt.Sprintf("printHelpMsg: %v", printHelpMsg))
	debugMsg(fmt.Sprintf("showAll: %v", showAll))
	debugMsg(fmt.Sprintf("configFile: %v", configFile))
	debugMsg(fmt.Sprintf("listDelimiter: %v", listDelimiter))
	debugMsg(fmt.Sprintf("slaveName: %v", slaveName))

	if printHelpMsg == true || configFile == "" {
		flag.PrintDefaults()
		log.Fatal()
	}

	if _, err := os.Stat(configFile); os.IsNotExist(err) {
		log.Fatal("ERROR: Cannot find ini file", configFile, "message:", err)
	}

	cfg, err := ini.LoadSources(ini.LoadOptions{Loose: false}, configFile)
	if err != nil {
		log.Fatal(curTime(), "ERROR: cannot read ini file:", configFile, "message:", err)
	}

	masterURI := cfg.Section("master").Key("URI").String()
	checkValue("masterURI", masterURI, true, printDebugMsg)

	sections := cfg.SectionStrings()
	for i := 0; i < len(sections); i++ {
		if sections[i] != "master" && sections[i] != "DEFAULT" && ((slaveName == "") || ((slaveName != "") && (sections[i] == slaveName))) {
			printMsg("**************** ", sections[i], " ******************")
			slaveURI := cfg.Section(sections[i]).Key("URI").String()

			//masterQuery := `select VARIABLE_NAME, VARIABLE_VALUE from information_schema.GLOBAL_VARIABLES order by 1;`
			//slaveQuery := `select VARIABLE_VALUE from information_schema.GLOBAL_VARIABLES where VARIABLE_NAME='%s';`
			masterQuery := `show variables;`
			slaveQuery := `show variables where Variable_name = '%s';`

			masterDB, err := sql.Open("mysql", masterURI)
			if err != nil {
				log.Fatal(curTime(), "cannot connect to mysql master:", err)
			}

			defer func() {
				if errClose := masterDB.Close(); err != nil {
					log.Println(curTime(), "closing master database:", errClose.Error())
				}
			}()

			slaveDB, err := sql.Open("mysql", slaveURI)
			if err != nil {
				log.Fatal(curTime(), "cannot connect to mysql slave:", err)
			}

			defer func() {
				if errClose := slaveDB.Close(); err != nil {
					log.Println(curTime(), "closing slave database:", errClose.Error())
				}
			}()

			// read from master
			masterRows, err := masterDB.Query(masterQuery)
			if err != nil {
				log.Fatalln(curTime(), "could not run master query:", err)
			}

			debugMsg("Starting check")

			var md, sd data
			if masterRows != nil {
				for masterRows.Next() {
					if err = masterRows.Scan(
						&md.variableName,
						&md.variableValue,
					); err != nil {
						log.Fatalln(curTime(), "cannot parse data:", err)
					}

					query := fmt.Sprintf(slaveQuery, md.variableName)
					slaveRow, err := slaveDB.Query(query)
					if err != nil {
						log.Fatalln(curTime(), "could not run slave query:", err)
					}
					if slaveRow != nil {
						for slaveRow.Next() {
							if err = slaveRow.Scan(
								&sd.variableName,
								&sd.variableValue,
							); err != nil {
								log.Fatalln("cannot read slave value", err)
							}
							if showAll == false {
								if md.variableValue != sd.variableValue {
									fmt.Println(md.variableName, " - master: ", md.variableValue, " - slave: ", sd.variableValue)
								}
							} else {
								fmt.Print(md.variableName, " - master: ", md.variableValue, " - slave: ", sd.variableValue)
								if md.variableValue != sd.variableValue {
									fmt.Println("!!!!")
								} else {
									fmt.Println("")
								}

							}
						}
					}
				}
			}
			debugMsg(sections[i], " - done")
		}
	}
	fmt.Println(curTime(), "DONE")

}

func curTime() string {
	return time.Now().UTC().Format(time.RFC3339) + ":"
}

func debugMsg(t ...interface{}) {
	if printDebugMsg == true {
		printMsg(t...)
	}
}

func printMsg(t ...interface{}) {
	fmt.Println(curTime(), fmt.Sprint(t...))
}

func checkValue(name string, value string, required bool, printit bool) {
	if value == "" && required == true {
		log.Fatal("ERROR: variable ", name, " cannot be empty!")
	}
	if printit == true {
		fmt.Println(curTime(), name, ": ", value)
	}
}