Skip to content

Commit

Permalink
factor argument parsing out of LoadApp (#413)
Browse files Browse the repository at this point in the history
  • Loading branch information
tgross authored Jun 23, 2017
1 parent 59e964f commit 8449323
Show file tree
Hide file tree
Showing 6 changed files with 299 additions and 300 deletions.
156 changes: 1 addition & 155 deletions core/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package core

import (
"encoding/json"
"flag"
"fmt"
"os"
"strings"
Expand All @@ -14,7 +13,6 @@ import (
"github.com/joyent/containerpilot/discovery"
"github.com/joyent/containerpilot/events"
"github.com/joyent/containerpilot/jobs"
"github.com/joyent/containerpilot/subcommands"
"github.com/joyent/containerpilot/telemetry"
"github.com/joyent/containerpilot/watches"

Expand Down Expand Up @@ -48,145 +46,9 @@ func EmptyApp() *App {
return app
}

// LoadApp parses the commandline arguments and loads the config
func LoadApp() (*App, error) {

var (
versionFlag bool
templateFlag bool
reloadFlag bool

configFlag string
renderFlag string
maintFlag string

putMetricFlags MultiFlag
putEnvFlags MultiFlag
pingFlag bool
)

if !flag.Parsed() {
flag.BoolVar(&versionFlag, "version", false,
"Show version identifier and quit.")

flag.BoolVar(&templateFlag, "template", false,
"Render template and quit.")

flag.BoolVar(&reloadFlag, "reload", false,
"Reload a ContainerPilot process through its control socket.")

flag.StringVar(&configFlag, "config", "",
"File path to JSON5 configuration file. Defaults to CONTAINERPILOT env var.")

flag.StringVar(&renderFlag, "out", "",
`File path where to save rendered config file when '-template' is used.
Defaults to stdout ('-').`)

flag.StringVar(&maintFlag, "maintenance", "",
`Toggle maintenance mode for a ContainerPilot process through its control socket.
Options: '-maintenance enable' or '-maintenance disable'`)

flag.Var(&putMetricFlags, "putmetric",
`Update metrics of a ContainerPilot process through its control socket.
Pass metrics in the format: 'key=value'`)

flag.Var(&putEnvFlags, "putenv",
`Update environ of a ContainerPilot process through its control socket.
Pass environment in the format: 'key=value'`)

flag.BoolVar(&pingFlag, "ping", false,
"Check that the ContainerPilot control socket is up.")

flag.Parse()
}

if versionFlag {
fmt.Printf("Version: %s\nGitHash: %s\n", Version, GitHash)
os.Exit(0)
}

if configFlag == "" {
configFlag = os.Getenv("CONTAINERPILOT")
}

if templateFlag {
err := config.RenderConfig(configFlag, renderFlag)
if err != nil {
return nil, err
}
os.Exit(0)
}

if reloadFlag {
cmd, err := subcommands.Init(configFlag)
if err != nil {
return nil, err
}
if err := cmd.SendReload(); err != nil {
return nil,
fmt.Errorf("-reload: failed to run subcommand: %v", err)
}
os.Exit(0)
}

if maintFlag != "" {
cmd, err := subcommands.Init(configFlag)
if err != nil {
return nil, err
}
if err := cmd.SendMaintenance(maintFlag); err != nil {
return nil,
fmt.Errorf("-maintenance: failed to run subcommand: %v", err)
}
os.Exit(0)
}

if putEnvFlags.Len() != 0 {
cmd, err := subcommands.Init(configFlag)
if err != nil {
return nil, err
}
if err := cmd.SendEnviron(putEnvFlags.Values); err != nil {
return nil, fmt.Errorf("-putenv: failed to run subcommand: %v", err)
}
os.Exit(0)
}

if putMetricFlags.Len() != 0 {
cmd, err := subcommands.Init(configFlag)
if err != nil {
return nil, err
}
if err := cmd.SendMetric(putMetricFlags.Values); err != nil {
return nil,
fmt.Errorf("-putmetric: failed to run subcommand: %v", err)
}
os.Exit(0)
}

if pingFlag {
cmd, err := subcommands.Init(configFlag)
if err != nil {
return nil, err
}
if err := cmd.GetPing(); err != nil {
return nil, fmt.Errorf("-ping: failed: %v", err)
}
fmt.Println("ok")
os.Exit(0)
}

os.Setenv("CONTAINERPILOT_PID", fmt.Sprintf("%v", os.Getpid()))

app, err := NewApp(configFlag)
if err != nil {
return nil, err
}
return app, nil
}

// NewApp creates a new App from the config
func NewApp(configFlag string) (*App, error) {
os.Setenv("CONTAINERPILOT_PID", fmt.Sprintf("%v", os.Getpid()))
a := EmptyApp()
cfg, err := config.LoadConfig(configFlag)
if err != nil {
Expand Down Expand Up @@ -256,22 +118,6 @@ func (a *App) Run() {
}
}

// Render the command line args thru golang templating so we can
// interpolate environment variables
func getArgs(args []string) []string {
var renderedArgs []string
for _, arg := range args {
newArg, err := config.ApplyTemplate([]byte(arg))
if err != nil {
log.Errorf("unable to render command arguments template: %v", err)
renderedArgs = args // skip rendering on error
break
}
renderedArgs = append(renderedArgs, string(newArg))
}
return renderedArgs
}

// Terminate kills the application
func (a *App) Terminate() {
a.signalLock.Lock()
Expand Down
101 changes: 0 additions & 101 deletions core/app_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package core

import (
"flag"
"fmt"
"io/ioutil"
"os"
Expand Down Expand Up @@ -62,85 +61,6 @@ func TestWatchConfigRequiredFields(t *testing.T) {
assert.Error(t, err, "unable to parse watches: watch[name].interval must be > 0")
}

func TestInvalidConfigNoConfigFlag(t *testing.T) {
defer argTestCleanup(argTestSetup())
os.Args = []string{"this", "/testdata/test.sh", "invalid1", "--debug"}
if _, err := LoadApp(); err != nil && err.Error() != "-config flag is required" {
t.Errorf("expected error but got %s", err)
}
}

func TestInvalidConfigParseNoDiscovery(t *testing.T) {
defer argTestCleanup(argTestSetup())
f1 := testCfgToTempFile(t, "{}")
defer os.Remove(f1.Name())
os.Args = []string{"this", "-config", f1.Name()}
_, err := LoadApp()
assert.Error(t, err, "no discovery backend defined")
}

func TestInvalidConfigMissingFile(t *testing.T) {
defer argTestCleanup(argTestSetup())
os.Args = []string{"this", "-config", "/xxxx"}
_, err := LoadApp()
assert.Error(t, err,
"could not read config file: open /xxxx: no such file or directory")
}

func TestInvalidConfigParseNotJson(t *testing.T) {
defer argTestCleanup(argTestSetup())
f1 := testCfgToTempFile(t, "<>")
defer os.Remove(f1.Name())
os.Args = []string{"this", "-config", f1.Name()}
_, err := LoadApp()
assert.Error(t, fmt.Errorf("%s", err.Error()[:29]),
"parse error at line:col [1:1]")
}

func TestInvalidConfigParseTemplateError(t *testing.T) {
defer argTestCleanup(argTestSetup())
// this config is missing quotes around the template
f1 := testCfgToTempFile(t, `{"test": {{ .NO_SUCH_KEY }}, "test2": "hello"}`)
defer os.Remove(f1.Name())
os.Args = []string{"this", "-config", f1.Name()}
_, err := LoadApp()
assert.Error(t, fmt.Errorf("%s", err.Error()[:30]),
"parse error at line:col [1:10]")
}

func TestRenderArgs(t *testing.T) {
flags := []string{"-name", "{{ .HOSTNAME }}"}
expected := os.Getenv("HOSTNAME")
if expected == "" {
// not all environments use this variable as a hostname but
// we really just want to make sure it's being rendered
expected, _ = os.Hostname()
os.Setenv("HOSTNAME", expected)
}
if got := getArgs(flags)[1]; got != expected {
t.Errorf("expected %v but got %v for rendered hostname", expected, got)
}

// invalid template should just be returned unchanged
flags = []string{"-name", "{{ .HOSTNAME }"}
expected = "{{ .HOSTNAME }"
if got := getArgs(flags)[1]; got != expected {
t.Errorf("expected %v but got %v for unrendered hostname", expected, got)
}
}

func TestControlServerCreation(t *testing.T) {
f1 := testCfgToTempFile(t, `{"consul": "consul:8500"}`)
defer os.Remove(f1.Name())
app, err := NewApp(f1.Name())
if err != nil {
t.Fatalf("got error while initializing config: %v", err)
}
if app.ControlServer == nil {
t.Error("expected control server to not be nil")
}
}

func TestMetricServiceCreation(t *testing.T) {

f := testCfgToTempFile(t, `{
Expand Down Expand Up @@ -174,17 +94,6 @@ func TestMetricServiceCreation(t *testing.T) {
}
}

func TestPidEnvVar(t *testing.T) {
defer argTestCleanup(argTestSetup())
os.Args = []string{"this", "-config", "{}", "/testdata/test.sh"}
if _, err := LoadApp(); err == nil {
t.Fatalf("expected error in LoadApp but got none")
}
if pid := os.Getenv("CONTAINERPILOT_PID"); pid == "" {
t.Errorf("expected CONTAINERPILOT_PID to be set even on error")
}
}

// Test configuration reload
func TestReloadConfig(t *testing.T) {
cfg := &jobs.Config{
Expand Down Expand Up @@ -254,13 +163,3 @@ func testCfgToTempFile(t *testing.T, text string) *os.File {
}
return f
}

func argTestSetup() []string {
flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
flag.Usage = nil
return os.Args
}

func argTestCleanup(oldArgs []string) {
os.Args = oldArgs
}
Loading

0 comments on commit 8449323

Please sign in to comment.