start.go 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  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. fmt.Println("Please register your admin account with an email and password:")
  116. username, err = promptPlaintext("Email: ")
  117. if err != nil {
  118. return err
  119. }
  120. pw, err = promptPasswordWithConfirmation()
  121. if err != nil {
  122. return err
  123. }
  124. credstore.Set(username, pw)
  125. }
  126. if !skipKubeconfig {
  127. err = generate(
  128. kubeconfigPath,
  129. outputConfPath,
  130. false,
  131. contexts,
  132. )
  133. if err != nil {
  134. return err
  135. }
  136. }
  137. agent, err := docker.NewAgentFromEnv()
  138. if err != nil {
  139. return err
  140. }
  141. // the volume mounts to use
  142. mounts := make([]mount.Mount, 0)
  143. // the volumes passed to the Porter container
  144. volumesMap := make(map[string]struct{})
  145. if !skipKubeconfig {
  146. // add a bind mount with the kubeconfig
  147. mount := mount.Mount{
  148. Type: mount.TypeBind,
  149. Source: outputConfPath,
  150. Target: containerConfPath,
  151. ReadOnly: true,
  152. Consistency: mount.ConsistencyFull,
  153. }
  154. mounts = append(mounts, mount)
  155. }
  156. netID, err := agent.CreateBridgeNetworkIfNotExist("porter_network")
  157. if err != nil {
  158. return err
  159. }
  160. env := make([]string, 0)
  161. env = append(env, []string{
  162. "ADMIN_INIT=true",
  163. "ADMIN_EMAIL=" + username,
  164. "ADMIN_PASSWORD=" + pw,
  165. }...)
  166. switch db {
  167. case "sqlite":
  168. // check if sqlite volume exists, create it if not
  169. vol, err := agent.CreateLocalVolumeIfNotExist("porter_sqlite")
  170. if err != nil {
  171. return err
  172. }
  173. // create mount
  174. mount := mount.Mount{
  175. Type: mount.TypeVolume,
  176. Source: vol.Name,
  177. Target: "/sqlite",
  178. ReadOnly: false,
  179. Consistency: mount.ConsistencyFull,
  180. }
  181. mounts = append(mounts, mount)
  182. volumesMap[vol.Name] = struct{}{}
  183. env = append(env, []string{
  184. "SQL_LITE=true",
  185. "SQL_LITE_PATH=/sqlite/porter.db",
  186. }...)
  187. case "postgres":
  188. // check if postgres volume exists, create it if not
  189. vol, err := agent.CreateLocalVolumeIfNotExist("porter_postgres")
  190. if err != nil {
  191. return err
  192. }
  193. // pgMount is mount for postgres container
  194. pgMount := []mount.Mount{
  195. mount.Mount{
  196. Type: mount.TypeVolume,
  197. Source: vol.Name,
  198. Target: "/var/lib/postgresql/data",
  199. ReadOnly: false,
  200. Consistency: mount.ConsistencyFull,
  201. },
  202. }
  203. // create postgres container with mount
  204. startOpts := docker.PostgresOpts{
  205. Name: "porter_postgres",
  206. Image: "postgres:latest",
  207. Mounts: pgMount,
  208. VolumeMap: map[string]struct{}{
  209. "porter_postgres": struct{}{},
  210. },
  211. NetworkID: netID,
  212. Env: []string{
  213. "POSTGRES_USER=porter",
  214. "POSTGRES_PASSWORD=porter",
  215. "POSTGRES_DB=porter",
  216. },
  217. }
  218. pgID, err := agent.StartPostgresContainer(startOpts)
  219. fmt.Println("Waiting for postgres:latest to be healthy...")
  220. agent.WaitForContainerHealthy(pgID, 10)
  221. if err != nil {
  222. return err
  223. }
  224. env = append(env, []string{
  225. "SQL_LITE=false",
  226. "DB_USER=porter",
  227. "DB_PASS=porter",
  228. "DB_NAME=porter",
  229. "DB_HOST=porter_postgres",
  230. "DB_PORT=5432",
  231. }...)
  232. defer agent.WaitForContainerStop(pgID)
  233. }
  234. // create Porter container
  235. // TODO -- look for unused port
  236. startOpts := docker.PorterStartOpts{
  237. Name: "porter_server",
  238. Image: "porter1/porter:" + imageTag,
  239. HostPort: uint(port),
  240. ContainerPort: 8080,
  241. Mounts: mounts,
  242. VolumeMap: volumesMap,
  243. NetworkID: netID,
  244. Env: env,
  245. }
  246. id, err := agent.StartPorterContainer(startOpts)
  247. if err != nil {
  248. return err
  249. }
  250. fmt.Println("Spinning up the server...")
  251. time.Sleep(7 * time.Second)
  252. openBrowser(fmt.Sprintf("http://localhost:%d/login?email=%s", port, username))
  253. fmt.Printf("Server ready: listening on localhost:%d\n", port)
  254. agent.WaitForContainerStop(id)
  255. return nil
  256. }
  257. // openBrowser opens the specified URL in the default browser of the user.
  258. func openBrowser(url string) error {
  259. var cmd string
  260. var args []string
  261. switch runtime.GOOS {
  262. case "windows":
  263. cmd = "cmd"
  264. args = []string{"/c", "start"}
  265. case "darwin":
  266. cmd = "open"
  267. default: // "linux", "freebsd", "openbsd", "netbsd"
  268. cmd = "xdg-open"
  269. }
  270. args = append(args, url)
  271. return exec.Command(cmd, args...).Start()
  272. }