func (i *Installer) createMySQLInstance(dsn mysql.DSN) (*proto.MySQLInstance, error) { // First use instance.Manager to fill in details about the MySQL server. dsnString, _ := dsn.DSN() mi := &proto.MySQLInstance{ Hostname: i.hostname, DSN: dsnString, } if err := instance.GetMySQLInfo(mi); err != nil { if i.flags["debug"] { log.Printf("err=%s\n", err) } return nil, err } // POST <api>/instances/mysql data, err := json.Marshal(mi) if err != nil { return nil, err } url := pct.URL(i.agentConfig.ApiHostname, "instances", "mysql") if i.flags["debug"] { log.Println(url) } resp, _, err := i.api.Post(i.agentConfig.ApiKey, url, data) if i.flags["debug"] { log.Printf("resp=%#v\n", resp) log.Printf("err=%s\n", err) } if err != nil { return nil, err } // Create new instance, if it already exist then just use it // todo: better handling of duplicate instance if resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusConflict { return nil, fmt.Errorf("Failed to create MySQL instance (status code %d)", resp.StatusCode) } // API returns URI of new resource in Location header uri := resp.Header.Get("Location") if uri == "" { return nil, fmt.Errorf("API did not return location of new MySQL instance") } // GET <api>/instances/mysql/id (URI) code, data, err := i.api.Get(i.agentConfig.ApiKey, uri) if i.flags["debug"] { log.Printf("code=%d\n", code) log.Printf("err=%s\n", err) } if err != nil { return nil, err } if code != http.StatusOK { return nil, fmt.Errorf("Failed to get new MySQL instance (status code %d)", code) } if err := json.Unmarshal(data, mi); err != nil { return nil, err } return mi, nil }
func TestMySQLConnection(dsn mysql.DSN) error { dsnString, err := dsn.DSN() if err != nil { return err } fmt.Printf("Testing MySQL connection %s...\n", dsn) conn := mysql.NewConnection(dsnString) if err := conn.Connect(1); err != nil { return err } defer conn.Close() return nil }
func (i *Installer) verifyMySQLConnection(dsn mysql.DSN) (err error) { dsnString, err := dsn.DSN() if err != nil { return err } if i.flags.Bool["debug"] { log.Printf("verifyMySQLConnection: %#v %s\n", dsn, dsnString) } conn := mysql.NewConnection(dsnString) if err := conn.Connect(1); err != nil { return err } conn.Close() return nil }
func (s *DSNTestSuite) TestAllFields(t *C) { dsn := mysql.DSN{ Username: "******", Password: "******", Hostname: "host.example.com", Port: "3306", } str, err := dsn.DSN() t.Check(err, IsNil) t.Check(str, Equals, "user:pass@tcp(host.example.com:3306)/?parseTime=true") // Stringify DSN removes password, e.g. makes it safe to print log, etc. str = fmt.Sprintf("%s", dsn) t.Check(str, Equals, "user:<password-hidden>@tcp(host.example.com:3306)") }
func ParsePTAgentConf(content string, agent *agent.Config, dsn *mysql.DSN) (string, error) { libDir := "" if content == "" { return libDir, nil } libDirRe := regexp.MustCompile(`lib\s*=(\S+)`) m := libDirRe.FindStringSubmatch(content) if len(m) > 1 { libDir = m[1] fmt.Printf("pt-agent lib dir: %s\n", libDir) } apiKeyRe := regexp.MustCompile(`^\s*api-key\s*=(\S+)`) m = apiKeyRe.FindStringSubmatch(content) if len(m) > 1 { agent.ApiKey = m[1] fmt.Printf("pt-agent API key: %s\n", agent.ApiKey) } socketRe := regexp.MustCompile(`socket\s*=(\S+)`) m = socketRe.FindStringSubmatch(content) if len(m) > 1 { dsn.Socket = m[1] fmt.Printf("pt-agent socket: %s\n", dsn.Socket) } return libDir, nil }
func ParseMyCnf(content string, dsn *mysql.DSN) error { if content == "" { return nil } userRe := regexp.MustCompile(`(?m)^user\s*=(\S+)`) m := userRe.FindStringSubmatch(content) if len(m) > 1 { dsn.Username = m[1] fmt.Printf("pt-agent MySQL user: %s\n", dsn.Username) } passRe := regexp.MustCompile(`(?m)^\s*pass\s*=(\S+)`) m = passRe.FindStringSubmatch(content) if len(m) > 1 { dsn.Password = m[1] fmt.Printf("pt-agent MySQL user pass: %s\n", dsn.Password) } return nil }
func (i *Installer) createMySQLUser(dsn mysql.DSN) (mysql.DSN, error) { // Same host:port or socket, but different user and pass. userDSN := dsn userDSN.Username = "******" userDSN.Password = fmt.Sprintf("%p%d", &dsn, rand.Uint32()) userDSN.OldPasswords = i.flags.Bool["old-passwords"] dsnString, _ := dsn.DSN() conn := mysql.NewConnection(dsnString) if err := conn.Connect(1); err != nil { return userDSN, err } defer conn.Close() grants := MakeGrant(dsn, userDSN.Username, userDSN.Password, i.flags.Int64["mysql-max-user-connections"]) for _, grant := range grants { if i.flags.Bool["debug"] { log.Println(grant) } _, err := conn.DB().Exec(grant) if err != nil { return userDSN, fmt.Errorf("Error executing %s: %s", grant, err) } } // Go MySQL driver resolves localhost to 127.0.0.1 but localhost is a special // value for MySQL, so 127.0.0.1 may not work with a grant @localhost, so we // add a 2nd grant @127.0.0.1 to be sure. if dsn.Hostname == "localhost" { dsn2 := dsn dsn2.Hostname = "127.0.0.1" grants := MakeGrant(dsn2, userDSN.Username, userDSN.Password, i.flags.Int64["mysql-max-user-connections"]) for _, grant := range grants { if i.flags.Bool["debug"] { log.Println(grant) } _, err := conn.DB().Exec(grant) if err != nil { return userDSN, fmt.Errorf("Error executing %s: %s", grant, err) } } } return userDSN, nil }
func (i *Installer) getDSNFromUser(dsn *mysql.DSN) error { // Ask for username username, err := i.term.PromptString("MySQL username", dsn.Username) if err != nil { return err } dsn.Username = username // Ask for password var password string if i.flags.Bool["plain-passwords"] { password, err = i.term.PromptString("MySQL password", "") } else { password, err = gopass.GetPass("MySQL password: "******"MySQL host[:port] or socket file", dsn.To(), ) if err != nil { return err } if filepath.IsAbs(hostname) { dsn.Socket = hostname dsn.Hostname = "" } else { f := strings.Split(hostname, ":") dsn.Hostname = f[0] if len(f) > 1 { dsn.Port = f[1] } else { dsn.Port = "3306" } dsn.Socket = "" } return nil }
func (i *Installer) autodetectDSN(dsn *mysql.DSN) error { params := []string{} if i.flags.String["mysql-defaults-file"] != "" { params = append(params, "--defaults-file="+i.flags.String["mysql-defaults-file"]) } // --print-defaults needs to be last param params = append(params, "--print-defaults") cmd := exec.Command("mysql", params...) byteOutput, err := cmd.Output() if err != nil { return err } output := string(byteOutput) if i.flags.Bool["debug"] { log.Println(output) } autoDSN := ParseMySQLDefaults(output) if i.flags.Bool["debug"] { log.Printf("autoDSN: %#v\n", autoDSN) } // Fill in the given DSN with auto-detected options. if dsn.Username == "" { dsn.Username = autoDSN.Username } if dsn.Password == "" { dsn.Password = autoDSN.Password } if dsn.Hostname == "" { dsn.Hostname = autoDSN.Hostname } if dsn.Port == "" { dsn.Port = autoDSN.Port } if dsn.Socket == "" { dsn.Socket = autoDSN.Socket } if dsn.Username == "" { user, err := user.Current() if err == nil { dsn.Username = user.Username } } if i.flags.Bool["debug"] { log.Printf("dsn: %#v\n", dsn) } return nil }
func (s *MySQLTestSuite) TestMakeGrant(t *C) { user := "******" pass := "******" dsn := mysql.DSN{ Username: "******", Password: "******", } dsn.Hostname = "localhost" maxOpenConnections := int64(1) got := i.MakeGrant(dsn, user, pass, maxOpenConnections) expect := []string{ "SET SESSION old_passwords=0", "GRANT SUPER, PROCESS, USAGE, SELECT ON *.* TO 'new-user'@'localhost' IDENTIFIED BY 'some pass' WITH MAX_USER_CONNECTIONS 1", "GRANT UPDATE, DELETE, DROP ON performance_schema.* TO 'new-user'@'localhost' IDENTIFIED BY 'some pass' WITH MAX_USER_CONNECTIONS 1", } t.Check(got, DeepEquals, expect) dsn.Hostname = "127.0.0.1" got = i.MakeGrant(dsn, user, pass, maxOpenConnections) expect = []string{ "SET SESSION old_passwords=0", "GRANT SUPER, PROCESS, USAGE, SELECT ON *.* TO 'new-user'@'127.0.0.1' IDENTIFIED BY 'some pass' WITH MAX_USER_CONNECTIONS 1", "GRANT UPDATE, DELETE, DROP ON performance_schema.* TO 'new-user'@'127.0.0.1' IDENTIFIED BY 'some pass' WITH MAX_USER_CONNECTIONS 1", } t.Check(got, DeepEquals, expect) dsn.Hostname = "10.1.1.1" got = i.MakeGrant(dsn, user, pass, maxOpenConnections) expect = []string{ "SET SESSION old_passwords=0", "GRANT SUPER, PROCESS, USAGE, SELECT ON *.* TO 'new-user'@'%' IDENTIFIED BY 'some pass' WITH MAX_USER_CONNECTIONS 1", "GRANT UPDATE, DELETE, DROP ON performance_schema.* TO 'new-user'@'%' IDENTIFIED BY 'some pass' WITH MAX_USER_CONNECTIONS 1", } t.Check(got, DeepEquals, expect) dsn.Hostname = "" dsn.Socket = "/var/lib/mysql.sock" got = i.MakeGrant(dsn, user, pass, maxOpenConnections) expect = []string{ "SET SESSION old_passwords=0", "GRANT SUPER, PROCESS, USAGE, SELECT ON *.* TO 'new-user'@'localhost' IDENTIFIED BY 'some pass' WITH MAX_USER_CONNECTIONS 1", "GRANT UPDATE, DELETE, DROP ON performance_schema.* TO 'new-user'@'localhost' IDENTIFIED BY 'some pass' WITH MAX_USER_CONNECTIONS 1", } t.Check(got, DeepEquals, expect) }
func (s *MySQLTestSuite) TestMakeGrant(t *C) { user := "******" pass := "******" dsn := mysql.DSN{ Username: "******", Password: "******", } dsn.Hostname = "localhost" t.Check(i.MakeGrant(dsn, user, pass), Equals, "GRANT SUPER, PROCESS, USAGE ON *.* TO 'new-user'@'localhost' IDENTIFIED BY 'some pass'") dsn.Hostname = "127.0.0.1" t.Check(i.MakeGrant(dsn, user, pass), Equals, "GRANT SUPER, PROCESS, USAGE ON *.* TO 'new-user'@'127.0.0.1' IDENTIFIED BY 'some pass'") dsn.Hostname = "10.1.1.1" t.Check(i.MakeGrant(dsn, user, pass), Equals, "GRANT SUPER, PROCESS, USAGE ON *.* TO 'new-user'@'%' IDENTIFIED BY 'some pass'") dsn.Hostname = "" dsn.Socket = "/var/lib/mysql.sock" t.Check(i.MakeGrant(dsn, user, pass), Equals, "GRANT SUPER, PROCESS, USAGE ON *.* TO 'new-user'@'localhost' IDENTIFIED BY 'some pass'") }
func (i *Installer) connectMySQL(def *mysql.DSN, creating bool) (mysql.DSN, error) { dsn := mysql.DSN{} if creating { fmt.Println("Specify a root/super MySQL user to create a user for the agent") } else if def != nil { fmt.Println("Verify the existing MySQL user to use for the agent") dsn = *def if dsn.Username == "" { user, _ := user.Current() if user != nil { dsn.Username = user.Username } } } else { fmt.Println("Specify an existing MySQL user to use for the agent") } CONNECT_MYSQL: for { username, err := i.term.PromptStringRequired("MySQL username", dsn.Username) if err != nil { return dsn, err } dsn.Username = username var password string if creating { password, err = gopass.GetPass("MySQL password: "******"MySQL password", dsn.Password) } if err != nil { return dsn, err } dsn.Password = password hostname, err := i.term.PromptStringRequired("MySQL host[:port] or socket file", dsn.To()) if err != nil { return dsn, err } if filepath.IsAbs(hostname) { dsn.Socket = hostname dsn.Hostname = "" } else { f := strings.Split(hostname, ":") dsn.Hostname = f[0] if len(f) > 1 { dsn.Port = f[1] } else { dsn.Port = "3306" } dsn.Socket = "" } if err := TestMySQLConnection(dsn); err != nil { fmt.Printf("Error connecting to MySQL %s: %s\n", dsn, err) again, err := i.term.PromptBool("Try again?", "Y") if err != nil { return dsn, err } if !again { return dsn, fmt.Errorf("Failed to connect to MySQL") } continue CONNECT_MYSQL } fmt.Printf("MySQL connection OK\n") break } return dsn, nil }