Commit c80bb4c0 authored by Dmitry Volodin's avatar Dmitry Volodin
Browse files

St. Marco's Campanile branch: golang core implementation

parent c2275117
Pipeline #1795 passed with stage
in 41 seconds
.idea/*
noc_tower.egg-info/
build/
dist/
root/
.*
!.gitlab-ci.yml
!.gitignore
!.dockerignore
bin/
pkg/
var/
pip-selfcheck.json
local/
lib/
include/
bin/*
share/
*.pyc
.idea/
\ No newline at end of file
core/src/tower/vendor/*
core/src/github.com/
!core/src/tower/vendor/manifest
//----------------------------------------------------------------------
// Commands entrypoint
//----------------------------------------------------------------------
// Copyright (C) 2017
//----------------------------------------------------------------------
package command
import (
"errors"
)
type CommandInterface interface {
GetName() string
GetShort() string
Run() int
SetParser()
}
type Command struct {
name string
short string
}
func (c Command) GetName() string {
return c.name
}
func (c Command) GetShort() string {
return c.short
}
func (c Command) Run() int {
return 0
}
func (c Command) SetParser() {
}
func FindCmd(name string) (CommandInterface, error) {
for _, c := range CommandsList {
if c.GetName() == name {
return c, nil
}
}
return nil, errors.New("Invalid command")
}
var CommandsList = []CommandInterface{
cmdHelp,
cmdVersion,
cmdDump,
cmdCommit,
}
//----------------------------------------------------------------------
// tower commit
//----------------------------------------------------------------------
// Copyright (C) 2017, The NOC Project
//----------------------------------------------------------------------
package command
import (
"flag"
"fmt"
"tower/config"
)
type CommitCommand struct {
Command
}
var cmdCommit = CommitCommand{
Command{
"commit",
"Commit configuration from file",
},
}
var commit_file *string
func (c CommitCommand) SetParser() {
commit_file = flag.String("file", "", "Config file path")
}
func (c CommitCommand) Run() int {
if *commit_file == "" {
fmt.Printf("File path required\n")
return 1
}
cfg, err := config.ParseFile(*commit_file)
if err != nil {
fmt.Printf("Cannot read file '%s': %s\n", *commit_file, err)
return 1
}
fmt.Printf("CONFIG=%+v\n", cfg)
return 0
}
//----------------------------------------------------------------------
// tower dump
//----------------------------------------------------------------------
// Copyright (C) 2017, The NOC Project
//----------------------------------------------------------------------
package command
import (
"fmt"
)
type DumpCommand struct {
Command
}
var cmdDump = DumpCommand{
Command{
"dump",
"Dump current config",
},
}
func (c DumpCommand) Run() int {
fmt.Println("dump")
return 0
}
//----------------------------------------------------------------------
// tower help
//----------------------------------------------------------------------
// Copyright (C) 2017, The NOC Project
//----------------------------------------------------------------------
package command
import "fmt"
type HelpCommand struct {
Command
}
var cmdHelp = HelpCommand{
Command{
name: "help",
short: "help on help",
},
}
func (c HelpCommand) Run() int {
fmt.Println("Usage:")
for _, c := range CommandsList {
fmt.Printf("\t%8s - %s\n", c.GetName(), c.GetShort())
}
return 0
}
func Help() {
cmdHelp.Run()
}
//----------------------------------------------------------------------
// tower version
//----------------------------------------------------------------------
// Copyright (C) 2017, The NOC Project
//----------------------------------------------------------------------
package command
import (
"fmt"
"runtime"
)
const version = "0.1a"
func GetVersion() string {
return version
}
type VersionCommand struct {
Command
}
var cmdVersion = VersionCommand{
Command{
"version",
"help on version",
},
}
func (c VersionCommand) Run() int {
fmt.Printf("tower version %s %s/%s\n",
GetVersion(),
runtime.GOOS,
runtime.GOARCH,
)
return 0
}
//----------------------------------------------------------------------
// config structure
//----------------------------------------------------------------------
// Copyright (C) 2017, The NOC Project
//----------------------------------------------------------------------
package config
import (
"fmt"
"github.com/tylerb/gls"
yaml "gopkg.in/yaml.v2"
"io/ioutil"
"os"
)
const (
CURRENT_VERSION = FMT_V1
CONFIG_KEY = "cfg"
)
type Config struct {
Version string
Templates []Template `yaml:,flow`
Pools []Pool `yaml:,flow`
Roles []Role `yaml:,flow`
Datacenters []Datacenter `yaml:,flow`
Hosts []Host `yaml:,flow`
Services []Service `yaml:,flow`
}
func Parse(data []byte) (*Config, error) {
// Check config format Version
version, err := GetConfigFormatVersion(data)
if err != nil {
return nil, err
}
// Parse only current Version
// @todo: Legacy versions conversion
if version != CURRENT_VERSION {
return nil, fmt.Errorf("Expected Version '%s", CURRENT_VERSION)
}
// Parse config
// Pass gls context to unmarshal to use in local parsers
config := Config{}
gls.With(gls.Values{CONFIG_KEY: &config}, func() {
err = yaml.Unmarshal(data, &config)
})
if err != nil {
return nil, err
}
// @todo: Additional validation
return &config, nil
}
func ParseFile(path string) (*Config, error) {
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
data, err := ioutil.ReadAll(file)
if err != nil {
return nil, err
}
return Parse(data)
}
func (c *Config) GetPoolByName(name string) *Pool {
for i := range c.Pools {
if c.Pools[i].Name == name {
return &c.Pools[i]
}
}
return nil
}
func (c *Config) GetRoleByName(name string) *Role {
for i := range c.Roles {
if c.Roles[i].Name == name {
return &c.Roles[i]
}
}
return nil
}
func (c *Config) GetDatacenterByName(name string) *Datacenter {
for i := range c.Datacenters {
if c.Datacenters[i].Name == name {
return &c.Datacenters[i]
}
}
return nil
}
func (c *Config) GetTemplateByName(name string) *Template {
for i := range c.Templates {
if c.Templates[i].Name == name {
return &c.Templates[i]
}
}
return nil
}
func (c *Config) GetHostByName(name string) *Host {
for i := range c.Hosts {
if c.Hosts[i].Name == name {
return &c.Hosts[i]
}
}
return nil
}
//
// Parse array of strings as array of template references
//
func (c *Config) ParseTemplates(templates []string) ([]*Template, error) {
result := []*Template{}
for i := range templates {
tpl := c.GetTemplateByName(templates[i])
if tpl == nil {
return nil, fmt.Errorf("Invalid template '%s'", templates[i])
}
result = append(result, tpl)
}
return result, nil
}
//
// Parse array of strings as array of roles references
//
func (c *Config) ParseRoles(roles []string) ([]*Role, error) {
result := []*Role{}
for i := range roles {
tpl := c.GetRoleByName(roles[i])
if tpl == nil {
return nil, fmt.Errorf("Invalid role '%s'", roles[i])
}
result = append(result, tpl)
}
return result, nil
}
//----------------------------------------------------------------------
// Datacenter
//----------------------------------------------------------------------
// Copyright (C) 2017, The NOC Project
//----------------------------------------------------------------------
package config
import (
"fmt"
"github.com/tylerb/gls"
)
type Datacenter struct {
Name string
Description string
Templates []*Template
}
func (c *Datacenter) UnmarshalYAML(unmarshal func(interface{}) error) error {
// Auxiliary structure
var aux struct {
Name string
Description string
Templates []string
}
err := unmarshal(&aux)
if err != nil {
return err
}
//
if aux.Name == "" {
return fmt.Errorf("Empty datacenter name")
}
// Copy structure
c.Name = aux.Name
c.Description = aux.Description
// Parse templates
config := gls.Get(CONFIG_KEY).(*Config)
c.Templates, err = config.ParseTemplates(aux.Templates)
if err != nil {
return fmt.Errorf("datacenter %s: %s", c.Name, err)
}
return nil
}
//----------------------------------------------------------------------
// Host
//----------------------------------------------------------------------
// Copyright (C) 2017, The NOC Project
//----------------------------------------------------------------------
package config
import (
"fmt"
"github.com/tylerb/gls"
"strconv"
)
const (
HT_UNKNOWN = 0
HT_LINUX
HT_FREEBSD
HT_DOCKER
HT_SWARM
HT_K8S
HT_NOMAD
HT_MESOS
DEFAULT_HOST_PORT = 22
)
var HT_MAP = map[string]int{
"linux": HT_LINUX,
"freebsd": HT_FREEBSD,
"docker": HT_DOCKER,
"swarm": HT_SWARM,
"kubernetes": HT_K8S,
"nomad": HT_NOMAD,
"mesos": HT_MESOS,
}
type Host struct {
Name string
Description string
Type int
Address string
Port int
User string
Password string
Datacenter *Datacenter
Roles []*Role
Templates []*Template
}
func (c *Host) UnmarshalYAML(unmarshal func(interface{}) error) error {
// Auxiliary structure
var aux struct {
Name string
Description string
Type string
Address string
Port string
User string
Password string
Datacenter string
Roles []string
Templates []string
}
var port int
err := unmarshal(&aux)
if err != nil {
return err
}
//
if aux.Name == "" {
return fmt.Errorf("Empty host name")
}
// Check port
if aux.Port == "" {
port = DEFAULT_HOST_PORT
} else {
port, err = strconv.Atoi(aux.Port)
if err != nil {
return fmt.Errorf("host %s: Invalid port '%s'", aux.Name, aux.Port)
}
}
// Check type
htype, ok := HT_MAP[aux.Type]
if !ok {
return fmt.Errorf("host %s: Invalid host type '%s'", aux.Name, aux.Type)
}
// Dereference datacenter
if aux.Datacenter == "" {
return fmt.Errorf("host %s: Empty datacenter", aux.Name)
}
config := gls.Get(CONFIG_KEY).(*Config)
// Copy structure
c.Name = aux.Name
c.Description = aux.Description
c.Type = htype
c.Address = aux.Address
c.Port = port
c.User = aux.User
c.Password = aux.Password
// Get datacenter
c.Datacenter = config.GetDatacenterByName(aux.Datacenter)
if c.Datacenter == nil {
return fmt.Errorf("host %s: Invalid datacenter '%s'", aux.Name, aux.Datacenter)
}
// Parse roles
c.Roles, err = config.ParseRoles(aux.Roles)
if err != nil {
return fmt.Errorf("datacenter %s: %s", c.Name, err)
}
// Parse templates
c.Templates, err = config.ParseTemplates(aux.Templates)
if err != nil {
return fmt.Errorf("datacenter %s: %s", c.Name, err)
}
return nil
}
//----------------------------------------------------------------------
// Pool
//----------------------------------------------------------------------
// Copyright (C) 2017, The NOC Project
//----------------------------------------------------------------------
package config
import (
"fmt"
"github.com/tylerb/gls"
"regexp"
)
type Pool struct {
Name string
Description string
Templates []*Template
}
var rx_pool_name *regexp.Regexp
func (c *Pool) UnmarshalYAML(unmarshal func(interface{}) error) error {
// Auxiliary structure
var aux struct {
Name string
Description string
Templates []string
}
err := unmarshal(&aux)
if err != nil {
return err
}
//
if aux.Name == "" {
return fmt.Errorf("Empty pool name")
}
// Check name format
matched := rx_pool_name.Match([]byte(aux.Name))
if !matched {
return fmt.Errorf("Invalid pool name: '%s'", aux.Name)
}
// Copy structure
c.Name = aux.Name
c.Description = aux.Description
// Parse templates
config := gls.Get(CONFIG_KEY).(*Config)
c.Templates, err = config.ParseTemplates(aux.Templates)
if err != nil {
return fmt.Errorf("datacenter %s: %s", c.Name, err)
}
return nil
}
func init() {
rx_pool_name, _ = regexp.Compile("^[a-zA-Z][a-zA-Z0-9]*$")
}
//----------------------------------------------------------------------
// Role
//----------------------------------------------------------------------
// Copyright (C) 2017, The NOC Project
//----------------------------------------------------------------------
package config
import (
"fmt"
"net/url"
)
type Role struct {
Name string
Description string
Url string
}
func (c *Role) UnmarshalYAML(unmarshal func(interface{}) error) error {
// Auxiliary structure
var aux struct {
Name string
Description string
Url string
}
err := unmarshal(&aux)
if err != nil {
return err
}
// Check required parameters
if aux.Name == "" {
return fmt.Errorf("Empty role name")
}
if aux.Url == "" {
return fmt.Errorf("Empty URL for role '%s'", aux.Name)