create.go 20 KB

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