From 68f26411962b5e1f688552a5cb62c9bf0a7d07b4 Mon Sep 17 00:00:00 2001 From: Christopher Stingl Date: Mon, 5 Mar 2018 13:18:46 -0500 Subject: [PATCH] [CS] Split setup out of deploy and move things to support this architecture --- cmd/app.go | 8 ++ cmd/deploy.go | 245 ++----------------------------------------------- cmd/ocean.go | 8 ++ cmd/setup.go | 250 ++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 275 insertions(+), 236 deletions(-) create mode 100644 cmd/setup.go diff --git a/cmd/app.go b/cmd/app.go index ac95f6f..079533f 100644 --- a/cmd/app.go +++ b/cmd/app.go @@ -54,3 +54,11 @@ func validateGit() error { return nil } + +func displayServerInfo() error { + ip, _ := exec.Command("docker-machine", "ip", serverName).Output() + fmt.Printf("\nssh root@%s -i ~/.docker/machine/machines/%s/id_rsa", strings.TrimSpace(string(ip)), serverName) + fmt.Printf("\nopen http://%s\n", ip) + + return nil +} diff --git a/cmd/deploy.go b/cmd/deploy.go index 662f28d..f408169 100644 --- a/cmd/deploy.go +++ b/cmd/deploy.go @@ -2,9 +2,6 @@ package cmd import ( "fmt" - "os" - "os/exec" - "strings" "github.com/fatih/color" "github.com/fatih/structs" @@ -21,108 +18,38 @@ var deployCmd = &cobra.Command{ Short: "Deploy to DigitalOcean using docker", RunE: func(cmd *cobra.Command, args []string) error { - return deploy.Run() + return deploy.runDeploy() }, } -type Deploy struct { - AppName string - Branch string - Environment string - Init bool - Key string - Service string - Tag string -} - -var deploy = Deploy{} +var deploy = Project{} var projectName string var serverName string func init() { - deployCmd.Flags().StringVarP(&deploy.AppName, "app-name", "a", "", "The name for the application") deployCmd.Flags().StringVarP(&deploy.Branch, "branch", "b", "master", "Branch to use for deployment") deployCmd.Flags().StringVarP(&deploy.Environment, "environment", "e", "production", "Setting for the GO_ENV variable") - deployCmd.Flags().BoolVar(&deploy.Init, "init", false, "Initialize the server along with deployment. Run this the first time.") - deployCmd.Flags().StringVarP(&deploy.Key, "key", "k", "", "API Key for the service you are deploying to") deployCmd.Flags().StringVarP(&deploy.Tag, "tag", "t", "", "Tag to use for deployment. Overrides banch.") oceanCmd.AddCommand(deployCmd) } -func (d Deploy) Run() error { - projectName = d.AppName - serverName = fmt.Sprintf("%s-%s", projectName, d.Environment) +func (p Project) runDeploy() error { + projectName = p.AppName + serverName = fmt.Sprintf("%s-%s", projectName, p.Environment) if msg, ok := validateMachine("machineInstalled", serverName); !ok { return errors.New(msg) } - if d.Init { - if err := provisionProcess(d); err != nil { - return errors.WithStack(err) - } - } else { - if err := deployProcess(d); err != nil { - return errors.WithStack(err) - } + if err := deployProcess(p); err != nil { + return errors.WithStack(err) } - return nil -} - -func provisionProcess(d Deploy) error { - green := color.New(color.FgGreen).SprintFunc() - color.Blue("\n==> PROVISIONING SERVER: %v.\n", green(serverName)) - g := makr.New() - g.Add(makr.Func{ - Runner: func(root string, data makr.Data) error { - return validateGit() - }, - }) - g.Add(makr.Func{ - Runner: func(root string, data makr.Data) error { - if msg, ok := validateMachine("isUnique", serverName); !ok { - return errors.New(msg) - } - return nil - }, - }) - g.Add(makr.Func{ - Runner: func(root string, data makr.Data) error { - return createCloudServer(data) - }, - }) - g.Add(makr.Func{ - Runner: func(root string, data makr.Data) error { - return createSwapFile() - }, - }) - g.Add(makr.Func{ - Runner: func(root string, data makr.Data) error { - return createDeployKeys() - }, - }) - g.Add(makr.Func{ - Runner: func(root string, data makr.Data) error { - return cloneProject() - }, - }) - g.Add(makr.Func{ - Runner: func(root string, data makr.Data) error { - return setupProject(data) - }, - }) - g.Add(makr.Func{ - Runner: func(root string, data makr.Data) error { - return displayServerInfo() - }, - }) - - return g.Run(".", structs.Map(d)) + return nil } -func deployProcess(d Deploy) error { +func deployProcess(d Project) error { green := color.New(color.FgGreen).SprintFunc() color.Blue("\n==> DEPLOYING TO SERVER: %v.\n", green(serverName)) @@ -164,152 +91,6 @@ func deployProcess(d Deploy) error { } -func createCloudServer(d makr.Data) error { - - green := color.New(color.FgGreen).SprintFunc() - - color.Blue("\n==> Creating docker machine: %s\n", green(serverName)) - - var k string - if d["Key"] != "" { - k = d["Key"].(string) - } else { - fmt.Println("Enter your write enabled Digital Ocean API KEY or create one with the link below.") - fmt.Println("https://cloud.digitalocean.com/settings/api/tokens/new") - k = requestUserInput("Please enter your DigitalOcean Token:") - } - - driver := "--driver=digitalocean" - accessToken := fmt.Sprintf("--digitalocean-access-token=%s", k) - serverSize := "--digitalocean-size=s-1vcpu-1gb" - - cmd := exec.Command("docker-machine", "create", serverName, driver, accessToken, serverSize) - cmd.Stdin = os.Stdin - cmd.Stderr = os.Stderr - cmd.Stdout = os.Stdout - - if err := cmd.Run(); err != nil { - errors.WithStack(err) - } - - return nil -} - -func createSwapFile() error { - color.Blue("\n==> Creating Swapfile") - cmds := []string{"dd if=/dev/zero of=/swapfile bs=2k count=1024k"} - cmds = append(cmds, "mkswap /swapfile") - cmds = append(cmds, "chmod 600 /swapfile") - cmds = append(cmds, "swapon /swapfile") - cmds = append(cmds, "bash -c \"echo '/swapfile none swap sw 0 0 ' >> /etc/fstab\"") - - if err := remoteCmd(strings.Join(cmds[:], " && ")); err != nil { - return errors.WithStack(err) - } - - return nil -} - -func createDeployKeys() error { - color.Blue("\n==> Creating Deploy Key") - cmd := fmt.Sprintf("bash -c \"echo | ssh-keygen -q -N '' -t rsa -b 4096 -C 'deploy@%s'\"", projectName) - - if err := remoteCmd(cmd); err != nil { - return errors.WithStack(err) - } - - color.Yellow("\n\nPlease add this to your project's deploy keys on Github or Gitlab:") - remoteCmd("tail .ssh/id_rsa.pub") - fmt.Println("") - - return nil -} - -func cloneProject() error { - // TODO: Check docker-machine if it has git to determine if this is needed - remoteCmd("apt-get install git") - - r := requestUserInput("Please enter the repo to deploy from (Example: git@github.com:username/project.git):") - - color.Blue("\n==> Cloning Project") - - if err := remoteCmd("ssh-keyscan github.com >> ~/.ssh/known_hosts"); err != nil { - return errors.WithStack(err) - } - - if err := remoteCmd(fmt.Sprintf("bash -c \"yes yes | git clone %s buffaloproject\"", r)); err != nil { - return errors.WithStack(err) - } - - // TODO: Check for database.yml file and check if database.yml.example exists - if _, err := os.Stat("./database.yml"); err == nil { - remoteCmd("bash -c \"cp buffaloproject/database.yml.example buffaloproject/database.yml\"") - } - - return nil -} - -func setupProject(d makr.Data) error { - green := color.New(color.FgGreen).SprintFunc() - magenta := color.New(color.FgMagenta).SprintFunc() - blue := color.New(color.FgBlue).SprintFunc() - color.Blue("\n==> Setting Up Project. (This may take a few minutes)") - - buffaloEnv := d["Environment"].(string) - - color.Blue("\n==> CREATING: %s", green("Docker Network")) - remoteCmd("docker network create --driver bridge buffalonet") - - color.Blue("\n==> CREATING: %s", green("Docker Image")) - remoteCmd("docker build -t buffaloimage -f buffaloproject/Dockerfile buffaloproject") - - color.Blue("\n==> CREATING: %s", green("Docker Database Container")) - remoteCmd(fmt.Sprintf("docker container run -it --name buffalodb -v /root/db_volume:/var/lib/postgresql/data --network=buffalonet -e POSTGRES_USER=admin -e POSTGRES_PASSWORD=password -e POSTGRES_DB=buffalo_%s -d postgres", buffaloEnv)) - - if err := setupEnvVars(); err != nil { - return errors.WithStack(err) - } - - dbURL := fmt.Sprintf("DATABASE_URL=postgres://admin:password@buffalodb:5432/buffalo_%s?sslmode=disable", buffaloEnv) - color.Blue("\n==> CREATING: %s", green("Docker Web Container")) - if err := remoteCmd(fmt.Sprintf("docker container run -it --name buffaloweb -v /root/buffaloproject:/app -p 80:3000 --network=buffalonet --env-file /root/buffaloproject/env.list -e GO_ENV=%s -e %s -d buffaloimage", buffaloEnv, dbURL)); err != nil { - return errors.WithStack(err) - } - - if _, err := os.Stat("./env.list"); err == nil { - if err := os.Remove("./env.list"); err != nil { - return errors.WithStack(err) - } - } - - emoji.Printf("\n%s :beers: %s :beers: %s\n", blue("========="), magenta("INITIAL SERVER SETUP & DEPLOYMENT COMPLETE"), blue("=========")) - return nil -} - -func setupEnvVars() error { - ev := requestUserInput("Enter the ENV variables for your project with a space between each: (eg. SAMPLE=test FOO=bar)") - e := strings.Split(ev, " ") - - f, err := os.Create("./env.list") - if err != nil { - return errors.WithStack(err) - } - defer f.Close() - - for _, s := range e { - sn := fmt.Sprintf("%s\n", s) - if _, err := f.WriteString(sn); err != nil { - return errors.WithStack(err) - } - } - - if err := copyFileToRemoteProject("./env.list"); err != nil { - return errors.WithStack(err) - } - - return nil -} - func updateProject(d makr.Data) error { color.Blue("\n==> Updating Project") co := fmt.Sprint(d["Branch"].(string)) @@ -346,11 +127,3 @@ func deployProject(d makr.Data) error { emoji.Printf("\n========= :beers: %s :beers: =========\n", magenta("DEPLOYMENT COMPLETE")) return nil } - -func displayServerInfo() error { - ip, _ := exec.Command("docker-machine", "ip", serverName).Output() - fmt.Printf("\nssh root@%s -i ~/.docker/machine/machines/%s/id_rsa", strings.TrimSpace(string(ip)), serverName) - fmt.Printf("\nopen http://%s\n", ip) - - return nil -} diff --git a/cmd/ocean.go b/cmd/ocean.go index 47bf0b1..5aa2c19 100644 --- a/cmd/ocean.go +++ b/cmd/ocean.go @@ -16,6 +16,14 @@ var oceanCmd = &cobra.Command{ }, } +type Project struct { + AppName string + Branch string + Environment string + Key string + Tag string +} + func init() { rootCmd.AddCommand(oceanCmd) } diff --git a/cmd/setup.go b/cmd/setup.go new file mode 100644 index 0000000..a2732a5 --- /dev/null +++ b/cmd/setup.go @@ -0,0 +1,250 @@ +package cmd + +import ( + "fmt" + "os" + "os/exec" + "strings" + + "github.com/fatih/color" + "github.com/fatih/structs" + "github.com/gobuffalo/makr" + "github.com/pkg/errors" + "github.com/spf13/cobra" + emoji "gopkg.in/kyokomi/emoji.v1" +) + +// setupCmd represents the setup command +var setupCmd = &cobra.Command{ + Use: "setup", + Aliases: []string{"s"}, + Short: "A brief description of your command", + + RunE: func(cmd *cobra.Command, args []string) error { + return setup.runSetup() + }, +} + +var setup = Project{} + +func init() { + setupCmd.Flags().StringVarP(&setup.AppName, "app-name", "a", "", "The name for the application") + setupCmd.Flags().StringVarP(&setup.Key, "key", "k", "", "API Key for the service you are deploying to") + setupCmd.Flags().StringVarP(&setup.Branch, "branch", "b", "master", "Branch to use for deployment") + setupCmd.Flags().StringVarP(&setup.Environment, "environment", "e", "production", "Setting for the GO_ENV variable") + setupCmd.Flags().StringVarP(&setup.Tag, "tag", "t", "", "Tag to use for deployment. Overrides banch.") + oceanCmd.AddCommand(setupCmd) +} + +func (p Project) runSetup() error { + projectName = p.AppName + serverName = fmt.Sprintf("%s-%s", projectName, p.Environment) + + if msg, ok := validateMachine("machineInstalled", serverName); !ok { + return errors.New(msg) + } + + if err := provisionProcess(p); err != nil { + return errors.WithStack(err) + } + + return nil +} + +func provisionProcess(p Project) error { + green := color.New(color.FgGreen).SprintFunc() + color.Blue("\n==> PROVISIONING SERVER: %v.\n", green(serverName)) + + g := makr.New() + g.Add(makr.Func{ + Runner: func(root string, data makr.Data) error { + return validateGit() + }, + }) + g.Add(makr.Func{ + Runner: func(root string, data makr.Data) error { + if msg, ok := validateMachine("isUnique", serverName); !ok { + return errors.New(msg) + } + return nil + }, + }) + g.Add(makr.Func{ + Runner: func(root string, data makr.Data) error { + return createCloudServer(data) + }, + }) + g.Add(makr.Func{ + Runner: func(root string, data makr.Data) error { + return createSwapFile() + }, + }) + g.Add(makr.Func{ + Runner: func(root string, data makr.Data) error { + return createDeployKeys() + }, + }) + g.Add(makr.Func{ + Runner: func(root string, data makr.Data) error { + return cloneProject() + }, + }) + g.Add(makr.Func{ + Runner: func(root string, data makr.Data) error { + return setupProject(data) + }, + }) + g.Add(makr.Func{ + Runner: func(root string, data makr.Data) error { + return displayServerInfo() + }, + }) + + return g.Run(".", structs.Map(p)) +} + +func createCloudServer(d makr.Data) error { + + green := color.New(color.FgGreen).SprintFunc() + + color.Blue("\n==> Creating docker machine: %s\n", green(serverName)) + + var k string + if d["Key"] != "" { + k = d["Key"].(string) + } else { + fmt.Println("Enter your write enabled Digital Ocean API KEY or create one with the link below.") + fmt.Println("https://cloud.digitalocean.com/settings/api/tokens/new") + k = requestUserInput("Please enter your DigitalOcean Token:") + } + + driver := "--driver=digitalocean" + accessToken := fmt.Sprintf("--digitalocean-access-token=%s", k) + serverSize := "--digitalocean-size=s-1vcpu-1gb" + + cmd := exec.Command("docker-machine", "create", serverName, driver, accessToken, serverSize) + cmd.Stdin = os.Stdin + cmd.Stderr = os.Stderr + cmd.Stdout = os.Stdout + + if err := cmd.Run(); err != nil { + errors.WithStack(err) + } + + return nil +} + +func createSwapFile() error { + color.Blue("\n==> Creating Swapfile") + cmds := []string{"dd if=/dev/zero of=/swapfile bs=2k count=1024k"} + cmds = append(cmds, "mkswap /swapfile") + cmds = append(cmds, "chmod 600 /swapfile") + cmds = append(cmds, "swapon /swapfile") + cmds = append(cmds, "bash -c \"echo '/swapfile none swap sw 0 0 ' >> /etc/fstab\"") + + if err := remoteCmd(strings.Join(cmds[:], " && ")); err != nil { + return errors.WithStack(err) + } + + return nil +} + +func createDeployKeys() error { + color.Blue("\n==> Creating Deploy Key") + cmd := fmt.Sprintf("bash -c \"echo | ssh-keygen -q -N '' -t rsa -b 4096 -C 'deploy@%s'\"", projectName) + + if err := remoteCmd(cmd); err != nil { + return errors.WithStack(err) + } + + color.Yellow("\n\nPlease add this to your project's deploy keys on Github or Gitlab:") + remoteCmd("tail .ssh/id_rsa.pub") + fmt.Println("") + + return nil +} + +func cloneProject() error { + // TODO: Check docker-machine if it has git to determine if this is needed + remoteCmd("apt-get install git") + + r := requestUserInput("Please enter the repo to deploy from (Example: git@github.com:username/project.git):") + + color.Blue("\n==> Cloning Project") + + if err := remoteCmd("ssh-keyscan github.com >> ~/.ssh/known_hosts"); err != nil { + return errors.WithStack(err) + } + + if err := remoteCmd(fmt.Sprintf("bash -c \"yes yes | git clone %s buffaloproject\"", r)); err != nil { + return errors.WithStack(err) + } + + // TODO: Check for database.yml file and check if database.yml.example exists + if _, err := os.Stat("./database.yml"); err == nil { + remoteCmd("bash -c \"cp buffaloproject/database.yml.example buffaloproject/database.yml\"") + } + + return nil +} + +func setupProject(d makr.Data) error { + green := color.New(color.FgGreen).SprintFunc() + magenta := color.New(color.FgMagenta).SprintFunc() + blue := color.New(color.FgBlue).SprintFunc() + color.Blue("\n==> Setting Up Project. (This may take a few minutes)") + + buffaloEnv := d["Environment"].(string) + + color.Blue("\n==> CREATING: %s", green("Docker Network")) + remoteCmd("docker network create --driver bridge buffalonet") + + color.Blue("\n==> CREATING: %s", green("Docker Image")) + remoteCmd("docker build -t buffaloimage -f buffaloproject/Dockerfile buffaloproject") + + color.Blue("\n==> CREATING: %s", green("Docker Database Container")) + remoteCmd(fmt.Sprintf("docker container run -it --name buffalodb -v /root/db_volume:/var/lib/postgresql/data --network=buffalonet -e POSTGRES_USER=admin -e POSTGRES_PASSWORD=password -e POSTGRES_DB=buffalo_%s -d postgres", buffaloEnv)) + + if err := setupEnvVars(); err != nil { + return errors.WithStack(err) + } + + dbURL := fmt.Sprintf("DATABASE_URL=postgres://admin:password@buffalodb:5432/buffalo_%s?sslmode=disable", buffaloEnv) + color.Blue("\n==> CREATING: %s", green("Docker Web Container")) + if err := remoteCmd(fmt.Sprintf("docker container run -it --name buffaloweb -v /root/buffaloproject:/app -p 80:3000 --network=buffalonet --env-file /root/buffaloproject/env.list -e GO_ENV=%s -e %s -d buffaloimage", buffaloEnv, dbURL)); err != nil { + return errors.WithStack(err) + } + + if _, err := os.Stat("./env.list"); err == nil { + if err := os.Remove("./env.list"); err != nil { + return errors.WithStack(err) + } + } + + emoji.Printf("\n%s :beers: %s :beers: %s\n", blue("========="), magenta("INITIAL SERVER SETUP & DEPLOYMENT COMPLETE"), blue("=========")) + return nil +} + +func setupEnvVars() error { + ev := requestUserInput("Enter the ENV variables for your project with a space between each: (eg. SAMPLE=test FOO=bar)") + e := strings.Split(ev, " ") + + f, err := os.Create("./env.list") + if err != nil { + return errors.WithStack(err) + } + defer f.Close() + + for _, s := range e { + sn := fmt.Sprintf("%s\n", s) + if _, err := f.WriteString(sn); err != nil { + return errors.WithStack(err) + } + } + + if err := copyFileToRemoteProject("./env.list"); err != nil { + return errors.WithStack(err) + } + + return nil +}