diff --git a/.goxc.json b/.goxc.json index 7e2b81e..577513a 100644 --- a/.goxc.json +++ b/.goxc.json @@ -9,6 +9,10 @@ "repository": "utils", "subject": "pacesys" }, + "publish-github": { + "owner": "gondor", + "repository": "depcon" + }, "debs": { "metadata": { "description": "Depcon - Mesos/Marathon, Kubernetes, Compose and ECS deployer", diff --git a/Makefile b/Makefile index 524803b..c18b0e1 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ -VERSION = 0.7 +VERSION = 0.8.1 GO_FMT = gofmt -s -w -l . -GO_XC = goxc -os="linux darwin windows" +GO_XC = goxc -os="linux darwin windows" -tasks-="rmbin" GOXC_FILE = .goxc.local.json @@ -13,8 +13,14 @@ goxc: $(shell echo '{\n "ConfigVersion": "0.9",\n "PackageVersion": "$(VERSION)",' > $(GOXC_FILE)) $(shell echo ' "TaskSettings": {' >> $(GOXC_FILE)) $(shell echo ' "bintray": {\n "apikey": "$(BINTRAY_APIKEY)"' >> $(GOXC_FILE)) + $(shell echo ' },' >> $(GOXC_FILE)) + $(shell echo ' "publish-github": {' >> $(GOXC_FILE)) + $(shell echo ' "apikey": "$(GITHUB_APIKEY)",' >> $(GOXC_FILE)) + $(shell echo ' "body": "",' >> $(GOXC_FILE)) + $(shell echo ' "include": "*.zip,*.tar.gz,*.deb,depcon_$(VERSION)_linux_amd64-bin"' >> $(GOXC_FILE)) $(shell echo ' }\n } \n}' >> $(GOXC_FILE)) $(GO_XC) + cp build/$(VERSION)/linux_amd64/depcon build/$(VERSION)/depcon_0.8.1_linux_amd64-bin deps: go get @@ -24,3 +30,6 @@ format: bintray: $(GO_XC) bintray + +github: + $(GO_XC) publish-github diff --git a/README.md b/README.md index fd9455d..b5c7ad3 100644 --- a/README.md +++ b/README.md @@ -26,14 +26,14 @@ Depcon makes managing clusters that run docker containers a breeze. It offers t ### Binary Installation -You can download the binaries (ver 0.7) +You can download the binaries (ver 0.8) - * Architecture i386 [ [linux](https://dl.bintray.com//content/pacesys/utils/depcon_0.7_linux_386.tar.gz?direct) / [windows](https://dl.bintray.com//content/pacesys/utils/depcon_0.7_windows_386.zip?direct) / [darwin](https://dl.bintray.com//content/pacesys/utils/depcon_0.7_darwin_386.zip?direct) ] - * Architecture amd64 [ [linux](https://dl.bintray.com//content/pacesys/utils/depcon_0.7_linux_amd64.tar.gz?direct) / [windows](https://dl.bintray.com//content/pacesys/utils/depcon_0.7_windows_amd64.zip?direct) / [darwin](https://dl.bintray.com//content/pacesys/utils/depcon_0.7_darwin_amd64.zip?direct) ] + * Architecture i386 [ [linux](https://dl.bintray.com//content/pacesys/utils/depcon_0.8_linux_386.tar.gz?direct) / [windows](https://dl.bintray.com//content/pacesys/utils/depcon_0.8_windows_386.zip?direct) / [darwin](https://dl.bintray.com//content/pacesys/utils/depcon_0.8_darwin_386.zip?direct) ] + * Architecture amd64 [ [linux](https://dl.bintray.com//content/pacesys/utils/depcon_0.8_linux_amd64.tar.gz?direct) / [windows](https://dl.bintray.com//content/pacesys/utils/depcon_0.8_windows_amd64.zip?direct) / [darwin](https://dl.bintray.com//content/pacesys/utils/depcon_0.8_darwin_amd64.zip?direct) ] Or by installing via deb packages (ver 0.7) - * [ [amd64](https://dl.bintray.com//content/pacesys/utils/depcon_0.7_amd64.deb?direct) / [armhf](https://dl.bintray.com//content/pacesys/utils/depcon_0.7_armhf.deb?direct) ] + * [ [amd64](https://dl.bintray.com//content/pacesys/utils/depcon_0.8_amd64.deb?direct) / [armhf](https://dl.bintray.com//content/pacesys/utils/depcon_0.8_armhf.deb?direct) ] ### Build and Install the Binaries from Source diff --git a/cliconfig/create_config.go b/cliconfig/create_config.go index 3c17911..b06172d 100644 --- a/cliconfig/create_config.go +++ b/cliconfig/create_config.go @@ -16,6 +16,25 @@ we are only dealing with a single service and know what it is. Root single environment` ) +func CreateMemoryMarathonConfig(host, user, password string) *ConfigFile { + configFile, _ := Load("") + configFile.RootService = true + configFile.Format = "column" + serviceEnv := &ServiceConfig{ + Name: "memory", + HostUrl: host, + Username: user, + Password: password, + } + configEnv := &ConfigEnvironment{ + Marathon: serviceEnv, + } + configFile.Environments[serviceEnv.Name] = configEnv + configFile.DefaultEnv = serviceEnv.Name + //configFile.Save() + return configFile +} + func CreateNewConfigFromUserInput() *ConfigFile { fmt.Println("\n-------------------------------[ Generating Initital Configuration ]-------------------------------") @@ -28,7 +47,6 @@ func CreateNewConfigFromUserInput() *ConfigFile { } configFile.Environments[serviceEnv.Name] = configEnv configFile.Save() - return configFile } diff --git a/commands/depcon.go b/commands/depcon.go index f38b124..5a1a0e0 100644 --- a/commands/depcon.go +++ b/commands/depcon.go @@ -14,11 +14,16 @@ import ( ) const ( - FlagVerbose = "verbose" - FlagEnv = "env" - ViperEnv = "env_name" - EnvHelp = `Specifies the Environment name to use (eg. test | prod | etc). This can be omitted if only a single environment has been defined` - DepConHelp = ` + FlagVerbose = "verbose" + EnvDepconMode = "DEPCON_MODE" + ModeMarathon = "marathon" + EnvMarathonHost = "MARATHON_HOST" + EnvMarathonUser = "MARATHON_USER" + EnvMarathonPass = "MARATHON_PASS" + FlagEnv = "env" + ViperEnv = "env_name" + EnvHelp = `Specifies the Environment name to use (eg. test | prod | etc). This can be omitted if only a single environment has been defined` + DepConHelp = ` DEPCON (Deploy Containers) == Version: %s - Built: %s == @@ -70,11 +75,20 @@ func Execute() { configFile = file executeWithExistingConfig() } else { - logger.Logger().Error("%s file not found. Generating initial configuration", file.Filename()) - configFile = cliconfig.CreateNewConfigFromUserInput() + if len(os.Getenv(EnvDepconMode)) > 0 && os.Getenv(EnvDepconMode) == ModeMarathon { + configFile = marathonConfigFromEnv() + executeWithExistingConfig() + } else { + logger.Logger().Error("%s file not found. Generating initial configuration", file.Filename()) + configFile = cliconfig.CreateNewConfigFromUserInput() + } } } +func marathonConfigFromEnv() *cliconfig.ConfigFile { + return cliconfig.CreateMemoryMarathonConfig(os.Getenv(EnvMarathonHost), os.Getenv(EnvMarathonUser), os.Getenv(EnvMarathonPass)) +} + func determineEnvironment() string { envName := findEnvNameFromArgs() diff --git a/commands/marathon/group_cmds.go b/commands/marathon/group_cmds.go index 63f7272..7f98a4a 100644 --- a/commands/marathon/group_cmds.go +++ b/commands/marathon/group_cmds.go @@ -4,7 +4,9 @@ import ( "fmt" "github.com/gondor/depcon/marathon" "github.com/gondor/depcon/pkg/cli" + "github.com/gondor/depcon/pkg/encoding" "github.com/spf13/cobra" + "os" "strings" ) @@ -40,8 +42,14 @@ var groupCreateCmd = &cobra.Command{ Run: createGroup, } +var groupConvertFileCmd = &cobra.Command{ + Use: "convert [from.(json | yaml)] [to.(json | yaml)]", + Short: "Utilty to convert an group file from json to yaml or yaml to json.", + Run: convertGroupFile, +} + func init() { - groupCmd.AddCommand(groupListCmd, groupGetCmd, groupCreateCmd, groupDestroyCmd) + groupCmd.AddCommand(groupListCmd, groupGetCmd, groupCreateCmd, groupDestroyCmd, groupConvertFileCmd) // Destroy Flags groupDestroyCmd.Flags().BoolP(WAIT_FLAG, "w", false, "Wait for destroy to complete") @@ -107,3 +115,14 @@ func createGroup(cmd *cobra.Command, args []string) { } cli.Output(AppGroup{result}, e) } + +func convertGroupFile(cmd *cobra.Command, args []string) { + if cli.EvalPrintUsage(cmd.Usage, args, 2) { + os.Exit(1) + } + if err := encoding.ConvertFile(args[0], args[1], &marathon.Groups{}); err != nil { + cli.Output(nil, err) + os.Exit(1) + } + fmt.Printf("Source file %s has been re-written into new format in %s\n\n", args[0], args[1]) +} diff --git a/marathon/application.go b/marathon/application.go index 3c9b5af..7bafded 100644 --- a/marathon/application.go +++ b/marathon/application.go @@ -21,7 +21,9 @@ var ( ErrorAppExists = errors.New("The application already exists") ErrorGroupExists = errors.New("The group already exists") ErrorInvalidAppId = errors.New("The application identifier is invalid") + ErrorInvalidGroupId = errors.New("The group identifier is invalid") ErrorNoAppExists = errors.New("The application does not exist. Create an application before updating") + ErrorGropAppExists = errors.New("The group does not exist. Create a group before updating") ErrorAppParamsMissing = errors.New("One or more ${PARAMS} that were defined in the app configuration could not be resolved.") ) @@ -111,10 +113,10 @@ func (c *MarathonClient) UpdateApplication(app *Application, wait bool) (*Applic return nil, resp.Error } if wait { - if err := c.WaitForDeployment(result.DeploymentID, c.determineTimeout(nil)); err != nil { + if err := c.WaitForDeployment(result.DeploymentID, c.determineTimeout(app)); err != nil { return nil, err } - err := c.WaitForApplication(id, c.determineTimeout(nil)) + err := c.WaitForApplication(id, c.determineTimeout(app)) if err != nil { return nil, err } diff --git a/marathon/group.go b/marathon/group.go index deb2b87..8c8810b 100644 --- a/marathon/group.go +++ b/marathon/group.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/gondor/depcon/pkg/encoding" "github.com/gondor/depcon/pkg/envsubst" + "github.com/gondor/depcon/pkg/httpclient" "os" "time" ) @@ -43,6 +44,18 @@ func (c *MarathonClient) CreateGroup(group *Group, wait, force bool) (*Group, er result := new(DeploymentID) resp := c.http.HttpPost(c.marathonUrl(API_GROUPS), group, result) if resp.Error != nil { + if resp.Error == httpclient.ErrorMessage { + if resp.Status == 409 { + if force { + return c.UpdateGroup(group, wait) + } + return nil, ErrorGroupExists + } + if resp.Status == 422 { + return nil, ErrorInvalidGroupId + } + return nil, fmt.Errorf("Error occurred (Status %v) Body -> %s", resp.Status, resp.Content) + } return nil, resp.Error } if wait { @@ -53,6 +66,28 @@ func (c *MarathonClient) CreateGroup(group *Group, wait, force bool) (*Group, er return group, nil } +func (c *MarathonClient) UpdateGroup(group *Group, wait bool) (*Group, error) { + log.Info("Update Group '%s', wait = %v", group.GroupID, wait) + result := new(DeploymentID) + resp := c.http.HttpPut(c.marathonUrl(API_GROUPS), group, result) + + if resp.Error != nil { + if resp.Error == httpclient.ErrorMessage { + if resp.Status == 422 { + return nil, ErrorGropAppExists + } + } + return nil, resp.Error + } + if wait { + if err := c.WaitForDeployment(result.DeploymentID, c.determineTimeout(nil)); err != nil { + return nil, err + } + } + // Get the latest version of the application to return + return c.GetGroup(group.GroupID) +} + func (c *MarathonClient) ListGroups() (*Groups, error) { groups := new(Groups)