예제 #1
0
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
}
예제 #2
0
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
}
예제 #3
0
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
}
예제 #4
0
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)")
}
예제 #5
0
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
}
예제 #6
0
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
}
예제 #7
0
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
}
예제 #8
0
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
}
예제 #9
0
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
}
예제 #10
0
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)
}
예제 #11
0
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'")
}
예제 #12
0
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
}