github.com/trevoraustin/hub@v2.2.0-preview1.0.20141105230840-96d8bfc654cc+incompatible/github/config.go (about)

     1  package github
     2  
     3  import (
     4  	"bufio"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"os"
     8  	"path/filepath"
     9  	"strconv"
    10  
    11  	"github.com/github/hub/utils"
    12  	"github.com/howeyc/gopass"
    13  )
    14  
    15  var (
    16  	defaultConfigsFile = filepath.Join(os.Getenv("HOME"), ".config", "hub")
    17  )
    18  
    19  type yamlHost struct {
    20  	User       string `yaml:"user"`
    21  	OAuthToken string `yaml:"oauth_token"`
    22  	Protocol   string `yaml:"protocol"`
    23  }
    24  
    25  type yamlConfig map[string][]yamlHost
    26  
    27  type Host struct {
    28  	Host        string `toml:"host"`
    29  	User        string `toml:"user"`
    30  	AccessToken string `toml:"access_token"`
    31  	Protocol    string `toml:"protocol"`
    32  }
    33  
    34  type Config struct {
    35  	Hosts []Host `toml:"hosts"`
    36  }
    37  
    38  func (c *Config) PromptForHost(host string) (h *Host, err error) {
    39  	h = c.Find(host)
    40  	if h != nil {
    41  		return
    42  	}
    43  
    44  	user := c.PromptForUser()
    45  	pass := c.PromptForPassword(host, user)
    46  
    47  	client := NewClient(host)
    48  	token, e := client.FindOrCreateToken(user, pass, "")
    49  	if e != nil {
    50  		if ae, ok := e.(*AuthError); ok && ae.Is2FAError() {
    51  			code := c.PromptForOTP()
    52  			token, err = client.FindOrCreateToken(user, pass, code)
    53  		} else {
    54  			err = e
    55  		}
    56  	}
    57  
    58  	if err != nil {
    59  		return
    60  	}
    61  
    62  	client.Host.AccessToken = token
    63  	currentUser, err := client.CurrentUser()
    64  	if err != nil {
    65  		return
    66  	}
    67  
    68  	h = &Host{
    69  		Host:        host,
    70  		User:        currentUser.Login,
    71  		AccessToken: token,
    72  		Protocol:    "https",
    73  	}
    74  	c.Hosts = append(c.Hosts, *h)
    75  	err = newConfigService().Save(configsFile(), c)
    76  
    77  	return
    78  }
    79  
    80  func (c *Config) PromptForUser() (user string) {
    81  	user = os.Getenv("GITHUB_USER")
    82  	if user != "" {
    83  		return
    84  	}
    85  
    86  	fmt.Printf("%s username: ", GitHubHost)
    87  	user = c.scanLine()
    88  
    89  	return
    90  }
    91  
    92  func (c *Config) PromptForPassword(host, user string) (pass string) {
    93  	pass = os.Getenv("GITHUB_PASSWORD")
    94  	if pass != "" {
    95  		return
    96  	}
    97  
    98  	fmt.Printf("%s password for %s (never stored): ", host, user)
    99  	if isTerminal(os.Stdout.Fd()) {
   100  		pass = string(gopass.GetPasswd())
   101  	} else {
   102  		pass = c.scanLine()
   103  	}
   104  
   105  	return
   106  }
   107  
   108  func (c *Config) PromptForOTP() string {
   109  	fmt.Print("two-factor authentication code: ")
   110  	return c.scanLine()
   111  }
   112  
   113  func (c *Config) scanLine() string {
   114  	var line string
   115  	scanner := bufio.NewScanner(os.Stdin)
   116  	if scanner.Scan() {
   117  		line = scanner.Text()
   118  	}
   119  	utils.Check(scanner.Err())
   120  
   121  	return line
   122  }
   123  
   124  func (c *Config) Find(host string) *Host {
   125  	for _, h := range c.Hosts {
   126  		if h.Host == host {
   127  			return &h
   128  		}
   129  	}
   130  
   131  	return nil
   132  }
   133  
   134  func (c *Config) selectHost() *Host {
   135  	options := len(c.Hosts)
   136  
   137  	if options == 1 {
   138  		return &c.Hosts[0]
   139  	}
   140  
   141  	prompt := "Select host:\n"
   142  	for idx, host := range c.Hosts {
   143  		prompt += fmt.Sprintf(" %d. %s\n", idx+1, host.Host)
   144  	}
   145  	prompt += fmt.Sprint("> ")
   146  
   147  	fmt.Printf(prompt)
   148  	index := c.scanLine()
   149  	i, err := strconv.Atoi(index)
   150  	if err != nil || i < 1 || i > options {
   151  		utils.Check(fmt.Errorf("Error: must enter a number [1-%d]", options))
   152  	}
   153  
   154  	return &c.Hosts[i-1]
   155  }
   156  
   157  func configsFile() string {
   158  	configsFile := os.Getenv("GH_CONFIG")
   159  	if configsFile == "" {
   160  		configsFile = defaultConfigsFile
   161  	}
   162  
   163  	return configsFile
   164  }
   165  
   166  func CurrentConfig() *Config {
   167  	c := &Config{}
   168  	newConfigService().Load(configsFile(), c)
   169  
   170  	return c
   171  }
   172  
   173  func (c *Config) DefaultHost() (host *Host, err error) {
   174  	if GitHubHostEnv != "" {
   175  		host, err = c.PromptForHost(GitHubHostEnv)
   176  	} else if len(c.Hosts) > 0 {
   177  		host = c.selectHost()
   178  	} else {
   179  		host, err = c.PromptForHost(DefaultGitHubHost())
   180  	}
   181  
   182  	return
   183  }
   184  
   185  // Public for testing purpose
   186  func CreateTestConfigs(user, token string) *Config {
   187  	f, _ := ioutil.TempFile("", "test-config")
   188  	defaultConfigsFile = f.Name()
   189  
   190  	host := Host{
   191  		User:        "jingweno",
   192  		AccessToken: "123",
   193  		Host:        GitHubHost,
   194  	}
   195  
   196  	c := &Config{Hosts: []Host{host}}
   197  	err := newConfigService().Save(f.Name(), c)
   198  	if err != nil {
   199  		panic(err)
   200  	}
   201  
   202  	return c
   203  }