cli/internal/config/config_file.go
Mislav Marohnić 77227a6c50 Trigger OAuth flow only when requesting auth token
Previously we would trigger OAuth flow when the config file did not
exist. Now we will let an empty Config object be initialized in that
case, but trigger OAuth flow when the Context caller requests an
AuthToken.
2020-06-02 13:24:39 +02:00

179 lines
3.5 KiB
Go

package config
import (
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"path"
"path/filepath"
"github.com/mitchellh/go-homedir"
"gopkg.in/yaml.v3"
)
func ConfigDir() string {
dir, _ := homedir.Expand("~/.config/gh")
return dir
}
func ConfigFile() string {
return path.Join(ConfigDir(), "config.yml")
}
func ParseDefaultConfig() (Config, error) {
return ParseConfig(ConfigFile())
}
var ReadConfigFile = func(fn string) ([]byte, error) {
f, err := os.Open(fn)
if err != nil {
return nil, err
}
defer f.Close()
data, err := ioutil.ReadAll(f)
if err != nil {
return nil, err
}
return data, nil
}
var WriteConfigFile = func(fn string, data []byte) error {
err := os.MkdirAll(filepath.Dir(fn), 0771)
if err != nil {
return err
}
cfgFile, err := os.OpenFile(fn, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600) // cargo coded from setup
if err != nil {
return err
}
defer cfgFile.Close()
n, err := cfgFile.Write(data)
if err == nil && n < len(data) {
err = io.ErrShortWrite
}
return err
}
var BackupConfigFile = func(fn string) error {
return os.Rename(fn, fn+".bak")
}
func parseConfigFile(fn string) ([]byte, *yaml.Node, error) {
data, err := ReadConfigFile(fn)
if err != nil {
return nil, nil, err
}
var root yaml.Node
err = yaml.Unmarshal(data, &root)
if err != nil {
return data, nil, err
}
if len(root.Content) < 1 {
return data, &root, fmt.Errorf("malformed config")
}
if root.Content[0].Kind != yaml.MappingNode {
return data, &root, fmt.Errorf("expected a top level map")
}
return data, &root, nil
}
func isLegacy(root *yaml.Node) bool {
for _, v := range root.Content[0].Content {
if v.Value == "hosts" {
return false
}
}
return true
}
func migrateConfig(fn string, root *yaml.Node) error {
type ConfigEntry map[string]string
type ConfigHash map[string]ConfigEntry
newConfigData := map[string]ConfigHash{}
newConfigData["hosts"] = ConfigHash{}
topLevelKeys := root.Content[0].Content
for i, x := range topLevelKeys {
if x.Value == "" {
continue
}
if i+1 == len(topLevelKeys) {
break
}
hostname := x.Value
newConfigData["hosts"][hostname] = ConfigEntry{}
authKeys := topLevelKeys[i+1].Content[0].Content
for j, y := range authKeys {
if j+1 == len(authKeys) {
break
}
switch y.Value {
case "user":
newConfigData["hosts"][hostname]["user"] = authKeys[j+1].Value
case "oauth_token":
newConfigData["hosts"][hostname]["oauth_token"] = authKeys[j+1].Value
}
}
}
if _, ok := newConfigData["hosts"][defaultHostname]; !ok {
return errors.New("could not find default host configuration")
}
defaultHostConfig := newConfigData["hosts"][defaultHostname]
if _, ok := defaultHostConfig["user"]; !ok {
return errors.New("default host configuration missing user")
}
if _, ok := defaultHostConfig["oauth_token"]; !ok {
return errors.New("default host configuration missing oauth_token")
}
newConfig, err := yaml.Marshal(newConfigData)
if err != nil {
return err
}
err = BackupConfigFile(fn)
if err != nil {
return fmt.Errorf("failed to back up existing config: %w", err)
}
return WriteConfigFile(fn, newConfig)
}
func ParseConfig(fn string) (Config, error) {
_, root, err := parseConfigFile(fn)
if err != nil {
return nil, err
}
if isLegacy(root) {
err = migrateConfig(fn, root)
if err != nil {
return nil, err
}
_, root, err = parseConfigFile(fn)
if err != nil {
return nil, fmt.Errorf("failed to reparse migrated config: %w", err)
}
}
return NewConfig(root), nil
}