blob: cca7913c827f8a46afb9c103476991290cae7012 [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package config
import (
"crypto/tls"
"fmt"
"net/http"
"net/http/cookiejar"
"os"
"path"
"path/filepath"
"strconv"
"time"
"github.com/gofrs/flock"
homedir "github.com/mitchellh/go-homedir"
ini "gopkg.in/ini.v1"
)
// Output formats
const (
COLUMN = "column"
CSV = "csv"
JSON = "json"
TABLE = "table"
TEXT = "text"
DEFAULT= "default"
)
// ServerProfile describes a management server
type ServerProfile struct {
URL string `ini:"url"`
Username string `ini:"username"`
Password string `ini:"password"`
Domain string `ini:"domain"`
APIKey string `ini:"apikey"`
SecretKey string `ini:"secretkey"`
Client *http.Client `ini:"-"`
}
// Core block describes common options for the CLI
type Core struct {
Prompt string `ini:"prompt"`
AsyncBlock bool `ini:"asyncblock"`
Timeout int `ini:"timeout"`
Output string `ini:"output"`
VerifyCert bool `ini:"verifycert"`
ProfileName string `ini:"profile"`
AutoComplete bool `ini:"autocomplete"`
}
// Config describes CLI config file and default options
type Config struct {
Dir string
ConfigFile string
HistoryFile string
LogFile string
HasShell bool
Core *Core
ActiveProfile *ServerProfile
}
func GetOutputFormats() []string {
return []string {"column", "csv", "json", "table", "text", "default"}
}
func CheckIfValuePresent(dataset []string, element string) bool {
for _, arg := range dataset {
if arg == element {
return true
}
}
return false
}
// CacheFile returns the path to the cache file for a server profile
func (c Config) CacheFile() string {
cacheDir := path.Join(c.Dir, "profiles")
cacheFileName := "cache"
if c.Core != nil && len(c.Core.ProfileName) > 0 {
cacheFileName = c.Core.ProfileName + ".cache"
}
checkAndCreateDir(cacheDir)
return path.Join(cacheDir, cacheFileName)
}
func hasAccess(path string) bool {
status := true
file, err := os.Open(path)
if (err != nil) {
status = false
}
file.Close()
return status
}
func checkAndCreateDir(path string) string {
if fileInfo, err := os.Stat(path); os.IsNotExist(err) || !fileInfo.IsDir() {
err := os.Mkdir(path, 0700)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
}
isAccsessible := hasAccess(path)
if !isAccsessible {
fmt.Println("User isn't authorized to access the config file")
os.Exit(1)
}
return path
}
func getDefaultConfigDir() string {
home, err := homedir.Dir()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
return checkAndCreateDir(path.Join(home, ".cmk"))
}
func defaultCoreConfig() Core {
return Core{
Prompt: "🐱",
AsyncBlock: true,
Timeout: 1800,
Output: JSON,
VerifyCert: true,
ProfileName: "localcloud",
AutoComplete: true,
}
}
func defaultProfile() ServerProfile {
return ServerProfile{
URL: "http://localhost:8080/client/api",
Username: "admin",
Password: "password",
Domain: "/",
APIKey: "",
SecretKey: "",
}
}
func defaultConfig() *Config {
configDir := getDefaultConfigDir()
defaultCoreConfig := defaultCoreConfig()
defaultProfile := defaultProfile()
return &Config{
Dir: configDir,
ConfigFile: path.Join(configDir, "config"),
HistoryFile: path.Join(configDir, "history"),
LogFile: path.Join(configDir, "log"),
HasShell: false,
Core: &defaultCoreConfig,
ActiveProfile: &defaultProfile,
}
}
var profiles []string
// GetProfiles returns list of available profiles
func GetProfiles() []string {
return profiles
}
func newHTTPClient(cfg *Config) *http.Client {
jar, _ := cookiejar.New(nil)
client := &http.Client{
Jar: jar,
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
TLSClientConfig: &tls.Config{InsecureSkipVerify: !cfg.Core.VerifyCert},
},
}
client.Timeout = time.Duration(time.Duration(cfg.Core.Timeout) * time.Second)
return client
}
func setActiveProfile(cfg *Config, profile *ServerProfile) {
cfg.ActiveProfile = profile
cfg.ActiveProfile.Client = newHTTPClient(cfg)
}
func reloadConfig(cfg *Config) *Config {
fileLock := flock.New(path.Join(getDefaultConfigDir(), "lock"))
err := fileLock.Lock()
if err != nil {
fmt.Println("Failed to grab config file lock, please try again")
return cfg
}
cfg = saveConfig(cfg)
fileLock.Unlock()
LoadCache(cfg)
return cfg
}
func readConfig(cfg *Config) *ini.File {
conf, err := ini.LoadSources(ini.LoadOptions{
IgnoreInlineComment: true,
}, cfg.ConfigFile)
if err != nil {
fmt.Printf("Fail to read config file: %v", err)
os.Exit(1)
}
return conf
}
func saveConfig(cfg *Config) *Config {
if _, err := os.Stat(cfg.Dir); err != nil {
os.Mkdir(cfg.Dir, 0700)
}
// Save on missing config
if _, err := os.Stat(cfg.ConfigFile); err != nil {
defaultCoreConfig := defaultCoreConfig()
defaultProfile := defaultProfile()
conf := ini.Empty()
conf.Section(ini.DEFAULT_SECTION).ReflectFrom(&defaultCoreConfig)
conf.Section(defaultCoreConfig.ProfileName).ReflectFrom(&defaultProfile)
conf.SaveTo(cfg.ConfigFile)
}
conf := readConfig(cfg)
core, err := conf.GetSection(ini.DEFAULT_SECTION)
if core == nil || err != nil {
defaultCore := defaultCoreConfig()
section, _ := conf.NewSection(ini.DEFAULT_SECTION)
section.ReflectFrom(&defaultCore)
cfg.Core = &defaultCore
} else {
// Write
if cfg.Core != nil {
conf.Section(ini.DEFAULT_SECTION).ReflectFrom(&cfg.Core)
}
// Update
core := new(Core)
conf.Section(ini.DEFAULT_SECTION).MapTo(core)
if (!conf.Section(ini.DEFAULT_SECTION).HasKey("autocomplete")) {
core.AutoComplete = true
}
cfg.Core = core
}
profile, err := conf.GetSection(cfg.Core.ProfileName)
if profile == nil {
activeProfile := defaultProfile()
section, _ := conf.NewSection(cfg.Core.ProfileName)
section.ReflectFrom(&activeProfile)
setActiveProfile(cfg, &activeProfile)
} else {
// Write
if cfg.ActiveProfile != nil {
conf.Section(cfg.Core.ProfileName).ReflectFrom(&cfg.ActiveProfile)
}
// Update
profile := new(ServerProfile)
conf.Section(cfg.Core.ProfileName).MapTo(profile)
setActiveProfile(cfg, profile)
}
// Save
conf.SaveTo(cfg.ConfigFile)
// Update available profiles list
profiles = []string{}
for _, profile := range conf.Sections() {
if profile.Name() == ini.DEFAULT_SECTION {
continue
}
profiles = append(profiles, profile.Name())
}
return cfg
}
// LoadProfile loads an existing profile
func (c *Config) LoadProfile(name string) {
Debug("Trying to load profile: " + name)
conf := readConfig(c)
section, err := conf.GetSection(name)
if err != nil || section == nil {
fmt.Printf("Unable to load profile '%s': %v", name, err)
os.Exit(1)
}
profile := new(ServerProfile)
conf.Section(name).MapTo(profile)
setActiveProfile(c, profile)
c.Core.ProfileName = name
}
// UpdateConfig updates and saves config
func (c *Config) UpdateConfig(key string, value string, update bool) {
switch key {
case "prompt":
c.Core.Prompt = value
case "asyncblock":
c.Core.AsyncBlock = value == "true"
case "display":
fallthrough
case "output":
c.Core.Output = value
case "timeout":
intValue, err := strconv.Atoi(value)
if err != nil {
fmt.Println("Error caught while setting timeout:", err)
}
c.Core.Timeout = intValue
case "profile":
c.Core.ProfileName = value
c.ActiveProfile = nil
case "url":
c.ActiveProfile.URL = value
case "username":
c.ActiveProfile.Username = value
case "password":
c.ActiveProfile.Password = value
case "domain":
c.ActiveProfile.Domain = value
case "apikey":
c.ActiveProfile.APIKey = value
case "secretkey":
c.ActiveProfile.SecretKey = value
case "verifycert":
c.Core.VerifyCert = value == "true"
case "debug":
if value == "true" || value == "on" {
EnableDebugging()
} else {
DisableDebugging()
}
case "autocomplete":
c.Core.AutoComplete = value == "true"
default:
fmt.Println("Invalid option provided:", key)
return
}
Debug("UpdateConfig key:", key, " value:", value, " update:", update)
if update {
reloadConfig(c)
}
}
// NewConfig creates or reload config and loads API cache
func NewConfig(configFilePath *string) *Config {
defaultConf := defaultConfig()
defaultConf.Core = nil
defaultConf.ActiveProfile = nil
if *configFilePath != "" {
defaultConf.ConfigFile, _ = filepath.Abs(*configFilePath)
if _, err := os.Stat(defaultConf.ConfigFile); os.IsNotExist(err) {
fmt.Println("Config file doesn't exist.")
os.Exit(1)
}
}
cfg := reloadConfig(defaultConf)
LoadCache(cfg)
return cfg
}