create.go 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. package cmd
  2. import (
  3. "fmt"
  4. "io/ioutil"
  5. "os"
  6. "path/filepath"
  7. "github.com/fatih/color"
  8. "github.com/porter-dev/porter/cli/cmd/api"
  9. "github.com/porter-dev/porter/cli/cmd/deploy"
  10. "github.com/porter-dev/porter/cli/cmd/gitutils"
  11. "github.com/spf13/cobra"
  12. "sigs.k8s.io/yaml"
  13. )
  14. // createCmd represents the "porter create" base command when called
  15. // without any subcommands
  16. var createCmd = &cobra.Command{
  17. Use: "create [kind]",
  18. Args: cobra.ExactArgs(1),
  19. Short: "Creates a new application with name given by the --app flag.",
  20. Long: fmt.Sprintf(`
  21. %s
  22. Creates a new application with name given by the --app flag and a "kind", which can be one of
  23. web, worker, or job. For example:
  24. %s
  25. To modify the default configuration of the application, you can pass a values.yaml file in via the
  26. --values flag.
  27. %s
  28. To read more about the configuration options, go here:
  29. https://docs.getporter.dev/docs/deploying-from-the-cli#common-configuration-options
  30. This command will automatically build from a local path, and will create a new Docker image in your
  31. default Docker registry. The path can be configured via the --path flag. For example:
  32. %s
  33. To connect the application to Github, so that the application rebuilds and redeploys on each push
  34. to a Github branch, you can specify "--source github". If your local branch is set to track changes
  35. from an upstream remote branch, Porter will try to use the connected remote and remote branch as the
  36. Github repository to link to. Otherwise, Porter will use the remote given by origin. For example:
  37. %s
  38. To deploy an application from a Docker registry, use "--source registry" and pass the image in via the
  39. --image flag. The image flag must be of the form repository:tag. For example:
  40. %s
  41. `,
  42. color.New(color.FgBlue, color.Bold).Sprintf("Help for \"porter create\":"),
  43. color.New(color.FgGreen, color.Bold).Sprintf("porter create web --app example-app"),
  44. color.New(color.FgGreen, color.Bold).Sprintf("porter create web --app example-app --values values.yaml"),
  45. color.New(color.FgGreen, color.Bold).Sprintf("porter create web --app example-app --path ./path/to/app"),
  46. color.New(color.FgGreen, color.Bold).Sprintf("porter create web --app example-app --source github"),
  47. color.New(color.FgGreen, color.Bold).Sprintf("porter create web --app example-app --source registry --image gcr.io/snowflake-12345/example-app:latest"),
  48. ),
  49. Run: func(cmd *cobra.Command, args []string) {
  50. err := checkLoginAndRun(args, createFull)
  51. if err != nil {
  52. os.Exit(1)
  53. }
  54. },
  55. }
  56. var name string
  57. var values string
  58. var source string
  59. var image string
  60. func init() {
  61. rootCmd.AddCommand(createCmd)
  62. createCmd.PersistentFlags().StringVar(
  63. &name,
  64. "app",
  65. "",
  66. "name of the new application/job/worker.",
  67. )
  68. createCmd.MarkPersistentFlagRequired("app")
  69. createCmd.PersistentFlags().BoolVar(
  70. &local,
  71. "local",
  72. true,
  73. "whether local context should be used for build",
  74. )
  75. createCmd.PersistentFlags().StringVarP(
  76. &localPath,
  77. "path",
  78. "p",
  79. ".",
  80. "if local build, the path to the build directory",
  81. )
  82. createCmd.PersistentFlags().StringVar(
  83. &namespace,
  84. "namespace",
  85. "default",
  86. "namespace of the application",
  87. )
  88. createCmd.PersistentFlags().StringVarP(
  89. &values,
  90. "values",
  91. "v",
  92. "",
  93. "filepath to a values.yaml file",
  94. )
  95. createCmd.PersistentFlags().StringVar(
  96. &dockerfile,
  97. "dockerfile",
  98. "",
  99. "the path to the dockerfile",
  100. )
  101. createCmd.PersistentFlags().StringVar(
  102. &method,
  103. "method",
  104. "",
  105. "the build method to use (\"docker\" or \"pack\")",
  106. )
  107. createCmd.PersistentFlags().StringVar(
  108. &source,
  109. "source",
  110. "local",
  111. "the type of source (\"local\", \"github\", or \"registry\")",
  112. )
  113. createCmd.PersistentFlags().StringVar(
  114. &image,
  115. "image",
  116. "",
  117. "if the source is \"registry\", the image to use, in repository:tag format",
  118. )
  119. }
  120. var supportedKinds = map[string]string{"web": "", "job": "", "worker": ""}
  121. func createFull(resp *api.AuthCheckResponse, client *api.Client, args []string) error {
  122. // check the kind
  123. if _, exists := supportedKinds[args[0]]; !exists {
  124. return fmt.Errorf("%s is not a supported type: specify web, job, or worker", args[0])
  125. }
  126. // read the values if necessary
  127. valuesObj, err := readValuesFile()
  128. if err != nil {
  129. return err
  130. }
  131. color.New(color.FgGreen).Printf("Creating %s release: %s\n", args[0], name)
  132. fullPath, err := filepath.Abs(localPath)
  133. if err != nil {
  134. return err
  135. }
  136. var buildMethod deploy.DeployBuildType
  137. if method != "" {
  138. buildMethod = deploy.DeployBuildType(method)
  139. } else if dockerfile != "" {
  140. buildMethod = deploy.DeployBuildTypeDocker
  141. }
  142. createAgent := &deploy.CreateAgent{
  143. Client: client,
  144. CreateOpts: &deploy.CreateOpts{
  145. SharedOpts: &deploy.SharedOpts{
  146. ProjectID: config.Project,
  147. ClusterID: config.Cluster,
  148. Namespace: namespace,
  149. LocalPath: fullPath,
  150. LocalDockerfile: dockerfile,
  151. Method: buildMethod,
  152. },
  153. Kind: args[0],
  154. ReleaseName: name,
  155. },
  156. }
  157. if source == "local" {
  158. subdomain, err := createAgent.CreateFromDocker(valuesObj)
  159. return handleSubdomainCreate(subdomain, err)
  160. } else if source == "github" {
  161. return createFromGithub(createAgent, valuesObj)
  162. }
  163. subdomain, err := createAgent.CreateFromRegistry(image, valuesObj)
  164. return handleSubdomainCreate(subdomain, err)
  165. }
  166. func handleSubdomainCreate(subdomain string, err error) error {
  167. if err != nil {
  168. return err
  169. }
  170. if subdomain != "" {
  171. color.New(color.FgGreen).Printf("Your web application is ready at: %s\n", subdomain)
  172. } else {
  173. color.New(color.FgGreen).Printf("Application created successfully\n")
  174. }
  175. return nil
  176. }
  177. func createFromGithub(createAgent *deploy.CreateAgent, overrideValues map[string]interface{}) error {
  178. fullPath, err := filepath.Abs(localPath)
  179. if err != nil {
  180. return err
  181. }
  182. _, err = gitutils.GitDirectory(fullPath)
  183. if err != nil {
  184. return err
  185. }
  186. remote, gitBranch, err := gitutils.GetRemoteBranch(fullPath)
  187. if err != nil {
  188. return err
  189. }
  190. ok, remoteRepo := gitutils.ParseGithubRemote(remote)
  191. if !ok {
  192. return fmt.Errorf("remote is not a Github repository")
  193. }
  194. subdomain, err := createAgent.CreateFromGithub(&deploy.GithubOpts{
  195. Branch: gitBranch,
  196. Repo: remoteRepo,
  197. }, overrideValues)
  198. return handleSubdomainCreate(subdomain, err)
  199. }
  200. func readValuesFile() (map[string]interface{}, error) {
  201. res := make(map[string]interface{})
  202. if values == "" {
  203. return res, nil
  204. }
  205. valuesFilePath, err := filepath.Abs(values)
  206. if err != nil {
  207. return nil, err
  208. }
  209. if info, err := os.Stat(valuesFilePath); os.IsNotExist(err) || info.IsDir() {
  210. return nil, fmt.Errorf("values file does not exist or is a directory")
  211. }
  212. reader, err := os.Open(valuesFilePath)
  213. if err != nil {
  214. return nil, err
  215. }
  216. bytes, err := ioutil.ReadAll(reader)
  217. if err != nil {
  218. return nil, err
  219. }
  220. err = yaml.Unmarshal(bytes, &res)
  221. if err != nil {
  222. return nil, err
  223. }
  224. return res, nil
  225. }