create.go 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611
  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. if existing != nil && request.EnvironmentConfigID == 0 {
  242. err = telemetry.Error(ctx, span, err, "app with name already exists in environment")
  243. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusForbidden))
  244. return
  245. }
  246. err = assignEnvironmentToApp(c, app, project.ID, cluster.ID, request.EnvironmentConfigID)
  247. if err != nil {
  248. err = telemetry.Error(ctx, span, err, "error assigning environment to app")
  249. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  250. return
  251. }
  252. // create the db entry
  253. porterApp, err := c.Repo().PorterApp().UpdatePorterApp(app)
  254. if err != nil {
  255. err = telemetry.Error(ctx, span, err, "error writing app to DB")
  256. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  257. return
  258. }
  259. _, err = createPorterAppEvent(ctx, "SUCCESS", porterApp.ID, 1, imageInfo.Tag, c.Repo().PorterAppEvent())
  260. if err != nil {
  261. err = telemetry.Error(ctx, span, err, "error creating porter app event")
  262. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  263. return
  264. }
  265. c.WriteResult(w, r, porterApp.ToPorterAppType())
  266. } else {
  267. telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "upgrading-application", Value: true})
  268. // create/update the release job chart
  269. if request.OverrideRelease {
  270. if preDeployJobValues == nil {
  271. releaseJobName := fmt.Sprintf("%s-r", stackName)
  272. _, err := helmAgent.GetRelease(ctx, releaseJobName, 0, false)
  273. if err == nil {
  274. // handle exception where the user has chosen to delete the release job
  275. telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "deleting-pre-deploy-job", Value: true})
  276. _, err = helmAgent.UninstallChart(ctx, releaseJobName)
  277. if err != nil {
  278. err = telemetry.Error(ctx, span, err, "error uninstalling pre-deploy job chart")
  279. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  280. return
  281. }
  282. }
  283. } else {
  284. releaseJobName := fmt.Sprintf("%s-r", stackName)
  285. helmRelease, err := helmAgent.GetRelease(ctx, releaseJobName, 0, false)
  286. if err != nil {
  287. telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "creating-pre-deploy-job", Value: true})
  288. conf, err := createReleaseJobChart(
  289. ctx,
  290. stackName,
  291. preDeployJobValues,
  292. c.Config().ServerConf.DefaultApplicationHelmRepoURL,
  293. registries,
  294. cluster,
  295. c.Repo(),
  296. )
  297. if err != nil {
  298. err = telemetry.Error(ctx, span, err, "error making config for pre-deploy job chart")
  299. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  300. return
  301. }
  302. _, err = helmAgent.InstallChart(ctx, conf, c.Config().DOConf, c.Config().ServerConf.DisablePullSecretsInjection)
  303. if err != nil {
  304. err = telemetry.Error(ctx, span, err, "error installing pre-deploy job chart")
  305. telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "install-pre-deploy-job-error", Value: err})
  306. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  307. _, uninstallChartErr := helmAgent.UninstallChart(ctx, fmt.Sprintf("%s-r", stackName))
  308. if uninstallChartErr != nil {
  309. uninstallChartErr = telemetry.Error(ctx, span, err, "error uninstalling pre-deploy job chart after failed install")
  310. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(uninstallChartErr, http.StatusInternalServerError))
  311. }
  312. return
  313. }
  314. } else {
  315. telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "updating-pre-deploy-job", Value: true})
  316. chart, err := loader.LoadChartPublic(ctx, c.Config().Metadata.DefaultAppHelmRepoURL, "job", "")
  317. if err != nil {
  318. err = telemetry.Error(ctx, span, err, "error loading latest job chart")
  319. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  320. return
  321. }
  322. conf := &helm.UpgradeReleaseConfig{
  323. Name: helmRelease.Name,
  324. Cluster: cluster,
  325. Repo: c.Repo(),
  326. Registries: registries,
  327. Values: preDeployJobValues,
  328. Chart: chart,
  329. }
  330. _, err = helmAgent.UpgradeReleaseByValues(ctx, conf, c.Config().DOConf, c.Config().ServerConf.DisablePullSecretsInjection, false)
  331. if err != nil {
  332. err = telemetry.Error(ctx, span, err, "error upgrading pre-deploy job chart")
  333. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  334. return
  335. }
  336. }
  337. }
  338. }
  339. // update the app chart
  340. conf := &helm.InstallChartConfig{
  341. Chart: chart,
  342. Name: stackName,
  343. Namespace: namespace,
  344. Values: values,
  345. Cluster: cluster,
  346. Repo: c.Repo(),
  347. Registries: registries,
  348. }
  349. // update the chart
  350. _, err = helmAgent.UpgradeInstallChart(ctx, conf, c.Config().DOConf, c.Config().ServerConf.DisablePullSecretsInjection)
  351. if err != nil {
  352. err = telemetry.Error(ctx, span, err, "error upgrading application")
  353. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
  354. return
  355. }
  356. // update the DB entry
  357. app, err := c.Repo().PorterApp().ReadPorterAppByName(cluster.ID, stackName, request.EnvironmentConfigID)
  358. if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
  359. err = telemetry.Error(ctx, span, err, "error reading app from DB")
  360. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  361. return
  362. }
  363. if app == nil {
  364. err = telemetry.Error(ctx, span, nil, "app with name does not exist in project")
  365. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusForbidden))
  366. return
  367. }
  368. if request.RepoName != "" {
  369. app.RepoName = request.RepoName
  370. }
  371. if request.GitBranch != "" {
  372. app.GitBranch = request.GitBranch
  373. }
  374. if request.BuildContext != "" {
  375. app.BuildContext = request.BuildContext
  376. }
  377. // handles deletion of builder,buildpacks, and dockerfile path
  378. if request.Builder != "" {
  379. if request.Builder == "null" {
  380. app.Builder = ""
  381. } else {
  382. app.Builder = request.Builder
  383. }
  384. }
  385. if request.Buildpacks != "" {
  386. if request.Buildpacks == "null" {
  387. app.Buildpacks = ""
  388. } else {
  389. app.Buildpacks = request.Buildpacks
  390. }
  391. }
  392. if request.Dockerfile != "" {
  393. if request.Dockerfile == "null" {
  394. app.Dockerfile = ""
  395. } else {
  396. app.Dockerfile = request.Dockerfile
  397. }
  398. }
  399. if request.ImageRepoURI != "" {
  400. app.ImageRepoURI = request.ImageRepoURI
  401. }
  402. if request.PullRequestURL != "" {
  403. app.PullRequestURL = request.PullRequestURL
  404. }
  405. telemetry.WithAttributes(
  406. span,
  407. telemetry.AttributeKV{Key: "updated-repo-name", Value: app.RepoName},
  408. telemetry.AttributeKV{Key: "updated-git-branch", Value: app.GitBranch},
  409. telemetry.AttributeKV{Key: "updated-build-context", Value: app.BuildContext},
  410. telemetry.AttributeKV{Key: "updated-builder", Value: app.Builder},
  411. telemetry.AttributeKV{Key: "updated-buildpacks", Value: app.Buildpacks},
  412. telemetry.AttributeKV{Key: "updated-dockerfile", Value: app.Dockerfile},
  413. telemetry.AttributeKV{Key: "updated-image-repo-uri", Value: app.ImageRepoURI},
  414. telemetry.AttributeKV{Key: "updated-pull-request-url", Value: app.PullRequestURL},
  415. )
  416. updatedPorterApp, err := c.Repo().PorterApp().UpdatePorterApp(app)
  417. if err != nil {
  418. err = telemetry.Error(ctx, span, err, "error writing updated app to DB")
  419. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  420. return
  421. }
  422. _, err = createPorterAppEvent(ctx, "SUCCESS", updatedPorterApp.ID, helmRelease.Version+1, imageInfo.Tag, c.Repo().PorterAppEvent())
  423. if err != nil {
  424. err = telemetry.Error(ctx, span, err, "error creating porter app event")
  425. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  426. return
  427. }
  428. c.WriteResult(w, r, updatedPorterApp.ToPorterAppType())
  429. }
  430. }
  431. // createPorterAppEvent creates an event for use in the activity feed
  432. func createPorterAppEvent(ctx context.Context, status string, appID uint, revision int, tag string, repo repository.PorterAppEventRepository) (*models.PorterAppEvent, error) {
  433. event := models.PorterAppEvent{
  434. ID: uuid.New(),
  435. Status: status,
  436. Type: "DEPLOY",
  437. TypeExternalSource: "KUBERNETES",
  438. PorterAppID: appID,
  439. Metadata: map[string]any{
  440. "revision": revision,
  441. "image_tag": tag,
  442. },
  443. }
  444. err := repo.CreateEvent(ctx, &event)
  445. if err != nil {
  446. return nil, err
  447. }
  448. if event.ID == uuid.Nil {
  449. return nil, err
  450. }
  451. return &event, nil
  452. }
  453. func createReleaseJobChart(
  454. ctx context.Context,
  455. stackName string,
  456. values map[string]interface{},
  457. repoUrl string,
  458. registries []*models.Registry,
  459. cluster *models.Cluster,
  460. repo repository.Repository,
  461. ) (*helm.InstallChartConfig, error) {
  462. chart, err := loader.LoadChartPublic(ctx, repoUrl, "job", "")
  463. if err != nil {
  464. return nil, err
  465. }
  466. releaseName := fmt.Sprintf("%s-r", stackName)
  467. namespace := fmt.Sprintf("porter-stack-%s", stackName)
  468. return &helm.InstallChartConfig{
  469. Chart: chart,
  470. Name: releaseName,
  471. Namespace: namespace,
  472. Values: values,
  473. Cluster: cluster,
  474. Repo: repo,
  475. Registries: registries,
  476. }, nil
  477. }
  478. func cloneEnvGroup(c *CreatePorterAppHandler, w http.ResponseWriter, r *http.Request, agent *kubernetes.Agent, envGroups []string, namespace string) {
  479. for _, envGroupName := range envGroups {
  480. cm, _, err := agent.GetLatestVersionedConfigMap(envGroupName, "porter-env-group")
  481. if err != nil {
  482. if errors.Is(err, kubernetes.IsNotFoundError) {
  483. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(
  484. fmt.Errorf("error cloning env group: envgroup %s in namespace %s not found", envGroupName, "porter-env-group"), http.StatusNotFound,
  485. "no config map found for envgroup",
  486. ))
  487. return
  488. }
  489. c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  490. return
  491. }
  492. secret, _, err := agent.GetLatestVersionedSecret(envGroupName, "porter-env-group")
  493. if err != nil {
  494. if errors.Is(err, kubernetes.IsNotFoundError) {
  495. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(
  496. fmt.Errorf("error cloning env group: envgroup %s in namespace %s not found", envGroupName, "porter-env-group"), http.StatusNotFound,
  497. "no k8s secret found for envgroup",
  498. ))
  499. return
  500. }
  501. c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  502. return
  503. }
  504. vars := make(map[string]string)
  505. secretVars := make(map[string]string)
  506. for key, val := range cm.Data {
  507. if !strings.Contains(val, "PORTERSECRET") {
  508. vars[key] = val
  509. }
  510. }
  511. for key, val := range secret.Data {
  512. secretVars[key] = string(val)
  513. }
  514. configMap, err := envgroup.CreateEnvGroup(agent, types.ConfigMapInput{
  515. Name: envGroupName,
  516. Namespace: namespace,
  517. Variables: vars,
  518. SecretVariables: secretVars,
  519. })
  520. if err != nil {
  521. c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  522. return
  523. }
  524. _, err = envgroup.ToEnvGroup(configMap)
  525. if err != nil {
  526. c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  527. return
  528. }
  529. }
  530. }
  531. func assignEnvironmentToApp(c *CreatePorterAppHandler, app *models.PorterApp, projectID, clusterID, envConfID uint) error {
  532. ctx, span := telemetry.NewSpan(context.Background(), "assign-env-to-app")
  533. if app == nil {
  534. return fmt.Errorf("Application does not exist")
  535. }
  536. if envConfID != 0 {
  537. envConf, err := c.Repo().EnvironmentConfig().ReadEnvironmentConfig(projectID, clusterID, envConfID)
  538. if err != nil {
  539. return telemetry.Error(ctx, span, err, "error reading environment config from DB")
  540. }
  541. app.EnvironmentConfigID = envConf.ID
  542. return nil
  543. }
  544. envConf, err := c.Repo().EnvironmentConfig().ReadDefaultEnvironmentConfig(projectID, clusterID)
  545. if err != nil {
  546. return telemetry.Error(ctx, span, err, "error reading environment config from DB")
  547. }
  548. app.EnvironmentConfigID = envConf.ID
  549. return nil
  550. }