create.go 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612
  1. package porter_app
  2. import (
  3. "context"
  4. "encoding/base64"
  5. "errors"
  6. "fmt"
  7. "net/http"
  8. "strings"
  9. "github.com/google/uuid"
  10. "github.com/porter-dev/porter/internal/kubernetes"
  11. "github.com/porter-dev/porter/internal/kubernetes/envgroup"
  12. "github.com/porter-dev/porter/internal/telemetry"
  13. "gorm.io/gorm"
  14. "github.com/porter-dev/porter/api/server/authz"
  15. "github.com/porter-dev/porter/api/server/handlers"
  16. "github.com/porter-dev/porter/api/server/shared"
  17. "github.com/porter-dev/porter/api/server/shared/apierrors"
  18. "github.com/porter-dev/porter/api/server/shared/config"
  19. "github.com/porter-dev/porter/api/server/shared/requestutils"
  20. "github.com/porter-dev/porter/api/types"
  21. "github.com/porter-dev/porter/internal/helm"
  22. "github.com/porter-dev/porter/internal/helm/loader"
  23. "github.com/porter-dev/porter/internal/models"
  24. "github.com/porter-dev/porter/internal/repository"
  25. "github.com/stefanmcshane/helm/pkg/chart"
  26. )
  27. type CreatePorterAppHandler struct {
  28. handlers.PorterHandlerReadWriter
  29. authz.KubernetesAgentGetter
  30. }
  31. func NewCreatePorterAppHandler(
  32. config *config.Config,
  33. decoderValidator shared.RequestDecoderValidator,
  34. writer shared.ResultWriter,
  35. ) *CreatePorterAppHandler {
  36. return &CreatePorterAppHandler{
  37. PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
  38. KubernetesAgentGetter: authz.NewOutOfClusterAgentGetter(config),
  39. }
  40. }
  41. func (c *CreatePorterAppHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  42. ctx := r.Context()
  43. project, _ := ctx.Value(types.ProjectScope).(*models.Project)
  44. cluster, _ := ctx.Value(types.ClusterScope).(*models.Cluster)
  45. ctx, span := telemetry.NewSpan(r.Context(), "serve-create-porter-app")
  46. defer span.End()
  47. request := &types.CreatePorterAppRequest{}
  48. if ok := c.DecodeAndValidate(w, r, request); !ok {
  49. err := telemetry.Error(ctx, span, nil, "error decoding request")
  50. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
  51. return
  52. }
  53. stackName, reqErr := requestutils.GetURLParamString(r, types.URLParamStackName)
  54. if reqErr != nil {
  55. err := telemetry.Error(ctx, span, reqErr, "error getting stack name from url")
  56. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
  57. return
  58. }
  59. telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "application-name", Value: stackName})
  60. var namespace string
  61. if request.Namespace != "" {
  62. namespace = request.Namespace
  63. } else {
  64. namespace = fmt.Sprintf("porter-stack-%s", stackName)
  65. }
  66. helmAgent, err := c.GetHelmAgent(ctx, r, cluster, namespace)
  67. if err != nil {
  68. err = telemetry.Error(ctx, span, err, "error getting helm agent")
  69. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  70. return
  71. }
  72. k8sAgent, err := c.GetAgent(r, cluster, namespace)
  73. if err != nil {
  74. err = telemetry.Error(ctx, span, err, "error getting k8s agent")
  75. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  76. return
  77. }
  78. helmRelease, err := helmAgent.GetRelease(ctx, stackName, 0, false)
  79. shouldCreate := err != nil
  80. porterYamlBase64 := request.PorterYAMLBase64
  81. porterYaml, err := base64.StdEncoding.DecodeString(porterYamlBase64)
  82. if err != nil {
  83. err = telemetry.Error(ctx, span, err, "error decoding porter yaml")
  84. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
  85. return
  86. }
  87. imageInfo := request.ImageInfo
  88. registries, err := c.Repo().Registry().ListRegistriesByProjectID(cluster.ProjectID)
  89. if err != nil {
  90. err = telemetry.Error(ctx, span, err, "error listing registries")
  91. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  92. return
  93. }
  94. var releaseValues map[string]interface{}
  95. var releaseDependencies []*chart.Dependency
  96. if shouldCreate || request.OverrideRelease {
  97. releaseValues = nil
  98. releaseDependencies = nil
  99. // this is required because when the front-end sends an update request with overrideRelease=true, it is unable to
  100. // get the image info from the release. unless it is explicitly provided in the request, we avoid overwriting it
  101. // by attempting to get the image info from the release or the provided helm values
  102. if helmRelease != nil && (imageInfo.Repository == "" || imageInfo.Tag == "") {
  103. if request.FullHelmValues != "" {
  104. imageInfo, err = attemptToGetImageInfoFromFullHelmValues(request.FullHelmValues)
  105. if err != nil {
  106. err = telemetry.Error(ctx, span, err, "error getting image info from full helm values")
  107. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
  108. return
  109. }
  110. } else {
  111. imageInfo = attemptToGetImageInfoFromRelease(helmRelease.Config)
  112. }
  113. }
  114. } else {
  115. releaseValues = helmRelease.Config
  116. releaseDependencies = helmRelease.Chart.Metadata.Dependencies
  117. }
  118. telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "image-repo", Value: imageInfo.Repository}, telemetry.AttributeKV{Key: "image-tag", Value: imageInfo.Tag})
  119. if request.Builder == "" {
  120. // attempt to get builder from db
  121. app, err := c.Repo().PorterApp().ReadPorterAppByName(cluster.ID, stackName, request.EnvironmentConfigID)
  122. if err == nil {
  123. request.Builder = app.Builder
  124. }
  125. }
  126. injectLauncher := strings.Contains(request.Builder, "heroku") ||
  127. strings.Contains(request.Builder, "paketo")
  128. telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "builder", Value: request.Builder})
  129. if shouldCreate {
  130. // create the namespace if it does not exist already
  131. _, err = k8sAgent.CreateNamespace(namespace, nil)
  132. if err != nil {
  133. err = telemetry.Error(ctx, span, err, "error creating namespace")
  134. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  135. return
  136. }
  137. cloneEnvGroup(c, w, r, k8sAgent, request.EnvGroups, namespace)
  138. }
  139. chart, values, preDeployJobValues, err := parse(
  140. ParseConf{
  141. PorterYaml: porterYaml,
  142. ImageInfo: imageInfo,
  143. ServerConfig: c.Config(),
  144. ProjectID: cluster.ProjectID,
  145. UserUpdate: request.UserUpdate,
  146. EnvGroups: request.EnvGroups,
  147. Namespace: namespace,
  148. ExistingHelmValues: releaseValues,
  149. ExistingChartDependencies: releaseDependencies,
  150. SubdomainCreateOpts: SubdomainCreateOpts{
  151. k8sAgent: k8sAgent,
  152. dnsRepo: c.Repo().DNSRecord(),
  153. powerDnsClient: c.Config().PowerDNSClient,
  154. appRootDomain: c.Config().ServerConf.AppRootDomain,
  155. stackName: stackName,
  156. },
  157. InjectLauncherToStartCommand: injectLauncher,
  158. ShouldValidateHelmValues: shouldCreate,
  159. FullHelmValues: request.FullHelmValues,
  160. },
  161. )
  162. if err != nil {
  163. err = telemetry.Error(ctx, span, err, "parse error")
  164. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  165. return
  166. }
  167. if shouldCreate {
  168. telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "installing-application", Value: true})
  169. // create the release job chart if it does not exist (only done by front-end currently, where we set overrideRelease=true)
  170. if request.OverrideRelease && preDeployJobValues != nil {
  171. telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "installing-pre-deploy-job", Value: true})
  172. conf, err := createReleaseJobChart(
  173. ctx,
  174. stackName,
  175. preDeployJobValues,
  176. c.Config().ServerConf.DefaultApplicationHelmRepoURL,
  177. registries,
  178. cluster,
  179. c.Repo(),
  180. )
  181. if err != nil {
  182. err = telemetry.Error(ctx, span, err, "error making config for pre-deploy job chart")
  183. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  184. return
  185. }
  186. _, err = helmAgent.InstallChart(ctx, conf, c.Config().DOConf, c.Config().ServerConf.DisablePullSecretsInjection)
  187. if err != nil {
  188. err = telemetry.Error(ctx, span, err, "error installing pre-deploy job chart")
  189. telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "install-pre-deploy-job-error", Value: err})
  190. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  191. _, uninstallChartErr := helmAgent.UninstallChart(ctx, fmt.Sprintf("%s-r", stackName))
  192. if uninstallChartErr != nil {
  193. uninstallChartErr = telemetry.Error(ctx, span, err, "error uninstalling pre-deploy job chart after failed install")
  194. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(uninstallChartErr, http.StatusInternalServerError))
  195. }
  196. return
  197. }
  198. }
  199. conf := &helm.InstallChartConfig{
  200. Chart: chart,
  201. Name: stackName,
  202. Namespace: namespace,
  203. Values: values,
  204. Cluster: cluster,
  205. Repo: c.Repo(),
  206. Registries: registries,
  207. }
  208. // create the app chart
  209. _, err = helmAgent.InstallChart(ctx, conf, c.Config().DOConf, c.Config().ServerConf.DisablePullSecretsInjection)
  210. if err != nil {
  211. err = telemetry.Error(ctx, span, err, "error installing app chart")
  212. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
  213. _, err = helmAgent.UninstallChart(ctx, stackName)
  214. if err != nil {
  215. err = telemetry.Error(ctx, span, err, "error uninstalling app chart after failed install")
  216. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  217. }
  218. return
  219. }
  220. app := &models.PorterApp{
  221. Name: stackName,
  222. ClusterID: cluster.ID,
  223. ProjectID: project.ID,
  224. RepoName: request.RepoName,
  225. GitRepoID: request.GitRepoID,
  226. GitBranch: request.GitBranch,
  227. BuildContext: request.BuildContext,
  228. Builder: request.Builder,
  229. Buildpacks: request.Buildpacks,
  230. Dockerfile: request.Dockerfile,
  231. ImageRepoURI: request.ImageRepoURI,
  232. PullRequestURL: request.PullRequestURL,
  233. PorterYamlPath: request.PorterYamlPath,
  234. }
  235. existing, err := c.Repo().PorterApp().ReadPorterAppByName(cluster.ID, stackName, request.EnvironmentConfigID)
  236. if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
  237. err = telemetry.Error(ctx, span, err, "error reading app from DB")
  238. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  239. return
  240. }
  241. // asssuming that namespace not being set means the app is running in production env
  242. if existing != nil && request.Namespace == "" {
  243. err = telemetry.Error(ctx, span, err, "app with name already exists in environment")
  244. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusForbidden))
  245. return
  246. }
  247. err = assignEnvironmentToApp(c, app, project.ID, cluster.ID, request.EnvironmentConfigID)
  248. if err != nil {
  249. err = telemetry.Error(ctx, span, err, "error assigning environment to app")
  250. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  251. return
  252. }
  253. // create the db entry
  254. porterApp, err := c.Repo().PorterApp().UpdatePorterApp(app)
  255. if err != nil {
  256. err = telemetry.Error(ctx, span, err, "error writing app to DB")
  257. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  258. return
  259. }
  260. _, err = createPorterAppEvent(ctx, "SUCCESS", porterApp.ID, 1, imageInfo.Tag, c.Repo().PorterAppEvent())
  261. if err != nil {
  262. err = telemetry.Error(ctx, span, err, "error creating porter app event")
  263. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  264. return
  265. }
  266. c.WriteResult(w, r, porterApp.ToPorterAppType())
  267. } else {
  268. telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "upgrading-application", Value: true})
  269. // create/update the pre-deploy job chart
  270. if request.OverrideRelease {
  271. if preDeployJobValues == nil {
  272. preDeployJobName := fmt.Sprintf("%s-r", stackName)
  273. _, err := helmAgent.GetRelease(ctx, preDeployJobName, 0, false)
  274. if err == nil {
  275. // handle exception where the user has chosen to delete the release job
  276. telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "deleting-pre-deploy-job", Value: true})
  277. _, err = helmAgent.UninstallChart(ctx, preDeployJobName)
  278. if err != nil {
  279. err = telemetry.Error(ctx, span, err, "error uninstalling pre-deploy job chart")
  280. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  281. return
  282. }
  283. }
  284. } else {
  285. preDeployJobName := fmt.Sprintf("%s-r", stackName)
  286. helmRelease, err := helmAgent.GetRelease(ctx, preDeployJobName, 0, false)
  287. if err != nil {
  288. telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "creating-pre-deploy-job", Value: true})
  289. conf, err := createReleaseJobChart(
  290. ctx,
  291. stackName,
  292. preDeployJobValues,
  293. c.Config().ServerConf.DefaultApplicationHelmRepoURL,
  294. registries,
  295. cluster,
  296. c.Repo(),
  297. )
  298. if err != nil {
  299. err = telemetry.Error(ctx, span, err, "error making config for pre-deploy job chart")
  300. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  301. return
  302. }
  303. _, err = helmAgent.InstallChart(ctx, conf, c.Config().DOConf, c.Config().ServerConf.DisablePullSecretsInjection)
  304. if err != nil {
  305. err = telemetry.Error(ctx, span, err, "error installing pre-deploy job chart")
  306. telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "install-pre-deploy-job-error", Value: err})
  307. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  308. _, uninstallChartErr := helmAgent.UninstallChart(ctx, fmt.Sprintf("%s-r", stackName))
  309. if uninstallChartErr != nil {
  310. uninstallChartErr = telemetry.Error(ctx, span, err, "error uninstalling pre-deploy job chart after failed install")
  311. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(uninstallChartErr, http.StatusInternalServerError))
  312. }
  313. return
  314. }
  315. } else {
  316. telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "updating-pre-deploy-job", Value: true})
  317. chart, err := loader.LoadChartPublic(ctx, c.Config().Metadata.DefaultAppHelmRepoURL, "job", "")
  318. if err != nil {
  319. err = telemetry.Error(ctx, span, err, "error loading latest job chart")
  320. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  321. return
  322. }
  323. conf := &helm.UpgradeReleaseConfig{
  324. Name: helmRelease.Name,
  325. Cluster: cluster,
  326. Repo: c.Repo(),
  327. Registries: registries,
  328. Values: preDeployJobValues,
  329. Chart: chart,
  330. }
  331. _, err = helmAgent.UpgradeReleaseByValues(ctx, conf, c.Config().DOConf, c.Config().ServerConf.DisablePullSecretsInjection, false)
  332. if err != nil {
  333. err = telemetry.Error(ctx, span, err, "error upgrading pre-deploy job chart")
  334. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  335. return
  336. }
  337. }
  338. }
  339. }
  340. // update the app chart
  341. conf := &helm.InstallChartConfig{
  342. Chart: chart,
  343. Name: stackName,
  344. Namespace: namespace,
  345. Values: values,
  346. Cluster: cluster,
  347. Repo: c.Repo(),
  348. Registries: registries,
  349. }
  350. // update the chart
  351. _, err = helmAgent.UpgradeInstallChart(ctx, conf, c.Config().DOConf, c.Config().ServerConf.DisablePullSecretsInjection)
  352. if err != nil {
  353. err = telemetry.Error(ctx, span, err, "error upgrading application")
  354. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
  355. return
  356. }
  357. // update the DB entry
  358. app, err := c.Repo().PorterApp().ReadPorterAppByName(cluster.ID, stackName, request.EnvironmentConfigID)
  359. if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
  360. err = telemetry.Error(ctx, span, err, "error reading app from DB")
  361. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  362. return
  363. }
  364. if app == nil {
  365. err = telemetry.Error(ctx, span, nil, "app with name does not exist in project")
  366. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusForbidden))
  367. return
  368. }
  369. if request.RepoName != "" {
  370. app.RepoName = request.RepoName
  371. }
  372. if request.GitBranch != "" {
  373. app.GitBranch = request.GitBranch
  374. }
  375. if request.BuildContext != "" {
  376. app.BuildContext = request.BuildContext
  377. }
  378. // handles deletion of builder,buildpacks, and dockerfile path
  379. if request.Builder != "" {
  380. if request.Builder == "null" {
  381. app.Builder = ""
  382. } else {
  383. app.Builder = request.Builder
  384. }
  385. }
  386. if request.Buildpacks != "" {
  387. if request.Buildpacks == "null" {
  388. app.Buildpacks = ""
  389. } else {
  390. app.Buildpacks = request.Buildpacks
  391. }
  392. }
  393. if request.Dockerfile != "" {
  394. if request.Dockerfile == "null" {
  395. app.Dockerfile = ""
  396. } else {
  397. app.Dockerfile = request.Dockerfile
  398. }
  399. }
  400. if request.ImageRepoURI != "" {
  401. app.ImageRepoURI = request.ImageRepoURI
  402. }
  403. if request.PullRequestURL != "" {
  404. app.PullRequestURL = request.PullRequestURL
  405. }
  406. telemetry.WithAttributes(
  407. span,
  408. telemetry.AttributeKV{Key: "updated-repo-name", Value: app.RepoName},
  409. telemetry.AttributeKV{Key: "updated-git-branch", Value: app.GitBranch},
  410. telemetry.AttributeKV{Key: "updated-build-context", Value: app.BuildContext},
  411. telemetry.AttributeKV{Key: "updated-builder", Value: app.Builder},
  412. telemetry.AttributeKV{Key: "updated-buildpacks", Value: app.Buildpacks},
  413. telemetry.AttributeKV{Key: "updated-dockerfile", Value: app.Dockerfile},
  414. telemetry.AttributeKV{Key: "updated-image-repo-uri", Value: app.ImageRepoURI},
  415. telemetry.AttributeKV{Key: "updated-pull-request-url", Value: app.PullRequestURL},
  416. )
  417. updatedPorterApp, err := c.Repo().PorterApp().UpdatePorterApp(app)
  418. if err != nil {
  419. err = telemetry.Error(ctx, span, err, "error writing updated app to DB")
  420. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  421. return
  422. }
  423. _, err = createPorterAppEvent(ctx, "SUCCESS", updatedPorterApp.ID, helmRelease.Version+1, imageInfo.Tag, c.Repo().PorterAppEvent())
  424. if err != nil {
  425. err = telemetry.Error(ctx, span, err, "error creating porter app event")
  426. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  427. return
  428. }
  429. c.WriteResult(w, r, updatedPorterApp.ToPorterAppType())
  430. }
  431. }
  432. // createPorterAppEvent creates an event for use in the activity feed
  433. func createPorterAppEvent(ctx context.Context, status string, appID uint, revision int, tag string, repo repository.PorterAppEventRepository) (*models.PorterAppEvent, error) {
  434. event := models.PorterAppEvent{
  435. ID: uuid.New(),
  436. Status: status,
  437. Type: "DEPLOY",
  438. TypeExternalSource: "KUBERNETES",
  439. PorterAppID: appID,
  440. Metadata: map[string]any{
  441. "revision": revision,
  442. "image_tag": tag,
  443. },
  444. }
  445. err := repo.CreateEvent(ctx, &event)
  446. if err != nil {
  447. return nil, err
  448. }
  449. if event.ID == uuid.Nil {
  450. return nil, err
  451. }
  452. return &event, nil
  453. }
  454. func createReleaseJobChart(
  455. ctx context.Context,
  456. stackName string,
  457. values map[string]interface{},
  458. repoUrl string,
  459. registries []*models.Registry,
  460. cluster *models.Cluster,
  461. repo repository.Repository,
  462. ) (*helm.InstallChartConfig, error) {
  463. chart, err := loader.LoadChartPublic(ctx, repoUrl, "job", "")
  464. if err != nil {
  465. return nil, err
  466. }
  467. releaseName := fmt.Sprintf("%s-r", stackName)
  468. namespace := fmt.Sprintf("porter-stack-%s", stackName)
  469. return &helm.InstallChartConfig{
  470. Chart: chart,
  471. Name: releaseName,
  472. Namespace: namespace,
  473. Values: values,
  474. Cluster: cluster,
  475. Repo: repo,
  476. Registries: registries,
  477. }, nil
  478. }
  479. func cloneEnvGroup(c *CreatePorterAppHandler, w http.ResponseWriter, r *http.Request, agent *kubernetes.Agent, envGroups []string, namespace string) {
  480. for _, envGroupName := range envGroups {
  481. cm, _, err := agent.GetLatestVersionedConfigMap(envGroupName, "porter-env-group")
  482. if err != nil {
  483. if errors.Is(err, kubernetes.IsNotFoundError) {
  484. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(
  485. fmt.Errorf("error cloning env group: envgroup %s in namespace %s not found", envGroupName, "porter-env-group"), http.StatusNotFound,
  486. "no config map found for envgroup",
  487. ))
  488. return
  489. }
  490. c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  491. return
  492. }
  493. secret, _, err := agent.GetLatestVersionedSecret(envGroupName, "porter-env-group")
  494. if err != nil {
  495. if errors.Is(err, kubernetes.IsNotFoundError) {
  496. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(
  497. fmt.Errorf("error cloning env group: envgroup %s in namespace %s not found", envGroupName, "porter-env-group"), http.StatusNotFound,
  498. "no k8s secret found for envgroup",
  499. ))
  500. return
  501. }
  502. c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  503. return
  504. }
  505. vars := make(map[string]string)
  506. secretVars := make(map[string]string)
  507. for key, val := range cm.Data {
  508. if !strings.Contains(val, "PORTERSECRET") {
  509. vars[key] = val
  510. }
  511. }
  512. for key, val := range secret.Data {
  513. secretVars[key] = string(val)
  514. }
  515. configMap, err := envgroup.CreateEnvGroup(agent, types.ConfigMapInput{
  516. Name: envGroupName,
  517. Namespace: namespace,
  518. Variables: vars,
  519. SecretVariables: secretVars,
  520. })
  521. if err != nil {
  522. c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  523. return
  524. }
  525. _, err = envgroup.ToEnvGroup(configMap)
  526. if err != nil {
  527. c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  528. return
  529. }
  530. }
  531. }
  532. func assignEnvironmentToApp(c *CreatePorterAppHandler, app *models.PorterApp, projectID, clusterID, envConfID uint) error {
  533. ctx, span := telemetry.NewSpan(context.Background(), "assign-env-to-app")
  534. if app == nil {
  535. return fmt.Errorf("Application does not exist")
  536. }
  537. if envConfID != 0 {
  538. envConf, err := c.Repo().EnvironmentConfig().ReadEnvironmentConfig(projectID, clusterID, envConfID)
  539. if err != nil {
  540. return telemetry.Error(ctx, span, err, "error reading environment config from DB")
  541. }
  542. app.EnvironmentConfigID = envConf.ID
  543. return nil
  544. }
  545. envConf, err := c.Repo().EnvironmentConfig().ReadDefaultEnvironmentConfig(projectID, clusterID)
  546. if err != nil {
  547. return telemetry.Error(ctx, span, err, "error reading environment config from DB")
  548. }
  549. app.EnvironmentConfigID = envConf.ID
  550. return nil
  551. }