From 77c172b823b64ebface655681ab0749b9d2f7081 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Benencia?= Date: Fri, 13 Apr 2018 16:30:31 -0700 Subject: First public commit --- internal/environment/environment.go | 173 +++++++++++++++++++++++++++++++ internal/environment/environment_test.go | 61 +++++++++++ internal/environment/flags.go | 57 ++++++++++ 3 files changed, 291 insertions(+) create mode 100644 internal/environment/environment.go create mode 100644 internal/environment/environment_test.go create mode 100644 internal/environment/flags.go (limited to 'internal/environment') diff --git a/internal/environment/environment.go b/internal/environment/environment.go new file mode 100644 index 0000000..eac0430 --- /dev/null +++ b/internal/environment/environment.go @@ -0,0 +1,173 @@ +// Copyright 2018 ThousandEyes Inc. +// +// Licensed 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 environment + +import ( + "fmt" + "html/template" + "io/ioutil" + "net" + "os" + "path" + "path/filepath" + "regexp" + "sync" + + "github.com/thousandeyes/shoelaces/internal/event" + "github.com/thousandeyes/shoelaces/internal/log" + "github.com/thousandeyes/shoelaces/internal/mappings" + "github.com/thousandeyes/shoelaces/internal/server" + "github.com/thousandeyes/shoelaces/internal/templates" +) + +// Environment struct holds the shoelaces instance global data. +type Environment struct { + ConfigFile string + BaseURL string + HostnameMaps []mappings.HostnameMap + NetworkMaps []mappings.NetworkMap + ServerStates *server.States + EventLog *event.Log + ParamsBlacklist []string + Templates *templates.ShoelacesTemplates // Dynamic slc templates + StaticTemplates *template.Template // Static Templates + Environments []string // Valid config environments + Logger log.Logger + + Port int + Domain string + DataDir string + StaticDir string + EnvDir string + TemplateExtension string + MappingsFile string + Debug bool +} + +// New returns an initialized environment structure +func New() *Environment { + env := defaultEnvironment() + env.setFlags() + env.validateFlags() + + if env.Debug { + env.Logger = log.AllowDebug(env.Logger) + } + + env.BaseURL = fmt.Sprintf("%s:%d", env.Domain, env.Port) + env.Environments = env.initEnvOverrides() + + env.EventLog = &event.Log{} + + env.Logger.Info("component", "environment", "msg", "Override found", "environment", env.Environments) + + mappingsPath := path.Join(env.DataDir, env.MappingsFile) + if err := env.initMappings(mappingsPath); err != nil { + panic(err) + } + + env.initStaticTemplates() + env.Templates.ParseTemplates(env.Logger, env.DataDir, env.EnvDir, env.Environments, env.TemplateExtension) + server.StartStateCleaner(env.Logger, env.ServerStates) + + return env +} + +func defaultEnvironment() *Environment { + env := &Environment{} + env.NetworkMaps = make([]mappings.NetworkMap, 0) + env.HostnameMaps = make([]mappings.HostnameMap, 0) + env.ServerStates = &server.States{sync.RWMutex{}, make(map[string]*server.State)} + env.ParamsBlacklist = []string{"baseURL"} + env.Templates = templates.New() + env.Environments = make([]string, 0) + env.Logger = log.MakeLogger(os.Stdout) + + return env +} + +func (env *Environment) initStaticTemplates() { + staticTemplates := []string{ + path.Join(env.StaticDir, "templates/html/header.html"), + path.Join(env.StaticDir, "templates/html/index.html"), + path.Join(env.StaticDir, "templates/html/events.html"), + path.Join(env.StaticDir, "templates/html/mappings.html"), + path.Join(env.StaticDir, "templates/html/footer.html"), + } + + fmt.Println(env.StaticDir) + + for _, t := range staticTemplates { + if _, err := os.Stat(t); err != nil { + env.Logger.Error("component", "environment", "msg", "Template does not exists!", "environment", t) + os.Exit(1) + } + } + + env.StaticTemplates = template.Must(template.ParseFiles(staticTemplates...)) +} + +func (env *Environment) initEnvOverrides() []string { + var environments = make([]string, 0) + envPath := filepath.Join(env.DataDir, env.EnvDir) + files, err := ioutil.ReadDir(envPath) + if err == nil { + for _, f := range files { + if f.IsDir() { + environments = append(environments, f.Name()) + } + } + } + return environments +} + +func (env *Environment) initMappings(mappingsPath string) error { + configMappings := mappings.ParseYamlMappings(env.Logger, mappingsPath) + + for _, configNetMap := range configMappings.NetworkMaps { + _, ipnet, err := net.ParseCIDR(configNetMap.Network) + if err != nil { + return err + } + + netMap := mappings.NetworkMap{Network: ipnet, Script: initScript(configNetMap.Script)} + env.NetworkMaps = append(env.NetworkMaps, netMap) + } + + for _, configHostMap := range configMappings.HostnameMaps { + regex, err := regexp.Compile(configHostMap.Hostname) + if err != nil { + return err + } + + hostMap := mappings.HostnameMap{Hostname: regex, Script: initScript(configHostMap.Script)} + env.HostnameMaps = append(env.HostnameMaps, hostMap) + } + + return nil +} + +func initScript(configScript mappings.YamlScript) *mappings.Script { + mappingScript := &mappings.Script{ + Name: configScript.Name, + Environment: configScript.Environment, + Params: make(map[string]interface{}), + } + for key := range configScript.Params { + mappingScript.Params[key] = configScript.Params[key] + } + + return mappingScript +} diff --git a/internal/environment/environment_test.go b/internal/environment/environment_test.go new file mode 100644 index 0000000..8ffe88d --- /dev/null +++ b/internal/environment/environment_test.go @@ -0,0 +1,61 @@ +// Copyright 2018 ThousandEyes Inc. +// +// Licensed 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 environment + +import ( + "testing" + + "github.com/thousandeyes/shoelaces/internal/mappings" +) + +func TestDefaultEnvironment(t *testing.T) { + env := defaultEnvironment() + if env.BaseURL != "" { + t.Error("BaseURL should be empty string if instantiated directly.") + } + if len(env.HostnameMaps) != 0 { + t.Error("Hostname mappings should be empty") + } + if len(env.NetworkMaps) != 0 { + t.Error("Network mappings should be empty") + } + if len(env.ParamsBlacklist) != 1 && + env.ParamsBlacklist[0] != "baseURL" { + t.Error("ParamsBlacklist should have only baseURL") + } +} + +func TestInitScript(t *testing.T) { + params := make(map[string]string) + params["one"] = "one_value" + configScript := mappings.YamlScript{Name: "testscript", Params: params} + mappingScript := initScript(configScript) + if mappingScript.Name != "testscript" { + t.Errorf("Expected: %s\nGot: %s\n", "testscript", mappingScript.Name) + } + val, ok := mappingScript.Params["one"] + if !ok { + t.Error("Missing param") + } else { + v, ok := val.(string) + if !ok { + t.Error("Bad value type") + } else { + if v != "one_value" { + t.Error("Bad value") + } + } + } +} diff --git a/internal/environment/flags.go b/internal/environment/flags.go new file mode 100644 index 0000000..8250690 --- /dev/null +++ b/internal/environment/flags.go @@ -0,0 +1,57 @@ +// Copyright 2018 ThousandEyes Inc. +// +// Licensed 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 environment + +import ( + "fmt" + "os" + + "github.com/namsral/flag" +) + +func (env *Environment) setFlags() { + flag.StringVar(&env.ConfigFile, "config", "", "My config file") + flag.IntVar(&env.Port, "port", 8080, "The port where I'm going to listen") + flag.StringVar(&env.Domain, "domain", "localhost", "The address where I'm going to listen") + flag.StringVar(&env.DataDir, "data-dir", "", "Directory with mappings, configs, templates, etc.") + flag.StringVar(&env.StaticDir, "static-dir", "web", "A custom web directory with static files") + flag.StringVar(&env.EnvDir, "env-dir", "env_overrides", "Directory with overrides") + flag.StringVar(&env.TemplateExtension, "template-extension", ".slc", "Shoelaces template extension") + flag.StringVar(&env.MappingsFile, "mappings-file", "mappings.yaml", "My mappings YAML file") + flag.BoolVar(&env.Debug, "debug", false, "Debug mode") + + flag.Parse() +} + +func (env *Environment) validateFlags() { + error := false + + if env.DataDir == "" { + fmt.Println("[*] You must specify the data-dir parameter") + error = true + } + + if env.StaticDir == "" { + fmt.Println("[*] You must specify the data-dir parameter") + error = true + } + + if error { + fmt.Println("\nAvailable parameters:") + flag.PrintDefaults() + fmt.Println("\nParameters can be specified as environment variables, arguments or in a config file.") + os.Exit(1) + } +} -- cgit v1.2.3