Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor: Config to Application State #138

Merged
merged 19 commits into from
May 2, 2016
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
243 changes: 102 additions & 141 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,44 +5,26 @@ import (
"bytes"
"encoding/json"
"errors"
"flag"
"fmt"
"io/ioutil"
"os"
"os/exec"
"strings"
"sync"

"github.com/joyent/containerpilot/backends"
"github.com/joyent/containerpilot/discovery"
"github.com/joyent/containerpilot/services"
"github.com/joyent/containerpilot/tasks"
"github.com/joyent/containerpilot/telemetry"
"github.com/joyent/containerpilot/utils"
)

var (
// Version is the version for this build, set at build time via LDFLAGS
Version string
// GitHash is the short-form commit hash of this build, set at build time
GitHash string
log "github.com/Sirupsen/logrus"
)

// Passing around config as a context to functions would be the ideomatic way.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know you're WIP on this, but this whole comment will need reworking.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops!

// But we need to support configuration reload from signals and have that reload
// effect function calls in the main goroutine. Wherever possible we should be
// accessing via `GetConfig` at the "top" of a goroutine and then use the config
// as context for a function after that.
var (
globalConfig *Config
configLock = new(sync.RWMutex)
)

func GetConfig() *Config {
configLock.RLock()
defer configLock.RUnlock()
return globalConfig
}

// Config is the top-level ContainerPilot Configuration
type Config struct {
Expand All @@ -58,90 +40,19 @@ type Config struct {
BackendsConfig json.RawMessage `json:"backends,omitempty"`
TasksConfig json.RawMessage `json:"tasks,omitempty"`
TelemetryConfig json.RawMessage `json:"telemetry,omitempty"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not necessarily in scope for this PR, but omitempty is really only used when we serialize a struct, right? Maybe we don't really need this annotation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, we can remove it - we really don't re-serialize the config anyway, so the extra tag options are mostly useless.

Services []*services.Service
Backends []*backends.Backend
Tasks []*tasks.Task
Telemetry *telemetry.Telemetry
PreStartCmd *exec.Cmd
PreStopCmd *exec.Cmd
PostStopCmd *exec.Cmd
Command *exec.Cmd
QuitChannels []chan bool
ConfigFlag string

ConfigFlag string
}

const (
// Amount of time to wait before killing the application
defaultStopTimeout int = 5
)

func LoadConfig() (*Config, error) {

var configFlag string
var versionFlag bool

if !flag.Parsed() {
flag.StringVar(&configFlag, "config", "",
"JSON config or file:// path to JSON config file.")
flag.BoolVar(&versionFlag, "version", false, "Show version identifier and quit.")
flag.Parse()
}
if versionFlag {
fmt.Printf("Version: %s\nGitHash: %s\n", Version, GitHash)
os.Exit(0)
}
if configFlag == "" {
configFlag = os.Getenv("CONTAINERPILOT")
}

if cfg, err := parseConfig(configFlag); err != nil {
return nil, err
} else {
return initializeConfig(cfg)
}
}

func ReloadConfig(configFlag string) (*Config, error) {
if cfg, err := parseConfig(configFlag); err != nil {
return nil, err
} else {
return initializeConfig(cfg)
}
}

func initializeConfig(cfg *Config) (*Config, error) {
// ParseDiscoveryService ...
func (cfg *Config) ParseDiscoveryService() (discovery.DiscoveryService, error) {
var discoveryService discovery.DiscoveryService
discoveryCount := 0

// onStart has been deprecated for preStart. Remove in 2.0
if cfg.PreStart != nil && cfg.OnStart != nil {
fmt.Println("The onStart option has been deprecated in favor of preStart. ContainerPilot will use only the preStart option provided")
}

// alias the onStart behavior to preStart
if cfg.PreStart == nil && cfg.OnStart != nil {
fmt.Println("The onStart option has been deprecated in favor of preStart. ContainerPilot will use the onStart option as a preStart")
cfg.PreStart = cfg.OnStart
}

preStartCmd, err := utils.ParseCommandArgs(cfg.PreStart)
if err != nil {
return nil, fmt.Errorf("Could not parse `preStart`: %s", err)
}
cfg.PreStartCmd = preStartCmd

preStopCmd, err := utils.ParseCommandArgs(cfg.PreStop)
if err != nil {
return nil, fmt.Errorf("Could not parse `preStop`: %s", err)
}
cfg.PreStopCmd = preStopCmd

postStopCmd, err := utils.ParseCommandArgs(cfg.PostStop)
if err != nil {
return nil, fmt.Errorf("Could not parse `postStop`: %s", err)
}
cfg.PostStopCmd = postStopCmd

for _, discoveryBackend := range []string{"Consul", "Etcd"} {
switch discoveryBackend {
case "Consul":
Expand All @@ -156,75 +67,124 @@ func initializeConfig(cfg *Config) (*Config, error) {
}
}
}

if discoveryCount == 0 {
return nil, errors.New("No discovery backend defined")
} else if discoveryCount > 1 {
return nil, errors.New("More than one discovery backend defined")
}
return discoveryService, nil
}

func parseCommand(name string, args json.RawMessage) (*exec.Cmd, error) {
cmd, err := utils.ParseCommandArgs(args)
if err != nil {
return nil, fmt.Errorf("Could not parse `%s`: %s", name, err)
}
return cmd, nil
}

// InitLogging ...
func (cfg *Config) InitLogging() error {
if cfg.LogConfig != nil {
err := cfg.LogConfig.init()
if err != nil {
return nil, err
}
return cfg.LogConfig.init()
}
return nil
}

if cfg.StopTimeout == 0 {
cfg.StopTimeout = defaultStopTimeout
// ParsePreStart ...
func (cfg *Config) ParsePreStart() (*exec.Cmd, error) {
// onStart has been deprecated for preStart. Remove in 2.0
if cfg.PreStart != nil && cfg.OnStart != nil {
log.Warnf("The onStart option has been deprecated in favor of preStart. ContainerPilot will use only the preStart option provided")
}

if backends, err := backends.NewBackends(cfg.BackendsConfig,
discoveryService); err != nil {
// alias the onStart behavior to preStart
if cfg.PreStart == nil && cfg.OnStart != nil {
log.Warnf("The onStart option has been deprecated in favor of preStart. ContainerPilot will use the onStart option as a preStart")
cfg.PreStart = cfg.OnStart
}
return parseCommand("preStart", cfg.PreStart)
}

// ParsePreStop ...
func (cfg *Config) ParsePreStop() (*exec.Cmd, error) {
return parseCommand("preStop", cfg.PreStop)
}

// ParsePostStop ...
func (cfg *Config) ParsePostStop() (*exec.Cmd, error) {
return parseCommand("postStop", cfg.PostStop)
}

// ParseBackends ...
func (cfg *Config) ParseBackends(discoveryService discovery.DiscoveryService) ([]*backends.Backend, error) {
backends, err := backends.NewBackends(cfg.BackendsConfig, discoveryService)
if err != nil {
return nil, err
} else {
cfg.Backends = backends
}
return backends, nil
}

if services, err := services.NewServices(cfg.ServicesConfig,
discoveryService); err != nil {
// ParseServices ...
func (cfg *Config) ParseServices(discoveryService discovery.DiscoveryService) ([]*services.Service, error) {
services, err := services.NewServices(cfg.ServicesConfig, discoveryService)
if err != nil {
return nil, err
} else {
cfg.Services = services
}
return services, nil
}

if cfg.TelemetryConfig != nil {
if t, err := telemetry.NewTelemetry(cfg.TelemetryConfig); err != nil {
return nil, err
} else {
cfg.Telemetry = t
// create a new service for Telemetry
if telemetryService, err := services.NewService(
t.ServiceName,
t.Poll,
t.Port,
t.TTL,
t.Interfaces,
t.Tags,
discoveryService); err != nil {
return nil, err
} else {
cfg.Services = append(cfg.Services, telemetryService)
}
}
// ParseStopTimeout ...
func (cfg *Config) ParseStopTimeout() (int, error) {
if cfg.StopTimeout == 0 {
return defaultStopTimeout, nil
}
return cfg.StopTimeout, nil
}

if cfg.TasksConfig != nil {
tasks, err := tasks.NewTasks(cfg.TasksConfig)
if err != nil {
return nil, err
}
cfg.Tasks = tasks
// ParseTelemetry ...
func (cfg *Config) ParseTelemetry() (*telemetry.Telemetry, error) {
if cfg.TelemetryConfig == nil {
return nil, nil
}
t, err := telemetry.NewTelemetry(cfg.TelemetryConfig)
if err != nil {
return nil, err
}
return t, nil
}

configLock.Lock()
globalConfig = cfg
configLock.Unlock()
// CreateTelemetryService ...
func CreateTelemetryService(t *telemetry.Telemetry, discoveryService discovery.DiscoveryService) (*services.Service, error) {
// create a new service for Telemetry
svc, err := services.NewService(
t.ServiceName,
t.Poll,
t.Port,
t.TTL,
t.Interfaces,
t.Tags,
discoveryService)
if err != nil {
return nil, err
}
return svc, nil
}

return cfg, nil
// ParseTasks ...
func (cfg *Config) ParseTasks() ([]*tasks.Task, error) {
if cfg.TasksConfig == nil {
return nil, nil
}
tasks, err := tasks.NewTasks(cfg.TasksConfig)
if err != nil {
return nil, err
}
return tasks, nil
}

func parseConfig(configFlag string) (*Config, error) {
// ParseConfig ...
func ParseConfig(configFlag string) (*Config, error) {
if configFlag == "" {
return nil, errors.New("-config flag is required")
}
Expand All @@ -245,15 +205,16 @@ func parseConfig(configFlag string) (*Config, error) {
return nil, fmt.Errorf(
"Could not apply template to config: %s", err)
}
cfg, err := unmarshalConfig(template)
cfg, err := UnmarshalConfig(template)
if cfg != nil {
// store so we can reload
cfg.ConfigFlag = configFlag
}
return cfg, err
}

func unmarshalConfig(data []byte) (*Config, error) {
// UnmarshalConfig unmarshalls the raw config bytes into a Config struct
func UnmarshalConfig(data []byte) (*Config, error) {
config := &Config{}
if err := json.Unmarshal(data, &config); err != nil {
syntax, ok := err.(*json.SyntaxError)
Expand Down
Loading