create.go 22 KB

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