grim/convey

closing merged branch
hostnames
2017-10-13, Gary Kramlich
33eae19fcbbe
closing merged branch
/*
* Convey
* Copyright 2016-2017 Gary Kramlich <grim@reaperworld.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package main
import (
"fmt"
"os"
"strings"
"github.com/alecthomas/kingpin"
"github.com/aphistic/gomol"
"bitbucket.org/rw_grim/convey/config"
"bitbucket.org/rw_grim/convey/environment"
"bitbucket.org/rw_grim/convey/loaders/bitbucket"
"bitbucket.org/rw_grim/convey/loaders/convey"
"bitbucket.org/rw_grim/convey/logging"
"bitbucket.org/rw_grim/convey/runners"
"bitbucket.org/rw_grim/convey/ssh"
"bitbucket.org/rw_grim/convey/state"
)
const (
version = "0.10.3dev"
)
var (
app = kingpin.New("convey", "container runner").Version(version)
color = app.Flag("color", "Enable colorized output").Default("true").Bool()
configLoader = app.Flag("config-loader", "Select the configuration loader").Short('l').Default("convey").Enum("convey", "bitbucket")
configFile = app.Flag("config", "The config file name to use").Short('f').String()
cpuShares = app.Flag("cpu-shares", "The amount of cpu shares to give to a run task").Short('c').String()
dockerConfig = app.Flag("docker-config", "Location of docker client config files").String()
env = app.Flag("env", "Set environment variables").Short('e').Strings()
forceSequential = app.Flag("force-sequential", "Don't run anything concurrently").Short('S').Default("False").Bool()
graphviz = app.Flag("graphviz", "Output a graphviz diagram of the config file").Short('g').Default("False").Bool()
keep = app.Flag("keep", "Keep the workspace volume").Short('k').Hidden().Default("False").Bool()
listTasks = app.Flag("list-tasks", "List the supported tasks").Short('L').Default("false").Bool()
listMetaPlans = app.Flag("list-meta-plans", "List the meta plans that are available").Short('M').Default("false").Bool()
listPlans = app.Flag("list-plans", "List the plans that are available").Short('P').Default("false").Bool()
memory = app.Flag("memory", "The ammount of memor to give the run task").Short('m').String()
showConfig = app.Flag("show-config", "Show a dump of the config file").Short('C').Hidden().Default("false").Bool()
sshAgent = app.Flag("ssh-agent", "A shortcut for --ssh-identity=*").Default("false").Bool()
sshIdentities = app.Flag("ssh-identity", "Enable ssh-agent for the given identities").Strings()
taskTimeout = app.Flag("timeout", "The maximum amount of time a task can run. 0 to disable. Units must be specified.").Default("15m").Duration()
verbose = app.Flag("verbose", "Be more verbose").Short('v').Default("False").Bool()
planNames = app.Arg("plan", "The plan or list of plans to run in specified order").Strings()
)
func main() {
exitCode := 0
defer func() {
os.Exit(exitCode)
}()
app.Parse(os.Args[1:])
// now load the config
var loader config.Loader
switch *configLoader {
case "convey":
loader = &convey.Loader{}
case "bitbucket":
loader = &bitbucket.Loader{}
}
// if a config file was not provided search for the loader's default file
if *configFile == "" {
for _, filename := range loader.Filenames() {
if _, err := os.Stat(filename); os.IsNotExist(err) {
continue
}
*configFile = filename
break
}
}
// now make sure we found a config file
if *configFile == "" {
fmt.Printf("config file not found, looking for %s\n", strings.Join(loader.Filenames(), ","))
os.Exit(1)
}
cfg, err := config.LoadFile(*configFile, loader)
if err != nil {
fmt.Printf("%s\n", err)
os.Exit(1)
}
// setup logging
if err := logging.Setup(*color, *verbose); err != nil {
fmt.Printf("failed to setup logging: %s\n", err)
os.Exit(1)
}
defer gomol.ShutdownLoggers()
// find our default environment variables and then update them with the
// values from the command line
defEnv := environment.Initialize()
// if the user specified the shortcut, add * to the list of acceptable keys
if *sshAgent {
*sshIdentities = append(*sshIdentities, "*")
}
// now merge in the keys from the config
*sshIdentities = append(*sshIdentities, cfg.SSHIdentities...)
// now check if we have any keys and make sure one of them is usable
enableSSHAgent, err := ssh.ShouldEnable(*sshIdentities)
if err != nil {
fmt.Printf("%s\n", err)
os.Exit(1)
}
st := state.New()
st.KeepWorkspace = *keep
st.ForceSequential = *forceSequential
st.EnableSSHAgent = enableSSHAgent
st.TaskTimeout = *taskTimeout
st.Environment = environment.Merge(defEnv, *env)
st.DockerConfig = *dockerConfig
st.CPUShares = *cpuShares
st.Memory = *memory
if err := st.Valid(); err != nil {
fmt.Printf("%s\n", err)
exitCode = 1
return
}
var runner runners.Runner
if *graphviz {
runner = &runners.Graphviz{}
} else if *listMetaPlans {
runner = &runners.ListMetaPlans{}
} else if *listPlans {
runner = &runners.ListPlans{}
} else if *listTasks {
runner = &runners.ListTasks{}
} else if *showConfig {
runner = &runners.ShowConfig{}
} else {
runner = &runners.Default{}
}
if len(*planNames) == 0 {
*planNames = []string{loader.DefaultPlan()}
}
// resolve the plan name with the load and options
realPlans := []string{}
for _, planName := range *planNames {
if metaPlan, found := cfg.MetaPlans[planName]; found {
realPlans = append(realPlans, metaPlan.Plans...)
} else {
realPlans = append(realPlans, loader.ResolvePlanName(planName, cfg, st))
}
}
exitCode = runner.Run(cfg, realPlans, *env, st)
}