refactor: move things around to make building a client easier from other projects (#123)
* refactor: split logic for building API config into public func * refactor: move config code out of internal/
This commit is contained in:
92
config/config.go
Normal file
92
config/config.go
Normal file
@ -0,0 +1,92 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/influxdata/influx-cli/v2/api"
|
||||
)
|
||||
|
||||
// Config store the crendentials of influxdb host and token.
|
||||
type Config struct {
|
||||
Name string `toml:"-" json:"-"`
|
||||
Host string `toml:"url" json:"url"`
|
||||
// Token is base64 encoded sequence.
|
||||
Token string `toml:"token" json:"token"`
|
||||
Org string `toml:"org" json:"org"`
|
||||
Active bool `toml:"active,omitempty" json:"active,omitempty"`
|
||||
PreviousActive bool `toml:"previous,omitempty" json:"previous,omitempty"`
|
||||
}
|
||||
|
||||
// DefaultConfig is default config without token
|
||||
var DefaultConfig = Config{
|
||||
Name: "default",
|
||||
Host: "http://localhost:8086",
|
||||
Active: true,
|
||||
}
|
||||
|
||||
// DefaultPath computes the path where CLI configs will be stored if not overridden.
|
||||
func DefaultPath() (string, error) {
|
||||
var dir string
|
||||
// By default, store meta and data files in current users home directory
|
||||
u, err := user.Current()
|
||||
if err == nil {
|
||||
dir = u.HomeDir
|
||||
} else if home := os.Getenv("HOME"); home != "" {
|
||||
dir = home
|
||||
} else {
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
dir = wd
|
||||
}
|
||||
dir = filepath.Join(dir, ".influxdbv2", "configs")
|
||||
|
||||
return dir, nil
|
||||
}
|
||||
|
||||
// Service is the service to list and write configs.
|
||||
type Service interface {
|
||||
CreateConfig(Config) (Config, error)
|
||||
DeleteConfig(name string) (Config, error)
|
||||
UpdateConfig(Config) (Config, error)
|
||||
SwitchActive(name string) (Config, error)
|
||||
Active() (Config, error)
|
||||
ListConfigs() (Configs, error)
|
||||
}
|
||||
|
||||
// Configs is map of configs indexed by name.
|
||||
type Configs map[string]Config
|
||||
|
||||
// Switch to another config.
|
||||
func (cfgs Configs) switchActive(name string) error {
|
||||
if _, ok := cfgs[name]; !ok {
|
||||
return &api.Error{
|
||||
Code: api.ERRORCODE_NOT_FOUND,
|
||||
Message: fmt.Sprintf("config %q is not found", name),
|
||||
}
|
||||
}
|
||||
for k, v := range cfgs {
|
||||
v.PreviousActive = v.Active && (k != name)
|
||||
v.Active = k == name
|
||||
cfgs[k] = v
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cfgs Configs) active() Config {
|
||||
for _, cfg := range cfgs {
|
||||
if cfg.Active {
|
||||
return cfg
|
||||
}
|
||||
}
|
||||
if len(cfgs) > 0 {
|
||||
for _, cfg := range cfgs {
|
||||
return cfg
|
||||
}
|
||||
}
|
||||
return DefaultConfig
|
||||
}
|
289
config/local.go
Normal file
289
config/local.go
Normal file
@ -0,0 +1,289 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/influxdata/influx-cli/v2/api"
|
||||
)
|
||||
|
||||
// store is the embedded store of the Config service.
|
||||
type store interface {
|
||||
parsePreviousActive() (Config, error)
|
||||
ListConfigs() (Configs, error)
|
||||
writeConfigs(cfgs Configs) error
|
||||
}
|
||||
|
||||
// localConfigsSVC can write and parse configs from a local path.
|
||||
type localConfigsSVC struct {
|
||||
store
|
||||
}
|
||||
|
||||
// NewLocalConfigService creates a new service that can write and parse configs
|
||||
// to/from a path on local disk.
|
||||
func NewLocalConfigService(path string) Service {
|
||||
return &localConfigsSVC{ioStore{Path: path}}
|
||||
}
|
||||
|
||||
// CreateConfig create new config.
|
||||
func (svc localConfigsSVC) CreateConfig(cfg Config) (Config, error) {
|
||||
if cfg.Name == "" {
|
||||
return Config{}, &api.Error{
|
||||
Code: api.ERRORCODE_INVALID,
|
||||
Message: "config name is empty",
|
||||
}
|
||||
}
|
||||
cfgs, err := svc.ListConfigs()
|
||||
if err != nil {
|
||||
return Config{}, err
|
||||
}
|
||||
if _, ok := cfgs[cfg.Name]; ok {
|
||||
return Config{}, &api.Error{
|
||||
Code: api.ERRORCODE_CONFLICT,
|
||||
Message: fmt.Sprintf("config %q already exists", cfg.Name),
|
||||
}
|
||||
}
|
||||
cfgs[cfg.Name] = cfg
|
||||
if cfg.Active {
|
||||
if err := cfgs.switchActive(cfg.Name); err != nil {
|
||||
return Config{}, err
|
||||
}
|
||||
}
|
||||
|
||||
return cfgs[cfg.Name], svc.writeConfigs(cfgs)
|
||||
}
|
||||
|
||||
// DeleteConfig will delete a config.
|
||||
func (svc localConfigsSVC) DeleteConfig(name string) (Config, error) {
|
||||
cfgs, err := svc.ListConfigs()
|
||||
if err != nil {
|
||||
return Config{}, err
|
||||
}
|
||||
|
||||
p, ok := cfgs[name]
|
||||
if !ok {
|
||||
return Config{}, &api.Error{
|
||||
Code: api.ERRORCODE_NOT_FOUND,
|
||||
Message: fmt.Sprintf("config %q is not found", name),
|
||||
}
|
||||
}
|
||||
delete(cfgs, name)
|
||||
|
||||
if p.Active && len(cfgs) > 0 {
|
||||
for name, cfg := range cfgs {
|
||||
cfg.Active = true
|
||||
cfgs[name] = cfg
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return p, svc.writeConfigs(cfgs)
|
||||
}
|
||||
|
||||
func (svc localConfigsSVC) Active() (Config, error) {
|
||||
cfgs, err := svc.ListConfigs()
|
||||
if err != nil {
|
||||
return Config{}, err
|
||||
}
|
||||
return cfgs.active(), nil
|
||||
}
|
||||
|
||||
// SwitchActive will active the config by name, if name is "-", active the previous one.
|
||||
func (svc localConfigsSVC) SwitchActive(name string) (Config, error) {
|
||||
var up Config
|
||||
if name == "-" {
|
||||
p0, err := svc.parsePreviousActive()
|
||||
if err != nil {
|
||||
return Config{}, err
|
||||
}
|
||||
up.Name = p0.Name
|
||||
} else {
|
||||
up.Name = name
|
||||
}
|
||||
up.Active = true
|
||||
return svc.UpdateConfig(up)
|
||||
}
|
||||
|
||||
// UpdateConfig will update the config.
|
||||
func (svc localConfigsSVC) UpdateConfig(up Config) (Config, error) {
|
||||
cfgs, err := svc.ListConfigs()
|
||||
if err != nil {
|
||||
return Config{}, err
|
||||
}
|
||||
p0, ok := cfgs[up.Name]
|
||||
if !ok {
|
||||
return Config{}, &api.Error{
|
||||
Code: api.ERRORCODE_NOT_FOUND,
|
||||
Message: fmt.Sprintf("config %q is not found", up.Name),
|
||||
}
|
||||
}
|
||||
if up.Token != "" {
|
||||
p0.Token = up.Token
|
||||
}
|
||||
if up.Host != "" {
|
||||
p0.Host = up.Host
|
||||
}
|
||||
if up.Org != "" {
|
||||
p0.Org = up.Org
|
||||
}
|
||||
|
||||
cfgs[up.Name] = p0
|
||||
if up.Active {
|
||||
if err := cfgs.switchActive(up.Name); err != nil {
|
||||
return Config{}, err
|
||||
}
|
||||
}
|
||||
|
||||
return cfgs[up.Name], svc.writeConfigs(cfgs)
|
||||
}
|
||||
|
||||
type baseRW struct {
|
||||
r io.Reader
|
||||
w io.Writer
|
||||
}
|
||||
|
||||
// parsePreviousActive return the previous active config from the reader
|
||||
func (s baseRW) parsePreviousActive() (Config, error) {
|
||||
return s.parseActiveConfig(false)
|
||||
}
|
||||
|
||||
// ListConfigs decodes configs from io readers
|
||||
func (s baseRW) ListConfigs() (Configs, error) {
|
||||
cfgs := make(Configs)
|
||||
_, err := toml.DecodeReader(s.r, &cfgs)
|
||||
for n, cfg := range cfgs {
|
||||
cfg.Name = n
|
||||
cfgs[n] = cfg
|
||||
}
|
||||
return cfgs, err
|
||||
}
|
||||
|
||||
func (s baseRW) writeConfigs(cfgs Configs) error {
|
||||
if err := blockBadName(cfgs); err != nil {
|
||||
return err
|
||||
}
|
||||
var b2 bytes.Buffer
|
||||
if err := toml.NewEncoder(s.w).Encode(cfgs); err != nil {
|
||||
return err
|
||||
}
|
||||
// a list cloud 2 clusters, commented out
|
||||
s.w.Write([]byte("# \n"))
|
||||
cfgs = map[string]Config{
|
||||
"us-central": {Host: "https://us-central1-1.gcp.cloud2.influxdata.com", Token: "XXX"},
|
||||
"us-west": {Host: "https://us-west-2-1.aws.cloud2.influxdata.com", Token: "XXX"},
|
||||
"eu-central": {Host: "https://eu-central-1-1.aws.cloud2.influxdata.com", Token: "XXX"},
|
||||
}
|
||||
|
||||
if err := toml.NewEncoder(&b2).Encode(cfgs); err != nil {
|
||||
return err
|
||||
}
|
||||
reader := bufio.NewReader(&b2)
|
||||
for {
|
||||
line, _, err := reader.ReadLine()
|
||||
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
s.w.Write([]byte("# " + string(line) + "\n"))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var badNames = map[string]bool{
|
||||
"-": false,
|
||||
"list": false,
|
||||
"update": false,
|
||||
"set": false,
|
||||
"delete": false,
|
||||
"switch": false,
|
||||
"create": false,
|
||||
}
|
||||
|
||||
func blockBadName(cfgs Configs) error {
|
||||
for n := range cfgs {
|
||||
if _, ok := badNames[n]; ok {
|
||||
return &api.Error{
|
||||
Code: api.ERRORCODE_INVALID,
|
||||
Message: fmt.Sprintf("%q is not a valid config name", n),
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s baseRW) parseActiveConfig(currentOrPrevious bool) (Config, error) {
|
||||
previousText := ""
|
||||
if !currentOrPrevious {
|
||||
previousText = "previous "
|
||||
}
|
||||
cfgs, err := s.ListConfigs()
|
||||
if err != nil {
|
||||
return DefaultConfig, err
|
||||
}
|
||||
var activated Config
|
||||
var hasActive bool
|
||||
for _, cfg := range cfgs {
|
||||
check := cfg.Active
|
||||
if !currentOrPrevious {
|
||||
check = cfg.PreviousActive
|
||||
}
|
||||
if check && !hasActive {
|
||||
activated = cfg
|
||||
hasActive = true
|
||||
} else if check {
|
||||
return DefaultConfig, &api.Error{
|
||||
Code: api.ERRORCODE_CONFLICT,
|
||||
Message: fmt.Sprintf("more than one %s activated configs found", previousText),
|
||||
}
|
||||
}
|
||||
}
|
||||
if hasActive {
|
||||
return activated, nil
|
||||
}
|
||||
return DefaultConfig, &api.Error{
|
||||
Code: api.ERRORCODE_NOT_FOUND,
|
||||
Message: fmt.Sprintf("%s activated config is not found", previousText),
|
||||
}
|
||||
}
|
||||
|
||||
type ioStore struct {
|
||||
Path string
|
||||
}
|
||||
|
||||
// ListConfigs from the local path.
|
||||
func (s ioStore) ListConfigs() (Configs, error) {
|
||||
r, err := os.Open(s.Path)
|
||||
if err != nil {
|
||||
return make(Configs), nil
|
||||
}
|
||||
defer r.Close()
|
||||
return (baseRW{r: r}).ListConfigs()
|
||||
}
|
||||
|
||||
// parsePreviousActive from the local path.
|
||||
func (s ioStore) parsePreviousActive() (Config, error) {
|
||||
r, err := os.Open(s.Path)
|
||||
if err != nil {
|
||||
return Config{}, nil
|
||||
}
|
||||
defer r.Close()
|
||||
return (baseRW{r: r}).parsePreviousActive()
|
||||
}
|
||||
|
||||
// writeConfigs to the path.
|
||||
func (s ioStore) writeConfigs(cfgs Configs) error {
|
||||
if err := os.MkdirAll(filepath.Dir(s.Path), os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
var b1 bytes.Buffer
|
||||
if err := (baseRW{w: &b1}).writeConfigs(cfgs); err != nil {
|
||||
return err
|
||||
}
|
||||
return ioutil.WriteFile(s.Path, b1.Bytes(), 0600)
|
||||
}
|
Reference in New Issue
Block a user