start.go 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. package cmd
  2. import (
  3. "fmt"
  4. "os"
  5. "os/exec"
  6. "path/filepath"
  7. "runtime"
  8. "time"
  9. "github.com/porter-dev/porter/cli/cmd/docker"
  10. "k8s.io/client-go/util/homedir"
  11. "github.com/porter-dev/porter/cli/cmd/credstore"
  12. "github.com/spf13/cobra"
  13. "github.com/docker/docker/api/types/mount"
  14. )
  15. type startOps struct {
  16. insecure *bool
  17. skipKubeconfig *bool
  18. kubeconfigPath string
  19. contexts *[]string
  20. imageTag string `form:"required"`
  21. db string `form:"oneof=sqlite postgres"`
  22. }
  23. var opts = &startOps{}
  24. // startCmd represents the start command
  25. var startCmd = &cobra.Command{
  26. Args: func(cmd *cobra.Command, args []string) error {
  27. return nil
  28. },
  29. Use: "start",
  30. Short: "Starts a Porter instance using the Docker engine.",
  31. Run: func(cmd *cobra.Command, args []string) {
  32. closeHandler(stop)
  33. err := start(
  34. opts.imageTag,
  35. opts.kubeconfigPath,
  36. opts.db,
  37. *opts.contexts,
  38. *opts.insecure,
  39. *opts.skipKubeconfig,
  40. )
  41. if err != nil {
  42. fmt.Println("Error running start:", err.Error())
  43. fmt.Println("Shutting down...")
  44. err = stop()
  45. if err != nil {
  46. fmt.Println("Shutdown unsuccessful:", err.Error())
  47. }
  48. os.Exit(1)
  49. }
  50. },
  51. }
  52. func init() {
  53. rootCmd.AddCommand(startCmd)
  54. opts.insecure = startCmd.PersistentFlags().Bool(
  55. "insecure",
  56. false,
  57. "skip admin setup and authorization",
  58. )
  59. opts.skipKubeconfig = startCmd.PersistentFlags().Bool(
  60. "skip-kubeconfig",
  61. false,
  62. "skip initialization of the kubeconfig",
  63. )
  64. opts.contexts = startCmd.PersistentFlags().StringArray(
  65. "contexts",
  66. nil,
  67. "the list of contexts to use (defaults to the current context)",
  68. )
  69. startCmd.PersistentFlags().StringVar(
  70. &opts.db,
  71. "db",
  72. "sqlite",
  73. "the db to use, one of sqlite or postgres",
  74. )
  75. startCmd.PersistentFlags().StringVar(
  76. &opts.kubeconfigPath,
  77. "kubeconfig",
  78. "",
  79. "path to kubeconfig",
  80. )
  81. startCmd.PersistentFlags().StringVar(
  82. &opts.imageTag,
  83. "image-tag",
  84. "latest",
  85. "the Porter image tag to use",
  86. )
  87. }
  88. func stop() error {
  89. agent, err := docker.NewAgentFromEnv()
  90. if err != nil {
  91. return err
  92. }
  93. err = agent.StopPorterContainers()
  94. if err != nil {
  95. return err
  96. }
  97. return nil
  98. }
  99. func start(
  100. imageTag string,
  101. kubeconfigPath string,
  102. db string,
  103. contexts []string,
  104. insecure bool,
  105. skipKubeconfig bool,
  106. ) error {
  107. var username, pw string
  108. var err error
  109. home := homedir.HomeDir()
  110. outputConfPath := filepath.Join(home, ".porter", "porter.kubeconfig")
  111. containerConfPath := "/porter/porter.kubeconfig"
  112. port := 8080
  113. // if not insecure, or username/pw set incorrectly, prompt for new username/pw
  114. if username, pw, err = credstore.Get(); !insecure && err != nil {
  115. username, err = promptPlaintext("Email: ")
  116. if err != nil {
  117. return err
  118. }
  119. pw, err = promptPasswordWithConfirmation()
  120. if err != nil {
  121. return err
  122. }
  123. credstore.Set(username, pw)
  124. }
  125. if !skipKubeconfig {
  126. err = generate(
  127. kubeconfigPath,
  128. outputConfPath,
  129. false,
  130. contexts,
  131. )
  132. if err != nil {
  133. return err
  134. }
  135. }
  136. agent, err := docker.NewAgentFromEnv()
  137. if err != nil {
  138. return err
  139. }
  140. // the volume mounts to use
  141. mounts := make([]mount.Mount, 0)
  142. // the volumes passed to the Porter container
  143. volumesMap := make(map[string]struct{})
  144. if !skipKubeconfig {
  145. // add a bind mount with the kubeconfig
  146. mount := mount.Mount{
  147. Type: mount.TypeBind,
  148. Source: outputConfPath,
  149. Target: containerConfPath,
  150. ReadOnly: true,
  151. Consistency: mount.ConsistencyFull,
  152. }
  153. mounts = append(mounts, mount)
  154. }
  155. netID, err := agent.CreateBridgeNetworkIfNotExist("porter_network")
  156. if err != nil {
  157. return err
  158. }
  159. env := make([]string, 0)
  160. env = append(env, []string{
  161. "ADMIN_INIT=true",
  162. "ADMIN_EMAIL=" + username,
  163. "ADMIN_PASSWORD=" + pw,
  164. }...)
  165. switch db {
  166. case "sqlite":
  167. // check if sqlite volume exists, create it if not
  168. vol, err := agent.CreateLocalVolumeIfNotExist("porter_sqlite")
  169. if err != nil {
  170. return err
  171. }
  172. // create mount
  173. mount := mount.Mount{
  174. Type: mount.TypeVolume,
  175. Source: vol.Name,
  176. Target: "/sqlite",
  177. ReadOnly: false,
  178. Consistency: mount.ConsistencyFull,
  179. }
  180. mounts = append(mounts, mount)
  181. volumesMap[vol.Name] = struct{}{}
  182. env = append(env, []string{
  183. "SQL_LITE=true",
  184. "SQL_LITE_PATH=/sqlite/porter.db",
  185. }...)
  186. case "postgres":
  187. // check if postgres volume exists, create it if not
  188. vol, err := agent.CreateLocalVolumeIfNotExist("porter_postgres")
  189. if err != nil {
  190. return err
  191. }
  192. // pgMount is mount for postgres container
  193. pgMount := []mount.Mount{
  194. mount.Mount{
  195. Type: mount.TypeVolume,
  196. Source: vol.Name,
  197. Target: "/var/lib/postgresql/data",
  198. ReadOnly: false,
  199. Consistency: mount.ConsistencyFull,
  200. },
  201. }
  202. // create postgres container with mount
  203. startOpts := docker.PostgresOpts{
  204. Name: "porter_postgres",
  205. Image: "postgres:latest",
  206. Mounts: pgMount,
  207. VolumeMap: map[string]struct{}{
  208. "porter_postgres": struct{}{},
  209. },
  210. NetworkID: netID,
  211. Env: []string{
  212. "POSTGRES_USER=porter",
  213. "POSTGRES_PASSWORD=porter",
  214. "POSTGRES_DB=porter",
  215. },
  216. }
  217. pgID, err := agent.StartPostgresContainer(startOpts)
  218. if err != nil {
  219. return err
  220. }
  221. env = append(env, []string{
  222. "SQL_LITE=false",
  223. "DB_USER=porter",
  224. "DB_PASS=porter",
  225. "DB_NAME=porter",
  226. "DB_HOST=porter_postgres",
  227. "DB_PORT=5432",
  228. }...)
  229. defer agent.WaitForContainerStop(pgID)
  230. }
  231. // create Porter container
  232. // TODO -- look for unused port
  233. startOpts := docker.PorterStartOpts{
  234. Name: "porter_server",
  235. Image: "porter1/porter:" + imageTag,
  236. HostPort: uint(port),
  237. ContainerPort: 8080,
  238. Mounts: mounts,
  239. VolumeMap: volumesMap,
  240. NetworkID: netID,
  241. Env: env,
  242. }
  243. id, err := agent.StartPorterContainer(startOpts)
  244. if err != nil {
  245. return err
  246. }
  247. fmt.Println("Spinning up the Server...")
  248. time.Sleep(7 * time.Second)
  249. openBrowser(fmt.Sprintf("http://localhost:%d/login?email=%s", port, username))
  250. fmt.Printf("Server ready: listening on localhost:%d\n", port)
  251. agent.WaitForContainerStop(id)
  252. return nil
  253. }
  254. // openBrowser opens the specified URL in the default browser of the user.
  255. func openBrowser(url string) error {
  256. var cmd string
  257. var args []string
  258. switch runtime.GOOS {
  259. case "windows":
  260. cmd = "cmd"
  261. args = []string{"/c", "start"}
  262. case "darwin":
  263. cmd = "open"
  264. default: // "linux", "freebsd", "openbsd", "netbsd"
  265. cmd = "xdg-open"
  266. }
  267. args = append(args, url)
  268. return exec.Command(cmd, args...).Start()
  269. }