internal/userconfig/userconfig.go
// Package userconfig provides user machine-level configuration.
package userconfig
import (
"encoding/base64"
"encoding/json"
"io/ioutil"
"os"
"path/filepath"
"github.com/apex/up/internal/util"
"github.com/mitchellh/go-homedir"
"github.com/pkg/errors"
)
var (
// configDir is the dir name where up config is stored relative to HOME.
configDir = ".up"
// envName is the environment variable which can be used to store
// Up's configuration, primarily for continuous integration.
envName = "UP_CONFIG"
)
// Team is the user configuration for a given team.
type Team struct {
// ID is the team identifier.
ID string `json:"team"`
// Email is the user's email.
Email string `json:"email"`
// Token is the access token.
Token string `json:"token"`
}
// IsPersonal returns true if it is a personal team.
func (t *Team) IsPersonal() bool {
return t.Email == t.ID
}
// Config is the user configuration.
type Config struct {
// Team is the active team.
Team string `json:"team"`
// Teams is the user's active teams.
Teams map[string]*Team `json:"teams"`
}
// initTeams inits the map.
func (c *Config) initTeams() {
if c.Teams == nil {
c.Teams = make(map[string]*Team)
}
}
// AddTeam adds or replaces the given team.
func (c *Config) AddTeam(t *Team) {
c.initTeams()
c.Teams[t.ID] = t
}
// GetTeams returns a list of teams.
func (c *Config) GetTeams() (teams []*Team) {
for _, t := range c.Teams {
teams = append(teams, t)
}
return
}
// GetTeam returns a team by id or nil
func (c *Config) GetTeam(id string) *Team {
return c.Teams[id]
}
// GetActiveTeam returns the active team.
func (c *Config) GetActiveTeam() *Team {
return c.GetTeam(c.Team)
}
// Authenticated returns true if the user has an active team.
func (c *Config) Authenticated() bool {
return c.GetActiveTeam() != nil
}
// Require requires authentication and returns the active team.
func Require() (*Team, error) {
var c Config
if err := c.Load(); err != nil {
return nil, errors.Wrap(err, "loading config")
}
if !c.Authenticated() {
return nil, errors.New("user credentials missing, make sure to `up team login` first")
}
return c.GetActiveTeam(), nil
}
// Alter config, loading and saving after manipulation.
func Alter(fn func(*Config)) error {
var config Config
if err := config.Load(); err != nil {
return errors.Wrap(err, "loading")
}
fn(&config)
if err := config.Save(); err != nil {
return errors.Wrap(err, "saving")
}
return nil
}
// Load the configuration.
func (c *Config) Load() error {
path, err := c.path()
if err != nil {
return errors.Wrap(err, "getting path")
}
// env
if s := os.Getenv(envName); s != "" {
// if not JSON, check for base64 encoding
if !util.IsJSON(s) {
decoded, err := base64.StdEncoding.DecodeString(s)
if err != nil {
return errors.Wrap(err, "decoding base64")
}
s = string(decoded)
}
if err := json.Unmarshal([]byte(s), &c); err != nil {
return errors.Wrap(err, "unmarshaling")
}
return nil
}
// file
b, err := ioutil.ReadFile(path)
if os.IsNotExist(err) {
return nil
}
if err != nil {
return errors.Wrap(err, "reading")
}
if err := json.Unmarshal(b, c); err != nil {
return errors.Wrap(err, "unmarshaling")
}
return nil
}
// Save the configuration.
func (c *Config) Save() error {
b, err := json.MarshalIndent(c, "", " ")
if err != nil {
return errors.Wrap(err, "marshaling")
}
path, err := c.path()
if err != nil {
return errors.Wrap(err, "getting path")
}
if err := ioutil.WriteFile(path, b, 0755); err != nil {
return errors.Wrap(err, "writing")
}
return nil
}
// path returns the path and sets up dir if necessary.
func (c *Config) path() (string, error) {
home, err := homedir.Dir()
if err != nil {
return "", errors.Wrap(err, "homedir")
}
dir := filepath.Join(home, configDir)
if err := os.MkdirAll(dir, 0755); err != nil {
return "", errors.Wrap(err, "mkdir")
}
path := filepath.Join(dir, "config.json")
return path, nil
}