internal/userconfig/userconfig.go

Summary

Maintainability
A
50 mins
Test Coverage
// 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
}