create.go 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683
  1. package release
  2. import (
  3. "context"
  4. "encoding/json"
  5. "errors"
  6. "fmt"
  7. "net/http"
  8. "strings"
  9. "time"
  10. "github.com/porter-dev/porter/internal/telemetry"
  11. "github.com/porter-dev/porter/api/server/authz"
  12. "github.com/porter-dev/porter/api/server/handlers"
  13. "github.com/porter-dev/porter/api/server/shared"
  14. "github.com/porter-dev/porter/api/server/shared/apierrors"
  15. "github.com/porter-dev/porter/api/server/shared/config"
  16. "github.com/porter-dev/porter/api/types"
  17. "github.com/porter-dev/porter/internal/analytics"
  18. "github.com/porter-dev/porter/internal/auth/token"
  19. "github.com/porter-dev/porter/internal/encryption"
  20. "github.com/porter-dev/porter/internal/helm"
  21. "github.com/porter-dev/porter/internal/helm/loader"
  22. "github.com/porter-dev/porter/internal/helm/repo"
  23. "github.com/porter-dev/porter/internal/integrations/ci/actions"
  24. "github.com/porter-dev/porter/internal/integrations/ci/gitlab"
  25. "github.com/porter-dev/porter/internal/models"
  26. "github.com/porter-dev/porter/internal/oauth"
  27. "github.com/porter-dev/porter/internal/registry"
  28. "github.com/stefanmcshane/helm/pkg/release"
  29. "golang.org/x/crypto/bcrypt"
  30. "gopkg.in/yaml.v2"
  31. v1 "k8s.io/api/core/v1"
  32. )
  33. type CreateReleaseHandler struct {
  34. handlers.PorterHandlerReadWriter
  35. authz.KubernetesAgentGetter
  36. }
  37. func NewCreateReleaseHandler(
  38. config *config.Config,
  39. decoderValidator shared.RequestDecoderValidator,
  40. writer shared.ResultWriter,
  41. ) *CreateReleaseHandler {
  42. return &CreateReleaseHandler{
  43. PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
  44. KubernetesAgentGetter: authz.NewOutOfClusterAgentGetter(config),
  45. }
  46. }
  47. func (c *CreateReleaseHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  48. tracer, _ := telemetry.InitTracer(r.Context(), c.Config().TelemetryConfig)
  49. defer tracer.Shutdown()
  50. user, _ := r.Context().Value(types.UserScope).(*models.User)
  51. cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
  52. namespace := r.Context().Value(types.NamespaceScope).(string)
  53. operationID := oauth.CreateRandomState()
  54. ctx, span := telemetry.NewSpan(r.Context(), "serve-create-release")
  55. defer span.End()
  56. telemetry.WithAttributes(span,
  57. telemetry.AttributeKV{Key: "project-id", Value: cluster.ProjectID},
  58. telemetry.AttributeKV{Key: "cluster-id", Value: cluster.ID},
  59. telemetry.AttributeKV{Key: "user-email", Value: user.Email},
  60. telemetry.AttributeKV{Key: "namespace", Value: namespace},
  61. )
  62. c.Config().AnalyticsClient.Track(analytics.ApplicationLaunchStartTrack(
  63. &analytics.ApplicationLaunchStartTrackOpts{
  64. ClusterScopedTrackOpts: analytics.GetClusterScopedTrackOpts(user.ID, cluster.ProjectID, cluster.ID),
  65. FlowID: operationID,
  66. },
  67. ))
  68. helmAgent, err := c.GetHelmAgent(ctx, r, cluster, "")
  69. if err != nil {
  70. c.HandleAPIError(w, r, apierrors.NewErrInternal(telemetry.Error(ctx, span, err, "error getting helm agent")))
  71. return
  72. }
  73. request := &types.CreateReleaseRequest{}
  74. if ok := c.DecodeAndValidate(w, r, request); !ok {
  75. return
  76. }
  77. if request.RepoURL == "" {
  78. request.RepoURL = c.Config().ServerConf.DefaultApplicationHelmRepoURL
  79. }
  80. telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "repo-url", Value: request.RepoURL})
  81. // if the repo url is not an addon or application url, validate against the helm repos
  82. if request.RepoURL != c.Config().ServerConf.DefaultAddonHelmRepoURL && request.RepoURL != c.Config().ServerConf.DefaultApplicationHelmRepoURL {
  83. // load the helm repos in the project
  84. hrs, err := c.Repo().HelmRepo().ListHelmReposByProjectID(cluster.ProjectID)
  85. if err != nil {
  86. c.HandleAPIError(w, r, apierrors.NewErrInternal(telemetry.Error(ctx, span, err, "error listing helm repos for project")))
  87. return
  88. }
  89. isValid := repo.ValidateRepoURL(c.Config().ServerConf.DefaultAddonHelmRepoURL, c.Config().ServerConf.DefaultApplicationHelmRepoURL, hrs, request.RepoURL)
  90. if !isValid {
  91. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(
  92. telemetry.Error(ctx, span, err, "invalid repo_url parameter"),
  93. http.StatusBadRequest,
  94. ))
  95. return
  96. }
  97. }
  98. if request.TemplateVersion == "latest" {
  99. request.TemplateVersion = ""
  100. }
  101. chart, err := loader.LoadChartPublic(ctx, request.RepoURL, request.TemplateName, request.TemplateVersion)
  102. if err != nil {
  103. c.HandleAPIError(w, r, apierrors.NewErrInternal(telemetry.Error(ctx, span, err, "error loading public chart")))
  104. return
  105. }
  106. registries, err := c.Repo().Registry().ListRegistriesByProjectID(cluster.ProjectID)
  107. if err != nil {
  108. c.HandleAPIError(w, r, apierrors.NewErrInternal(telemetry.Error(ctx, span, err, "error listing registries")))
  109. return
  110. }
  111. k8sAgent, err := c.GetAgent(r, cluster, "")
  112. if err != nil {
  113. c.HandleAPIError(w, r, apierrors.NewErrInternal(telemetry.Error(ctx, span, err, "error getting k8s agent")))
  114. return
  115. }
  116. // create the namespace if it does not exist already
  117. _, err = k8sAgent.CreateNamespace(namespace, nil)
  118. if err != nil {
  119. c.HandleAPIError(w, r, apierrors.NewErrInternal(telemetry.Error(ctx, span, err, "error creating namespace")))
  120. return
  121. }
  122. conf := &helm.InstallChartConfig{
  123. Chart: chart,
  124. Name: request.Name,
  125. Namespace: namespace,
  126. Values: request.Values,
  127. Cluster: cluster,
  128. Repo: c.Repo(),
  129. Registries: registries,
  130. }
  131. helmRelease, err := helmAgent.InstallChart(ctx, conf, c.Config().DOConf, c.Config().ServerConf.DisablePullSecretsInjection)
  132. if err != nil {
  133. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(
  134. telemetry.Error(ctx, span, err, "error installing a new chart"),
  135. http.StatusBadRequest,
  136. ))
  137. return
  138. }
  139. configMaps := make([]*v1.ConfigMap, 0)
  140. if request.SyncedEnvGroups != nil && len(request.SyncedEnvGroups) > 0 {
  141. for _, envGroupName := range request.SyncedEnvGroups {
  142. // read the attached configmap
  143. cm, _, err := k8sAgent.GetLatestVersionedConfigMap(envGroupName, namespace)
  144. if err != nil {
  145. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(telemetry.Error(ctx, span, err, "Couldn't find the env group"), http.StatusNotFound))
  146. return
  147. }
  148. configMaps = append(configMaps, cm)
  149. }
  150. }
  151. release, err := CreateAppReleaseFromHelmRelease(ctx, c.Config(), cluster.ProjectID, cluster.ID, 0, helmRelease)
  152. if err != nil {
  153. c.HandleAPIError(w, r, apierrors.NewErrInternal(telemetry.Error(ctx, span, err, "error creating app release from helm release")))
  154. return
  155. }
  156. if len(configMaps) > 0 {
  157. for _, cm := range configMaps {
  158. _, err = k8sAgent.AddApplicationToVersionedConfigMap(cm, release.Name)
  159. if err != nil {
  160. telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "release-name", Value: release.Name})
  161. telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "config-map-name", Value: cm.Name})
  162. c.HandleAPIErrorNoWrite(w, r, apierrors.NewErrInternal(telemetry.Error(ctx, span, err, "Couldn't add release to the config map")))
  163. }
  164. }
  165. }
  166. if request.Tags != nil {
  167. tags, err := c.Repo().Tag().LinkTagsToRelease(request.Tags, release)
  168. if err == nil {
  169. release.Tags = append(release.Tags, tags...)
  170. }
  171. }
  172. if request.BuildConfig != nil {
  173. _, err = createBuildConfig(ctx, c.Config(), release, request.BuildConfig)
  174. }
  175. if err != nil {
  176. c.HandleAPIError(w, r, apierrors.NewErrInternal(telemetry.Error(ctx, span, err, "error building config")))
  177. return
  178. }
  179. if request.GitActionConfig != nil {
  180. _, _, err := createGitAction(
  181. ctx,
  182. c.Config(),
  183. user.ID,
  184. cluster.ProjectID,
  185. cluster.ID,
  186. request.GitActionConfig,
  187. request.Name,
  188. namespace,
  189. release,
  190. )
  191. if err != nil {
  192. unwrappedErr := errors.Unwrap(err)
  193. if unwrappedErr != nil {
  194. if errors.Is(unwrappedErr, actions.ErrProtectedBranch) {
  195. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(telemetry.Error(ctx, span, err, "error creating git action on protected branch"), http.StatusConflict))
  196. } else if errors.Is(unwrappedErr, actions.ErrCreatePRForProtectedBranch) {
  197. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(telemetry.Error(ctx, span, err, "error creating PR on protected branch"), http.StatusPreconditionFailed))
  198. }
  199. } else {
  200. c.HandleAPIError(w, r, apierrors.NewErrInternal(telemetry.Error(ctx, span, err, "error creating git action")))
  201. return
  202. }
  203. }
  204. }
  205. c.Config().AnalyticsClient.Track(analytics.ApplicationLaunchSuccessTrack(
  206. &analytics.ApplicationLaunchSuccessTrackOpts{
  207. ApplicationScopedTrackOpts: analytics.GetApplicationScopedTrackOpts(
  208. user.ID,
  209. cluster.ProjectID,
  210. cluster.ID,
  211. release.Name,
  212. release.Namespace,
  213. chart.Metadata.Name,
  214. ),
  215. FlowID: operationID,
  216. },
  217. ))
  218. w.WriteHeader(http.StatusCreated)
  219. }
  220. func CreateAppReleaseFromHelmRelease(
  221. ctx context.Context,
  222. config *config.Config,
  223. projectID, clusterID, stackResourceID uint,
  224. helmRelease *release.Release,
  225. ) (*models.Release, error) {
  226. ctx, span := telemetry.NewSpan(ctx, "create-app-release-from-helm-release")
  227. defer span.End()
  228. telemetry.WithAttributes(span,
  229. telemetry.AttributeKV{Key: "project-id", Value: projectID},
  230. telemetry.AttributeKV{Key: "cluster-id", Value: clusterID},
  231. telemetry.AttributeKV{Key: "stack-resource-id", Value: stackResourceID},
  232. telemetry.AttributeKV{Key: "helm-release-name", Value: helmRelease.Name},
  233. )
  234. token, err := encryption.GenerateRandomBytes(16)
  235. if err != nil {
  236. return nil, err
  237. }
  238. // create release with webhook token in db
  239. image, ok := helmRelease.Config["image"].(map[string]interface{})
  240. if !ok {
  241. return nil, telemetry.Error(ctx, span, nil, "Could not find field image in config")
  242. }
  243. repository := image["repository"]
  244. repoStr, ok := repository.(string)
  245. if !ok {
  246. return nil, telemetry.Error(ctx, span, nil, "Could not find field repository in config")
  247. }
  248. telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "repo-uri", Value: repoStr})
  249. release := &models.Release{
  250. ClusterID: clusterID,
  251. ProjectID: projectID,
  252. Namespace: helmRelease.Namespace,
  253. Name: helmRelease.Name,
  254. WebhookToken: token,
  255. ImageRepoURI: repoStr,
  256. StackResourceID: stackResourceID,
  257. }
  258. return config.Repo.Release().CreateRelease(release)
  259. }
  260. func CreateAddonReleaseFromHelmRelease(
  261. config *config.Config,
  262. projectID, clusterID, stackResourceID uint,
  263. helmRelease *release.Release,
  264. ) (*models.Release, error) {
  265. release := &models.Release{
  266. ClusterID: clusterID,
  267. ProjectID: projectID,
  268. Namespace: helmRelease.Namespace,
  269. Name: helmRelease.Name,
  270. StackResourceID: stackResourceID,
  271. }
  272. return config.Repo.Release().CreateRelease(release)
  273. }
  274. func createGitAction(
  275. ctx context.Context,
  276. config *config.Config,
  277. userID, projectID, clusterID uint,
  278. request *types.CreateGitActionConfigRequest,
  279. name, namespace string,
  280. release *models.Release,
  281. ) (*types.GitActionConfig, []byte, error) {
  282. ctx, span := telemetry.NewSpan(ctx, "create-git-action")
  283. defer span.End()
  284. telemetry.WithAttributes(span,
  285. telemetry.AttributeKV{Key: "project-id", Value: projectID},
  286. telemetry.AttributeKV{Key: "cluster-id", Value: clusterID},
  287. telemetry.AttributeKV{Key: "user-id", Value: userID},
  288. telemetry.AttributeKV{Key: "name", Value: name},
  289. telemetry.AttributeKV{Key: "namespace", Value: namespace},
  290. )
  291. // if the registry was provisioned through Porter, create a repository if necessary
  292. if release != nil && request.RegistryID != 0 {
  293. // read the registry
  294. reg, err := config.Repo.Registry().ReadRegistry(projectID, request.RegistryID)
  295. if err != nil {
  296. return nil, nil, telemetry.Error(ctx, span, err, "could not read repo registry")
  297. }
  298. _reg := registry.Registry(*reg)
  299. regAPI := &_reg
  300. // parse the name from the registry
  301. nameSpl := strings.Split(request.ImageRepoURI, "/")
  302. repoName := nameSpl[len(nameSpl)-1]
  303. telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "repo-name", Value: repoName})
  304. err = regAPI.CreateRepository(ctx, config, repoName)
  305. if err != nil {
  306. return nil, nil, telemetry.Error(ctx, span, err, "could not create repo")
  307. }
  308. }
  309. isDryRun := release == nil
  310. telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "is-dry-run", Value: isDryRun})
  311. encoded := ""
  312. var err error
  313. // if this isn't a dry run, generate the token
  314. if !isDryRun {
  315. encoded, err = getToken(ctx, config, userID, projectID, clusterID, request)
  316. if err != nil {
  317. return nil, nil, telemetry.Error(ctx, span, err, "error getting token")
  318. }
  319. }
  320. var workflowYAML []byte
  321. var gitErr error
  322. if request.GitlabIntegrationID != 0 {
  323. giRunner := &gitlab.GitlabCI{
  324. ServerURL: config.ServerConf.ServerURL,
  325. GitRepoPath: request.GitRepo,
  326. GitBranch: request.GitBranch,
  327. Repo: config.Repo,
  328. ProjectID: projectID,
  329. ClusterID: clusterID,
  330. UserID: userID,
  331. IntegrationID: request.GitlabIntegrationID,
  332. PorterConf: config,
  333. ReleaseName: name,
  334. ReleaseNamespace: namespace,
  335. FolderPath: request.FolderPath,
  336. PorterToken: encoded,
  337. }
  338. gitErr = giRunner.Setup()
  339. } else {
  340. repoSplit := strings.Split(request.GitRepo, "/")
  341. if len(repoSplit) != 2 {
  342. return nil, nil, fmt.Errorf("invalid formatting of repo name")
  343. }
  344. // create the commit in the git repo
  345. gaRunner := &actions.GithubActions{
  346. InstanceName: config.ServerConf.InstanceName,
  347. ServerURL: config.ServerConf.ServerURL,
  348. GithubOAuthIntegration: nil,
  349. GithubAppID: config.GithubAppConf.AppID,
  350. GithubAppSecretPath: config.GithubAppConf.SecretPath,
  351. GithubInstallationID: request.GitRepoID,
  352. GitRepoName: repoSplit[1],
  353. GitRepoOwner: repoSplit[0],
  354. Repo: config.Repo,
  355. ProjectID: projectID,
  356. ClusterID: clusterID,
  357. ReleaseName: name,
  358. ReleaseNamespace: namespace,
  359. GitBranch: request.GitBranch,
  360. DockerFilePath: request.DockerfilePath,
  361. FolderPath: request.FolderPath,
  362. ImageRepoURL: request.ImageRepoURI,
  363. PorterToken: encoded,
  364. Version: "v0.1.0",
  365. ShouldCreateWorkflow: request.ShouldCreateWorkflow,
  366. DryRun: release == nil,
  367. }
  368. // Save the github err for after creating the git action config. However, we
  369. // need to call Setup() in order to get the workflow file before writing the
  370. // action config, in the case of a dry run, since the dry run does not create
  371. // a git action config.
  372. workflowYAML, gitErr = gaRunner.Setup()
  373. if gaRunner.DryRun {
  374. if gitErr != nil {
  375. return nil, nil, telemetry.Error(ctx, span, gitErr, "error setting up git")
  376. }
  377. return nil, workflowYAML, nil
  378. }
  379. }
  380. // handle write to the database
  381. ga, err := config.Repo.GitActionConfig().CreateGitActionConfig(&models.GitActionConfig{
  382. ReleaseID: release.ID,
  383. GitRepo: request.GitRepo,
  384. GitBranch: request.GitBranch,
  385. ImageRepoURI: request.ImageRepoURI,
  386. GitRepoID: request.GitRepoID,
  387. GitlabIntegrationID: request.GitlabIntegrationID,
  388. DockerfilePath: request.DockerfilePath,
  389. FolderPath: request.FolderPath,
  390. IsInstallation: true,
  391. Version: "v0.1.0",
  392. })
  393. if err != nil {
  394. return nil, nil, telemetry.Error(ctx, span, err, "error creating git action config")
  395. }
  396. // update the release in the db with the image repo uri
  397. release.ImageRepoURI = ga.ImageRepoURI
  398. _, err = config.Repo.Release().UpdateRelease(release)
  399. if err != nil {
  400. return nil, nil, telemetry.Error(ctx, span, err, "error updating release")
  401. }
  402. return ga.ToGitActionConfigType(), workflowYAML, gitErr
  403. }
  404. func getToken(
  405. ctx context.Context,
  406. config *config.Config,
  407. userID, projectID, clusterID uint,
  408. request *types.CreateGitActionConfigRequest,
  409. ) (string, error) {
  410. ctx, span := telemetry.NewSpan(ctx, "get-git-action-token")
  411. defer span.End()
  412. telemetry.WithAttributes(span,
  413. telemetry.AttributeKV{Key: "project-id", Value: projectID},
  414. telemetry.AttributeKV{Key: "cluster-id", Value: clusterID},
  415. telemetry.AttributeKV{Key: "user-id", Value: userID},
  416. )
  417. // create a policy for the token
  418. policy := []*types.PolicyDocument{
  419. {
  420. Scope: types.ProjectScope,
  421. Verbs: types.ReadWriteVerbGroup(),
  422. Children: map[types.PermissionScope]*types.PolicyDocument{
  423. types.ClusterScope: {
  424. Scope: types.ClusterScope,
  425. Verbs: types.ReadWriteVerbGroup(),
  426. },
  427. types.RegistryScope: {
  428. Scope: types.RegistryScope,
  429. Verbs: types.ReadVerbGroup(),
  430. },
  431. types.HelmRepoScope: {
  432. Scope: types.HelmRepoScope,
  433. Verbs: types.ReadVerbGroup(),
  434. },
  435. },
  436. },
  437. }
  438. uid, err := encryption.GenerateRandomBytes(16)
  439. if err != nil {
  440. return "", telemetry.Error(ctx, span, err, "error generating uid")
  441. }
  442. policyBytes, err := json.Marshal(policy)
  443. if err != nil {
  444. return "", telemetry.Error(ctx, span, err, "error marshalling policy into json")
  445. }
  446. policyModel := &models.Policy{
  447. ProjectID: projectID,
  448. UniqueID: uid,
  449. CreatedByUserID: userID,
  450. Name: strings.ToLower(fmt.Sprintf("repo-%s-token-policy", request.GitRepo)),
  451. PolicyBytes: policyBytes,
  452. }
  453. policyModel, err = config.Repo.Policy().CreatePolicy(policyModel)
  454. if err != nil {
  455. return "", telemetry.Error(ctx, span, err, "error creating policy")
  456. }
  457. // create the token in the database
  458. tokenUID, err := encryption.GenerateRandomBytes(16)
  459. if err != nil {
  460. return "", telemetry.Error(ctx, span, err, "error generating tokenUID")
  461. }
  462. secretKey, err := encryption.GenerateRandomBytes(16)
  463. if err != nil {
  464. return "", telemetry.Error(ctx, span, err, "error generating secret key")
  465. }
  466. // hash the secret key for storage in the db
  467. hashedToken, err := bcrypt.GenerateFromPassword([]byte(secretKey), 8)
  468. if err != nil {
  469. return "", telemetry.Error(ctx, span, err, "error generating hashedToken")
  470. }
  471. expiresAt := time.Now().Add(time.Hour * 24 * 365)
  472. apiToken := &models.APIToken{
  473. UniqueID: tokenUID,
  474. ProjectID: projectID,
  475. CreatedByUserID: userID,
  476. Expiry: &expiresAt,
  477. Revoked: false,
  478. PolicyUID: policyModel.UniqueID,
  479. PolicyName: policyModel.Name,
  480. Name: strings.ToLower(fmt.Sprintf("repo-%s-token", request.GitRepo)),
  481. SecretKey: hashedToken,
  482. }
  483. apiToken, err = config.Repo.APIToken().CreateAPIToken(apiToken)
  484. if err != nil {
  485. return "", telemetry.Error(ctx, span, err, "error creating api token")
  486. }
  487. // generate porter jwt token
  488. jwt, err := token.GetStoredTokenForAPI(userID, projectID, apiToken.UniqueID, secretKey)
  489. if err != nil {
  490. return "", telemetry.Error(ctx, span, err, "error getting stored token for api")
  491. }
  492. return jwt.EncodeToken(config.TokenConf)
  493. }
  494. func createBuildConfig(
  495. ctx context.Context,
  496. config *config.Config,
  497. release *models.Release,
  498. bcRequest *types.CreateBuildConfigRequest,
  499. ) (*types.BuildConfig, error) {
  500. ctx, span := telemetry.NewSpan(ctx, "create-build-config")
  501. defer span.End()
  502. data, err := json.Marshal(bcRequest.Config)
  503. if err != nil {
  504. return nil, telemetry.Error(ctx, span, err, "error marshalling build config request")
  505. }
  506. // handle write to the database
  507. bc, err := config.Repo.BuildConfig().CreateBuildConfig(&models.BuildConfig{
  508. Builder: bcRequest.Builder,
  509. Buildpacks: strings.Join(bcRequest.Buildpacks, ","),
  510. Config: data,
  511. })
  512. if err != nil {
  513. return nil, telemetry.Error(ctx, span, err, "error creating build config")
  514. }
  515. release.BuildConfig = bc.ID
  516. _, err = config.Repo.Release().UpdateRelease(release)
  517. if err != nil {
  518. return nil, telemetry.Error(ctx, span, err, "error updating release")
  519. }
  520. return bc.ToBuildConfigType(), nil
  521. }
  522. type containerEnvConfig struct {
  523. Container struct {
  524. Env struct {
  525. Normal map[string]string `yaml:"normal"`
  526. } `yaml:"env"`
  527. } `yaml:"container"`
  528. }
  529. func GetGARunner(
  530. ctx context.Context,
  531. config *config.Config,
  532. userID, projectID, clusterID uint,
  533. ga *models.GitActionConfig,
  534. name, namespace string,
  535. release *models.Release,
  536. helmRelease *release.Release,
  537. ) (*actions.GithubActions, error) {
  538. ctx, span := telemetry.NewSpan(ctx, "get-ga-runner")
  539. defer span.End()
  540. cEnv := &containerEnvConfig{}
  541. rawValues, err := yaml.Marshal(helmRelease.Config)
  542. if err == nil {
  543. err = yaml.Unmarshal(rawValues, cEnv)
  544. // if unmarshal error, just set to empty map
  545. if err != nil {
  546. cEnv.Container.Env.Normal = make(map[string]string)
  547. }
  548. }
  549. repoSplit := strings.Split(ga.GitRepo, "/")
  550. if len(repoSplit) != 2 {
  551. return nil, telemetry.Error(ctx, span, nil, "invalid formatting of repo name")
  552. }
  553. // create the commit in the git repo
  554. return &actions.GithubActions{
  555. ServerURL: config.ServerConf.ServerURL,
  556. GithubOAuthIntegration: nil,
  557. BuildEnv: cEnv.Container.Env.Normal,
  558. GithubAppID: config.GithubAppConf.AppID,
  559. GithubAppSecretPath: config.GithubAppConf.SecretPath,
  560. GithubInstallationID: ga.GitRepoID,
  561. GitRepoName: repoSplit[1],
  562. GitRepoOwner: repoSplit[0],
  563. Repo: config.Repo,
  564. ProjectID: projectID,
  565. ClusterID: clusterID,
  566. ReleaseName: name,
  567. GitBranch: ga.GitBranch,
  568. DockerFilePath: ga.DockerfilePath,
  569. FolderPath: ga.FolderPath,
  570. ImageRepoURL: ga.ImageRepoURI,
  571. Version: "v0.1.0",
  572. }, nil
  573. }