Files
xc/backend/conductor/conductor.go
Pavel Vorobyov 7e2dec0ef0 project move
2019-09-24 11:04:48 +03:00

255 lines
5.4 KiB
Go

package conductor
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"path"
"regexp"
"strings"
"time"
"github.com/viert/xc/config"
"github.com/viert/xc/store"
"github.com/viert/xc/term"
)
// New creates a new instance of Conductor backend
func New(cfg *config.XCConfig) (*Conductor, error) {
c := &Conductor{
cacheTTL: cfg.CacheTTL,
cacheDir: cfg.CacheDir,
hosts: make([]*store.Host, 0),
groups: make([]*store.Group, 0),
workgroups: make([]*store.WorkGroup, 0),
datacenters: make([]*store.Datacenter, 0),
}
options := cfg.BackendCfg.Options
// workgroups configuration
wgString, found := options["work_groups"]
if !found || wgString == "" {
c.workgroupNames = make([]string, 0)
} else {
splitExpr := regexp.MustCompile(`\s*,\s*`)
c.workgroupNames = splitExpr.Split(wgString, -1)
}
// url configuration
url, found := options["url"]
if !found {
return nil, fmt.Errorf("Inventoree backend URL is not configured")
}
c.url = url
return c, nil
}
// Hosts exported backend method
func (c *Conductor) Hosts() []*store.Host {
return c.hosts
}
// Groups exported backend method
func (c *Conductor) Groups() []*store.Group {
return c.groups
}
// WorkGroups exported backend method
func (c *Conductor) WorkGroups() []*store.WorkGroup {
return c.workgroups
}
// Datacenters exported backend method
func (c *Conductor) Datacenters() []*store.Datacenter {
return c.datacenters
}
// Reload forces reloading data from HTTP(S)
func (c *Conductor) Reload() error {
err := c.loadRemote()
if err != nil {
// trying to use cache
return c.loadLocal()
}
return nil
}
// Load tries to load data from cache unless it's expired
// In case of cache expiration or absense it triggers Reload()
func (c *Conductor) Load() error {
var err error
if c.cacheExpired() {
return c.Reload()
}
// trying to use cache
err = c.loadLocal()
if err != nil {
// if it failed, trying to get data from remote
return c.loadRemote()
}
return nil
}
func (c *Conductor) loadLocal() error {
data, err := ioutil.ReadFile(c.cacheFilename())
if err != nil {
return err
}
lc := new(cache)
err = json.Unmarshal(data, lc)
if err != nil {
return err
}
c.extractCache(lc)
term.Warnf("Hosts loaded from cache\n")
return nil
}
func (c *Conductor) cacheExpired() bool {
st, err := os.Stat(c.cacheFilename())
if err != nil {
if os.IsNotExist(err) {
// no cache in general means that it's been expired
return true
}
}
modifiedAt := st.ModTime()
return modifiedAt.Add(c.cacheTTL).Before(time.Now())
}
func (c *Conductor) cacheFilename() string {
var wglist string
if len(c.workgroupNames) > 0 {
wglist = strings.Join(c.workgroupNames, "_")
} else {
wglist = "all"
}
fn := fmt.Sprintf("cnd_cache_%s.json", wglist)
return path.Join(c.cacheDir, fn)
}
func (c *Conductor) saveCache(lc *cache) error {
_, err := os.Stat(c.cacheDir)
if err != nil && os.IsNotExist(err) {
err = os.MkdirAll(c.cacheDir, 0755)
if err != nil {
return fmt.Errorf("Error creating cache dir: %s", err)
}
}
f, err := os.Create(c.cacheFilename())
if err != nil {
return err
}
defer f.Close()
data, err := json.Marshal(lc)
if err != nil {
return err
}
f.Write(data)
return nil
}
func (c *Conductor) extractCache(lc *cache) {
c.datacenters = make([]*store.Datacenter, 0)
c.workgroups = make([]*store.WorkGroup, 0)
c.groups = make([]*store.Group, 0)
c.hosts = make([]*store.Host, 0)
for _, dc := range lc.Datacenters {
c.datacenters = append(c.datacenters, &store.Datacenter{
ID: dc.ID,
Name: dc.Name,
Description: dc.Description,
ParentID: dc.ParentID,
})
}
for _, wg := range lc.WorkGroups {
c.workgroups = append(c.workgroups, &store.WorkGroup{
ID: wg.ID,
Name: wg.Name,
Description: wg.Description,
})
}
for _, g := range lc.Groups {
group := &store.Group{
ID: g.ID,
Name: g.Name,
Description: g.Description,
Tags: g.Tags,
WorkGroupID: g.WorkGroupID,
}
if len(g.ParentIDs) > 0 {
group.ParentID = g.ParentIDs[0]
}
c.groups = append(c.groups, group)
}
for _, h := range lc.Hosts {
c.hosts = append(c.hosts, &store.Host{
ID: h.ID,
FQDN: h.FQDN,
Aliases: h.Aliases,
Tags: h.Tags,
GroupID: h.GroupID,
DatacenterID: h.DatacenterID,
})
}
}
func (c *Conductor) httpGet(path string) ([]byte, error) {
client := &http.Client{}
url := fmt.Sprintf("%s%s", c.url, path)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
if resp.StatusCode != 200 {
return nil, fmt.Errorf("Status code %d while fetching %s", resp.StatusCode, url)
}
return ioutil.ReadAll(resp.Body)
}
func (c *Conductor) loadRemote() error {
var data []byte
var err error
term.Warnf("Loading executer data...\n")
path := "/api/v1/open/executer_data"
if len(c.workgroupNames) > 0 {
wglist := strings.Join(c.workgroupNames, ",")
path += fmt.Sprintf("?work_groups=%s", wglist)
}
data, err = c.httpGet(path)
if err != nil {
return err
}
apiResponse := new(api)
err = json.Unmarshal(data, apiResponse)
if err != nil {
return err
}
lc := apiResponse.Data
err = c.saveCache(lc)
if err != nil {
term.Errorf("Error saving cacne: %s\n", err)
} else {
term.Successf("Cache saved to %s\n", c.cacheFilename())
}
c.extractCache(lc)
return nil
}