cli/config/config.go

168 lines
4.4 KiB
Go

package config
import (
"encoding/json"
"errors"
"fmt"
"net/url"
"os"
"path/filepath"
"runtime"
"strings"
"time"
"github.com/sirupsen/logrus"
"k8s.io/client-go/tools/clientcmd/api"
)
var ErrNoConfigurationFound = errors.New("no configuration found, run `login`")
// Config holds the main config for the user
type Config struct {
Servers map[string]*ServerConfig
//Path to the config file
Path string `json:"path,omitempty"`
// CurrentServer the user has in focus
CurrentServer string
}
// ServerConfig holds the config for each server the user has setup
type ServerConfig struct {
AccessKey string `json:"accessKey"`
SecretKey string `json:"secretKey"`
TokenKey string `json:"tokenKey"`
URL string `json:"url"`
Project string `json:"project"`
CACerts string `json:"cacert"`
KubeCredentials map[string]*ExecCredential `json:"kubeCredentials"`
KubeConfigs map[string]*api.Config `json:"kubeConfigs"`
ProxyURL string `json:"proxyUrl"`
HTTPTimeoutSeconds int `json:"httpTimeoutSeconds"`
}
func (c *ServerConfig) GetHTTPTimeout() time.Duration {
return time.Duration(c.HTTPTimeoutSeconds) * time.Second
}
// LoadFromPath attempts to load a config from the given file path. If the file
// doesn't exist, an empty config is returned.
func LoadFromPath(path string) (Config, error) {
cf := Config{
Path: path,
Servers: make(map[string]*ServerConfig),
}
content, err := os.ReadFile(path)
if err != nil {
// it's okay if the file is empty, we still return a valid config
if os.IsNotExist(err) {
return cf, nil
}
return cf, err
}
if err := json.Unmarshal(content, &cf); err != nil {
return cf, fmt.Errorf("unmarshaling %s: %w", path, err)
}
cf.Path = path
return cf, nil
}
// GetFilePermissionWarnings returns the following warnings based on the file permission:
// - one warning if the file is group-readable
// - one warning if the file is world-readable
// We want this because configuration may have sensitive information (eg: creds).
// A nil error is returned if the file doesn't exist.
func GetFilePermissionWarnings(path string) ([]string, error) {
// Permission bits on Windows are not representative
// of the actual ACLs set for a given file. As the owner
// bit is always used for all three bits on Windows, this check
// will always fail, even if the file has restricted access to admins only.
if runtime.GOOS == "windows" {
return nil, nil
}
info, err := os.Stat(path)
if err != nil {
if os.IsNotExist(err) {
return []string{}, nil
}
return []string{}, fmt.Errorf("get file info: %w", err)
}
var warnings []string
if info.Mode()&0040 > 0 {
warnings = append(warnings, fmt.Sprintf("Rancher configuration file %s is group-readable. This is insecure.", path))
}
if info.Mode()&0004 > 0 {
warnings = append(warnings, fmt.Sprintf("Rancher configuration file %s is world-readable. This is insecure.", path))
}
return warnings, nil
}
func (c Config) Write() error {
err := os.MkdirAll(filepath.Dir(c.Path), 0700)
if err != nil {
return err
}
logrus.Infof("Saving config to %s", c.Path)
p := c.Path
c.Path = ""
output, err := os.OpenFile(p, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
return err
}
defer output.Close()
return json.NewEncoder(output).Encode(c)
}
func (c Config) FocusedServer() (*ServerConfig, error) {
currentServer, found := c.Servers[c.CurrentServer]
if !found || currentServer == nil {
return nil, ErrNoConfigurationFound
}
return currentServer, nil
}
func (c ServerConfig) FocusedCluster() string {
cluster, _, ok := strings.Cut(c.Project, ":")
if !ok {
return ""
}
return cluster
}
func (c ServerConfig) FocusedProject() string {
return c.Project
}
func (c ServerConfig) KubeToken(key string) *ExecCredential {
return c.KubeCredentials[key]
}
func (c ServerConfig) EnvironmentURL() (string, error) {
url, err := baseURL(c.URL)
if err != nil {
return "", err
}
return url, nil
}
func baseURL(fullURL string) (string, error) {
idx := strings.LastIndex(fullURL, "/v3")
if idx == -1 {
u, err := url.Parse(fullURL)
if err != nil {
return "", err
}
newURL := url.URL{
Scheme: u.Scheme,
Host: u.Host,
}
return newURL.String(), nil
}
return fullURL[:idx], nil
}